diff options
author | truebrain <truebrain@openttd.org> | 2009-01-12 17:11:45 +0000 |
---|---|---|
committer | truebrain <truebrain@openttd.org> | 2009-01-12 17:11:45 +0000 |
commit | a3dd7506d377b1434f913bd65c019eed52b64b6e (patch) | |
tree | ced1a262eb143ad6e64ec02f4a4c89835c0c32fd /src | |
parent | 9294f9616866b9778c22076c19b5a32b4f85f788 (diff) | |
download | openttd-a3dd7506d377b1434f913bd65c019eed52b64b6e.tar.xz |
(svn r15027) -Merge: tomatos and bananas left to be, here is NoAI for all to see.
NoAI is an API (a framework) to build your own AIs in. See:
http://wiki.openttd.org/wiki/index.php/AI:Main_Page
With many thanks to:
- glx and Rubidium for their syncing, feedback and hard work
- Yexo for his feedback, patches, and AIs which tested the system very deep
- Morloth for his feedback and patches
- TJIP for hosting a challenge which kept NoAI on track
- All AI authors for testing our AI API, and all other people who helped in one way or another
-Remove: all old AIs and their cheats/hacks
Diffstat (limited to 'src')
224 files changed, 21510 insertions, 7487 deletions
diff --git a/src/ai/ai.cpp b/src/ai/ai.cpp deleted file mode 100644 index 01c8dcd68..000000000 --- a/src/ai/ai.cpp +++ /dev/null @@ -1,244 +0,0 @@ -/* $Id$ */ - -/** @file ai.cpp Base for all AIs. */ - -#include "../stdafx.h" -#include "../openttd.h" -#include "../variables.h" -#include "../command_func.h" -#include "../network/network.h" -#include "../core/alloc_func.hpp" -#include "../company_func.h" -#include "../company_base.h" -#include "ai.h" -#include "default/default.h" -#include "trolly/trolly.h" -#include "../signal_func.h" - -AIStruct _ai; -AICompany _ai_company[MAX_COMPANIES]; - -/** - * Dequeues commands put in the queue via AI_PutCommandInQueue. - */ -static void AI_DequeueCommands(CompanyID company) -{ - AICommand *com, *entry_com; - - entry_com = _ai_company[company].queue; - - /* It happens that DoCommandP issues a new DoCommandAI which adds a new command - * to this very same queue (don't argue about this, if it currently doesn't - * happen I can tell you it will happen with AIScript -- TrueLight). If we - * do not make the queue NULL, that commands will be dequeued immediatly. - * Therefor we safe the entry-point to entry_com, and make the queue NULL, so - * the new queue can be safely built up. */ - _ai_company[company].queue = NULL; - _ai_company[company].queue_tail = NULL; - - /* Dequeue all commands */ - while ((com = entry_com) != NULL) { - _current_company = company; - - DoCommandP(com->tile, com->p1, com->p2, com->cmd, com->callback, com->text); - - /* Free item */ - entry_com = com->next; - free(com->text); - free(com); - } -} - -/** - * Needed for SP; we need to delay DoCommand with 1 tick, because else events - * will make infinite loops (AIScript). - */ -static void AI_PutCommandInQueue(CompanyID company, TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const char *text = NULL) -{ - AICommand *com; - - if (_ai_company[company].queue_tail == NULL) { - /* There is no item in the queue yet, create the queue */ - _ai_company[company].queue = MallocT<AICommand>(1); - _ai_company[company].queue_tail = _ai_company[company].queue; - } else { - /* Add an item at the end */ - _ai_company[company].queue_tail->next = MallocT<AICommand>(1); - _ai_company[company].queue_tail = _ai_company[company].queue_tail->next; - } - - /* This is our new item */ - com = _ai_company[company].queue_tail; - - /* Assign the info */ - com->tile = tile; - com->p1 = p1; - com->p2 = p2; - com->cmd = cmd; - com->callback = callback; - com->next = NULL; - com->text = text == NULL ? NULL : strdup(text); -} - -/** - * Executes a raw DoCommand for the AI. - */ -CommandCost AI_DoCommandCc(TileIndex tile, uint32 p1, uint32 p2, uint32 flags, uint32 cmd, CommandCallback *callback, const char *text) -{ - CompanyID old_local_company; - CommandCost res; - - /* If you enable DC_EXEC with DC_QUERY_COST you are a really strange - * person.. should we check for those funny jokes? - */ - - /* First, do a test-run to see if we can do this */ - res = DoCommand(tile, p1, p2, flags & ~DC_EXEC, cmd, text); - /* The command failed, or you didn't want to execute, or you are quering, return */ - if (CmdFailed(res) || !(flags & DC_EXEC) || (flags & DC_QUERY_COST)) { - return res; - } - - /* NetworkSend_Command needs _local_company to be set correctly, so - * adjust it, and put it back right after the function */ - old_local_company = _local_company; - _local_company = _current_company; - -#ifdef ENABLE_NETWORK - /* Send the command */ - if (_networking) { - /* Network is easy, send it to his handler */ - NetworkSend_Command(tile, p1, p2, cmd, callback, text); - } else { -#else - { -#endif - /* If we execute BuildCommands directly in SP, we have a big problem with events - * so we need to delay is for 1 tick */ - AI_PutCommandInQueue(_current_company, tile, p1, p2, cmd, callback, text); - } - - /* Set _local_company back */ - _local_company = old_local_company; - - return res; -} - - -CommandCost AI_DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint32 flags, uint32 cmd, const char *text) -{ - return AI_DoCommandCc(tile, p1, p2, flags, cmd, NULL, text); -} - - -/** - * Run 1 tick of the AI. Don't overdo it, keep it realistic. - */ -static void AI_RunTick(CompanyID company) -{ - extern void AiNewDoGameLoop(Company *c); - - Company *c = GetCompany(company); - _current_company = company; - - if (_settings_game.ai.ainew_active) { - AiNewDoGameLoop(c); - } else { - /* Enable all kind of cheats the old AI needs in order to operate correctly... */ - _is_old_ai_company = true; - AiDoGameLoop(c); - _is_old_ai_company = false; - } - - /* AI could change some track, so update signals */ - UpdateSignalsInBuffer(); -} - - -/** - * The gameloop for AIs. - * Handles one tick for all the AIs. - */ -void AI_RunGameLoop() -{ - /* Don't do anything if ai is disabled */ - if (!_ai.enabled) return; - - /* Don't do anything if we are a network-client, or the AI has been disabled */ - if (_networking && (!_network_server || !_settings_game.ai.ai_in_multiplayer)) return; - - /* New tick */ - _ai.tick++; - - /* Make sure the AI follows the difficulty rule.. */ - assert(_settings_game.difficulty.competitor_speed <= 4); - if ((_ai.tick & ((1 << (4 - _settings_game.difficulty.competitor_speed)) - 1)) != 0) return; - - /* Check for AI-client (so joining a network with an AI) */ - if (!_networking || _network_server) { - /* Check if we want to run AIs (server or SP only) */ - const Company *c; - - FOR_ALL_COMPANIES(c) { - if (c->is_ai) { - /* This should always be true, else something went wrong... */ - assert(_ai_company[c->index].active); - - /* Run the script */ - AI_DequeueCommands(c->index); - AI_RunTick(c->index); - } - } - } - - _current_company = OWNER_NONE; -} - -/** - * A new AI sees the day of light. You can do here what ever you think is needed. - */ -void AI_StartNewAI(CompanyID company) -{ - assert(IsValidCompanyID(company)); - - /* Called if a new AI is booted */ - _ai_company[company].active = true; -} - -/** - * This AI company died. Give it some chance to make a final puf. - */ -void AI_CompanyDied(CompanyID company) -{ - /* Called if this AI died */ - _ai_company[company].active = false; - - if (_companies_ainew[company].pathfinder == NULL) return; - - AyStarMain_Free(_companies_ainew[company].pathfinder); - delete _companies_ainew[company].pathfinder; - _companies_ainew[company].pathfinder = NULL; - -} - -/** - * Initialize some AI-related stuff. - */ -void AI_Initialize() -{ - /* First, make sure all AIs are DEAD! */ - AI_Uninitialize(); - - memset(&_ai, 0, sizeof(_ai)); - memset(&_ai_company, 0, sizeof(_ai_company)); - - _ai.enabled = true; -} - -/** - * Deinitializer for AI-related stuff. - */ -void AI_Uninitialize() -{ - for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) AI_CompanyDied(c); -} diff --git a/src/ai/ai.h b/src/ai/ai.h deleted file mode 100644 index 0f18490d5..000000000 --- a/src/ai/ai.h +++ /dev/null @@ -1,115 +0,0 @@ -/* $Id$ */ - -/** @file ai.h Base functions for all AIs. */ - -#ifndef AI_H -#define AI_H - -#include "../network/network.h" -#include "../command_type.h" -#include "../core/random_func.hpp" -#include "../settings_type.h" - -/* How DoCommands look like for an AI */ -struct AICommand { - uint32 tile; - uint32 p1; - uint32 p2; - uint32 cmd; - CommandCallback *callback; - - char *text; - uint uid; - - AICommand *next; -}; - -/* The struct for an AIScript Company */ -struct AICompany { - bool active; ///< Is this AI active? - AICommand *queue; ///< The commands that he has in his queue - AICommand *queue_tail; ///< The tail of this queue -}; - -/* The struct to keep some data about the AI in general */ -struct AIStruct { - /* General */ - bool enabled; ///< Is AI enabled? - uint tick; ///< The current tick (something like _frame_counter, only for AIs) -}; - -extern AIStruct _ai; -extern AICompany _ai_company[MAX_COMPANIES]; - -// ai.c -void AI_StartNewAI(CompanyID company); -void AI_CompanyDied(CompanyID company); -void AI_RunGameLoop(); -void AI_Initialize(); -void AI_Uninitialize(); -CommandCost AI_DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint32 flags, uint procc, const char *text = NULL); -CommandCost AI_DoCommandCc(TileIndex tile, uint32 p1, uint32 p2, uint32 flags, uint procc, CommandCallback *callback, const char *text = NULL); - -/** Is it allowed to start a new AI. - * This function checks some boundries to see if we should launch a new AI. - * @return True if we can start a new AI. - */ -static inline bool AI_AllowNewAI() -{ - /* If disabled, no AI */ - if (!_ai.enabled) - return false; - - /* If in network, but no server, no AI */ - if (_networking && !_network_server) - return false; - - /* If in network, and server, possible AI */ - if (_networking && _network_server) { - /* Do we want AIs in multiplayer? */ - if (!_settings_game.ai.ai_in_multiplayer) - return false; - - /* Only the NewAI is allowed... sadly enough the old AI just doesn't support this - * system, because all commands are delayed by at least 1 tick, which causes - * a big problem, because it uses variables that are only set AFTER the command - * is really executed... */ - if (!_settings_game.ai.ainew_active) - return false; - } - - return true; -} - -#define AI_CHANCE16(a, b) ((uint16) AI_Random() <= (uint16)((65536 * a) / b)) -#define AI_CHANCE16R(a, b, r) ((uint16)(r = AI_Random()) <= (uint16)((65536 * a) / b)) - -/** - * The random-function that should be used by ALL AIs. - */ -static inline uint AI_RandomRange(uint max) -{ - /* We pick RandomRange if we are in SP (so when saved, we do the same over and over) - * but we pick InteractiveRandomRange if we are a network_server or network-client. - */ - if (_networking) - return InteractiveRandomRange(max); - else - return RandomRange(max); -} - -/** - * The random-function that should be used by ALL AIs. - */ -static inline uint32 AI_Random() -{ -/* We pick RandomRange if we are in SP (so when saved, we do the same over and over) - * but we pick InteractiveRandomRange if we are a network_server or network-client. - */ - if (_networking) - return InteractiveRandom(); - else - return Random(); -} - -#endif /* AI_H */ diff --git a/src/ai/ai.hpp b/src/ai/ai.hpp new file mode 100644 index 000000000..bd1c2a907 --- /dev/null +++ b/src/ai/ai.hpp @@ -0,0 +1,102 @@ +/* $Id$ */ + +/** @file ai.hpp Base functions for all AIs. */ + +#ifndef AI_HPP +#define AI_HPP + +#include "api/ai_event_types.hpp" + +#ifndef AI_CONFIG_HPP +struct ltstr { bool operator()(const char *s1, const char *s2) const { return strcmp(s1, s2) < 0; } }; +#endif /* AI_CONFIG_HPP */ +typedef std::map<const char *, class AIInfo *, ltstr> AIInfoList; + + +void CcAI(bool success, TileIndex tile, uint32 p1, uint32 p2); + +class AI { +public: + /** + * Is it possible to start a new AI company? + * @return True if a new AI company can be started. + */ + static bool CanStartNew(); + + /** + * Start a new AI company. + * @param company At which slot the AI company should start. + */ + static void StartNew(CompanyID company); + + /** + * Called every game-tick to let AIs do something. + */ + static void GameLoop(); + + /** + * Get the current AI tick. + */ + static uint GetTick(); + + /** + * Stop a company to be controlled by an AI. + * @param company The company from which the AI needs to detach. + * @pre !IsHumanCompany(company). + */ + static void Stop(CompanyID company); + + /** + * Kill any and all AIs we manage. + */ + static void KillAll(); + + /** + * Initialize the AI system. + */ + static void Initialize(); + + /** + * Uninitialize the AI system + * @param keepConfig Should we keep AIConfigs, or can we free that memory? + */ + static void Uninitialize(bool keepConfig); + + /** + * Reset all AIConfigs, and make them reload their AIInfo. + * If the AIInfo could no longer be found, an error is reported to the user. + */ + static void ResetConfig(); + + /** + * Queue a new event for an AI. + */ + static void NewEvent(CompanyID company, AIEvent *event); + + /** + * Broadcast a new event to all active AIs. + */ + static void BroadcastNewEvent(AIEvent *event, CompanyID skip_company = MAX_COMPANIES); + + /** + * Save data from an AI to a savegame. + */ + static void Save(CompanyID company); + + /** + * Load data for an AI from a savegame. + */ + static void Load(CompanyID company); + + static char *GetConsoleList(char *p, const char *last); + static const AIInfoList *GetInfoList(); + static AIInfo *GetCompanyInfo(const char *name); + static bool ImportLibrary(const char *library, const char *class_name, int version, HSQUIRRELVM vm); + static void Rescan(); + +private: + static uint frame_counter; + static class AIScanner *ai_scanner; +}; + +#endif /* AI_HPP */ diff --git a/src/ai/ai_config.cpp b/src/ai/ai_config.cpp new file mode 100644 index 000000000..f5d22bdaa --- /dev/null +++ b/src/ai/ai_config.cpp @@ -0,0 +1,143 @@ +/* $Id$ */ + +/** @file ai_config.cpp Implementation of AIConfig. */ + +#include "../stdafx.h" +#include "../openttd.h" +#include "../settings_type.h" +#include "ai.hpp" +#include "ai_config.hpp" +#include "ai_info.hpp" + +void AIConfig::ChangeAI(const char *name) +{ + free((void *)this->name); + this->name = (name == NULL) ? NULL : strdup(name); + this->info = (name == NULL) ? NULL : AI::GetCompanyInfo(this->name); + + for (SettingValueList::iterator it = this->settings.begin(); it != this->settings.end(); it++) { + free((void*)(*it).first); + } + this->settings.clear(); +} + +AIConfig::AIConfig(const AIConfig *config) : + name(NULL), + info(NULL) +{ + this->name = (config->name == NULL) ? NULL : strdup(config->name); + this->info = config->info; + + for (SettingValueList::const_iterator it = config->settings.begin(); it != config->settings.end(); it++) { + this->settings[strdup((*it).first)] = (*it).second; + } +} + +AIConfig::~AIConfig() +{ + this->ChangeAI(NULL); +} + +AIInfo *AIConfig::GetInfo() +{ + return this->info; +} + +bool AIConfig::ResetInfo() +{ + this->info = AI::GetCompanyInfo(this->name); + return this->info != NULL; +} + +AIConfig *AIConfig::GetConfig(CompanyID company, bool forceNewgameSetting) +{ + AIConfig **config; + if (!forceNewgameSetting) { + config = (_game_mode == GM_MENU) ? &_settings_newgame.ai_config[company] : &_settings_game.ai_config[company]; + } else { + config = &_settings_newgame.ai_config[company]; + } + if (*config == NULL) *config = new AIConfig(); + return *config; +} + +int AIConfig::GetSetting(const char *name) +{ + assert(this->info != NULL); + + SettingValueList::iterator it = this->settings.find(name); + /* Return the default value if the setting is not set, or if we are in a not-custom difficult level */ + if (it == this->settings.end() || ((_game_mode == GM_MENU) ? _settings_newgame.difficulty.diff_level : _settings_game.difficulty.diff_level) != 3) { + return this->info->GetSettingDefaultValue(name); + } + return (*it).second; +} + +void AIConfig::SetSetting(const char *name, int value) +{ + /* You can only set ai specific settings if an AI is selected. */ + assert(strcmp(name, "start_date") == 0 || this->info != NULL); + + SettingValueList::iterator it = this->settings.find(name); + if (it != this->settings.end()) { + (*it).second = value; + } else { + this->settings[strdup(name)] = value; + } +} + +bool AIConfig::HasAI() +{ + return this->info != NULL; +} + +const char *AIConfig::GetName() +{ + return this->name; +} + +void AIConfig::StringToSettings(const char *value) +{ + char *value_copy = strdup(value); + char *s = value_copy; + + while (s != NULL) { + /* Analyze the string ('name=value,name=value\0') */ + char *item_name = s; + s = strchr(s, '='); + if (s == NULL) break; + if (*s == '\0') break; + *s = '\0'; + s++; + + char *item_value = s; + s = strchr(s, ','); + if (s != NULL) { + *s = '\0'; + s++; + } + + this->SetSetting(item_name, atoi(item_value)); + } + free(value_copy); +} + +void AIConfig::SettingsToString(char *string, int size) +{ + string[0] = '\0'; + for (SettingValueList::iterator it = this->settings.begin(); it != this->settings.end(); it++) { + char no[10]; + snprintf(no, sizeof(no), "%d", (*it).second); + + /* Check if the string would fit in the destination */ + size -= strlen((*it).first) - 1 - strlen(no) - 1; + /* If it doesn't fit, skip the next settings */ + if (size <= 0) return; + + strcat(string, (*it).first); + strcat(string, "="); + strcat(string, no); + strcat(string, ","); + } + string[strlen(string) - 1] = '\0'; +} diff --git a/src/ai/ai_config.hpp b/src/ai/ai_config.hpp new file mode 100644 index 000000000..c628a855f --- /dev/null +++ b/src/ai/ai_config.hpp @@ -0,0 +1,91 @@ +/* $Id$ */ + +/** @file ai_config.hpp AIConfig stores the configuration settings of every AI. */ + +#ifndef AI_CONFIG_HPP +#define AI_CONFIG_HPP + +#include <map> + +#ifndef AI_HPP +struct ltstr { bool operator()(const char *s1, const char *s2) const { return strcmp(s1, s2) < 0; } }; +#endif /* AI_HPP */ + +class AIConfig { +private: + typedef std::map<const char *, int, ltstr> SettingValueList; + +public: + AIConfig() : + name(NULL), + info(NULL) + {} + AIConfig(const AIConfig *config); + ~AIConfig(); + + /** + * Set another AI to be loaded in this slot. + */ + void ChangeAI(const char *name); + + /** + * When ever the AI Scanner is reloaded, all infos become invalid. This + * function tells AIConfig about this. + * @return True if the reset was successfull, false if the AI was no longer + * found. + */ + bool ResetInfo(); + + /** + * Get the AIInfo linked to this AIConfig. + */ + class AIInfo *GetInfo(); + + /** + * Get the config of a company. + */ + static AIConfig *GetConfig(CompanyID company, bool forceNewgameSetting = false); + + /** + * Get the value of a setting for this config. It might fallback to his + * 'info' to find the default value (if not set or if not-custom difficulty + * level). + * @return The (default) value of the setting, or -1 if the setting was not + * found. + */ + int GetSetting(const char *name); + + /** + * Set the value of a setting for this config. + */ + void SetSetting(const char *name, int value); + + /** + * Is this config attached to an AI? + */ + bool HasAI(); + + /** + * Get the name of the AI. + */ + const char *GetName(); + + /** + * Convert a string which is stored in the config file or savegames to + * custom settings of this AI. + */ + void StringToSettings(const char *value); + + /** + * Convert the custom settings to a string that can be stored in the config + * file or savegames. + */ + void SettingsToString(char *string, int size); + +private: + const char *name; + class AIInfo *info; + SettingValueList settings; +}; + +#endif /* AI_CONFIG_HPP */ diff --git a/src/ai/ai_core.cpp b/src/ai/ai_core.cpp new file mode 100644 index 000000000..f0a82f771 --- /dev/null +++ b/src/ai/ai_core.cpp @@ -0,0 +1,254 @@ +/* $Id$ */ + +/** @file ai_core.cpp Implementation of AI. */ + +#include "../stdafx.h" +#include "../openttd.h" +#include "../company_type.h" +#include "../company_base.h" +#include "../company_func.h" +#include "../debug.h" +#include "../network/network.h" +#include "../settings_type.h" +#include "../window_type.h" +#include "../window_func.h" +#include "../command_func.h" +#include "ai.hpp" +#include "ai_info.hpp" +#include "ai_scanner.hpp" +#include "ai_instance.hpp" +#include "ai_config.hpp" + +/* static */ uint AI::frame_counter = 0; +/* static */ AIScanner *AI::ai_scanner = NULL; + +/* static */ bool AI::CanStartNew() +{ + /* Only allow new AIs on the server and only when that is allowed in multiplayer */ + return !_networking || (_network_server && _settings_game.ai.ai_in_multiplayer); +} + +/* static */ void AI::StartNew(CompanyID company) +{ + assert(IsValidCompanyID(company)); + + /* Clients shouldn't start AIs */ + if (_networking && !_network_server) return; + + AIInfo *info = AIConfig::GetConfig(company)->GetInfo(); + if (info == NULL) { + info = AI::ai_scanner->SelectRandomAI(); + assert(info != NULL); + /* Load default data and store the name in the settings */ + AIConfig::GetConfig(company)->ChangeAI(info->GetDirName()); + } + + _current_company = company; + Company *c = GetCompany(company); + + c->ai_info = info; + c->ai_instance = new AIInstance(info); + + InvalidateWindowData(WC_AI_DEBUG, 0, -1); + return; +} + +/* static */ void AI::GameLoop() +{ + /* If we are in networking, only servers run this function, and that only if it is allowed */ + if (_networking && (!_network_server || !_settings_game.ai.ai_in_multiplayer)) return; + + /* The speed with which AIs go, is limited by the 'competitor_speed' */ + AI::frame_counter++; + assert(_settings_game.difficulty.competitor_speed <= 4); + if ((AI::frame_counter & ((1 << (4 - _settings_game.difficulty.competitor_speed)) - 1)) != 0) return; + + const Company *c; + FOR_ALL_COMPANIES(c) { + if (!IsHumanCompany(c->index)) { + _current_company = c->index; + c->ai_instance->GameLoop(); + } + } + + _current_company = OWNER_NONE; +} + +/* static */ uint AI::GetTick() +{ + return AI::frame_counter; +} + +/* static */ void AI::Stop(CompanyID company) +{ + if (_networking && !_network_server) return; + + _current_company = company; + Company *c = GetCompany(company); + + delete c->ai_instance; + c->ai_instance = NULL; + + InvalidateWindowData(WC_AI_DEBUG, 0, -1); +} + +/* static */ void AI::KillAll() +{ + /* It might happen there are no companies .. than we have nothing to loop */ + if (GetCompanyPoolSize() == 0) return; + + const Company *c; + FOR_ALL_COMPANIES(c) { + if (!IsHumanCompany(c->index)) AI::Stop(c->index); + } +} + +/* static */ void AI::Initialize() +{ + if (AI::ai_scanner != NULL) AI::Uninitialize(true); + + AI::frame_counter = 0; + if (AI::ai_scanner == NULL) AI::ai_scanner = new AIScanner(); +} + +/* static */ void AI::Uninitialize(bool keepConfig) +{ + AI::KillAll(); + + if (keepConfig) { + /* Run a rescan, which indexes all AIInfos again, and check if we can + * still load all the AIS, while keeping the configs in place */ + Rescan(); + } else { + delete AI::ai_scanner; + AI::ai_scanner = NULL; + + for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) { + if (_settings_game.ai_config[c] != NULL) { + delete _settings_game.ai_config[c]; + _settings_game.ai_config[c] = NULL; + } + if (_settings_newgame.ai_config[c] != NULL) { + delete _settings_newgame.ai_config[c]; + _settings_newgame.ai_config[c] = NULL; + } + } + } +} + +/* static */ void AI::ResetConfig() +{ + /* Check for both newgame as current game if we can reload the AIInfo insde + * the AIConfig. If not, remove the AI from the list (which will assign + * a random new AI on reload). */ + for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) { + if (_settings_game.ai_config[c] != NULL && _settings_game.ai_config[c]->HasAI()) { + if (!_settings_game.ai_config[c]->ResetInfo()) { + DEBUG(ai, 0, "After a reload, the AI by the name '%s' was no longer found, and removed from the list.", _settings_game.ai_config[c]->GetName()); + _settings_game.ai_config[c]->ChangeAI(NULL); + } + } + if (_settings_newgame.ai_config[c] != NULL && _settings_newgame.ai_config[c]->HasAI()) { + if (!_settings_newgame.ai_config[c]->ResetInfo()) { + DEBUG(ai, 0, "After a reload, the AI by the name '%s' was no longer found, and removed from the list.", _settings_newgame.ai_config[c]->GetName()); + _settings_newgame.ai_config[c]->ChangeAI(NULL); + } + } + } +} + +/* static */ void AI::NewEvent(CompanyID company, AIEvent *event) +{ + /* Clients should ignore events */ + if (_networking && !_network_server) return; + + /* Only AIs can have an event-queue */ + if (!IsValidCompanyID(company) || IsHumanCompany(company)) return; + + /* Queue the event */ + CompanyID old_company = _current_company; + _current_company = company; + AIEventController::InsertEvent(event); + _current_company = old_company; +} + +/* static */ void AI::BroadcastNewEvent(AIEvent *event, CompanyID skip_company) +{ + /* Clients should ignore events */ + if (_networking && !_network_server) return; + + /* Try to send the event to all AIs */ + for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) { + if (c != skip_company) AI::NewEvent(c, event); + } +} + +void CcAI(bool success, TileIndex tile, uint32 p1, uint32 p2) +{ + AIObject::SetLastCommandRes(success); + + if (!success) { + AIObject::SetLastError(AIError::StringToError(_error_message)); + } else { + AIObject::IncreaseDoCommandCosts(AIObject::GetLastCost()); + } + + GetCompany(_current_company)->ai_instance->Continue(); +} + +/* static */ void AI::Save(CompanyID company) +{ + if (!_networking || _network_server) { + assert(IsValidCompanyID(company)); + assert(GetCompany(company)->ai_instance != NULL); + + CompanyID old_company = _current_company; + _current_company = company; + GetCompany(company)->ai_instance->Save(); + _current_company = old_company; + } else { + AIInstance::SaveEmpty(); + } +} + +/* static */ void AI::Load(CompanyID company) +{ + if (!_networking || _network_server) { + assert(IsValidCompanyID(company)); + assert(GetCompany(company)->ai_instance != NULL); + + CompanyID old_company = _current_company; + _current_company = company; + GetCompany(company)->ai_instance->Load(); + _current_company = old_company; + } else { + /* Read, but ignore, the load data */ + AIInstance::LoadEmpty(); + } +} + +/* static */ char *AI::GetConsoleList(char *p, const char *last) +{ + return AI::ai_scanner->GetAIConsoleList(p, last); +} + +/* static */ const AIInfoList *AI::GetInfoList() +{ + return AI::ai_scanner->GetAIInfoList(); +} + +/* static */ AIInfo *AI::GetCompanyInfo(const char *name) +{ + return AI::ai_scanner->FindAI(name); +} + +/* static */ bool AI::ImportLibrary(const char *library, const char *class_name, int version, HSQUIRRELVM vm) +{ + return AI::ai_scanner->ImportLibrary(library, class_name, version, vm, GetCompany(_current_company)->ai_instance->GetController()); +} + +/* static */ void AI::Rescan() +{ + AI::ai_scanner->RescanAIDir(); + ResetConfig(); +} diff --git a/src/ai/ai_gui.cpp b/src/ai/ai_gui.cpp new file mode 100644 index 000000000..8272cc966 --- /dev/null +++ b/src/ai/ai_gui.cpp @@ -0,0 +1,257 @@ +/* $Id$ */ + +/** @file ai_gui.cpp Window for configuring the AIs */ + +#include "../stdafx.h" +#include "../openttd.h" +#include "../gui.h" +#include "../window_gui.h" +#include "../company_func.h" +#include "../company_base.h" +#include "../company_gui.h" +#include "../economy_func.h" +#include "../variables.h" +#include "../cargotype.h" +#include "../strings_func.h" +#include "../core/alloc_func.hpp" +#include "../window_func.h" +#include "../date_func.h" +#include "../gfx_func.h" +#include "../debug.h" +#include "../command_func.h" +#include "../network/network.h" + +#include "ai.hpp" +#include "api/ai_types.hpp" +#include "api/ai_controller.hpp" +#include "api/ai_object.hpp" +#include "api/ai_log.hpp" +#include "ai_info.hpp" + +#include "table/strings.h" +#include "../table/sprites.h" + +struct AIDebugWindow : public Window { + enum AIDebugWindowWidgets { + AID_WIDGET_CLOSEBOX = 0, + AID_WIDGET_CAPTION, + AID_WIDGET_VIEW, + AID_WIDGET_NAME_TEXT, + AID_WIDGET_RELOAD_TOGGLE, + AID_WIDGET_LOG_PANEL, + AID_WIDGET_SCROLLBAR, + AID_WIDGET_UNUSED_1, + AID_WIDGET_UNUSED_2, + AID_WIDGET_UNUSED_3, + AID_WIDGET_UNUSED_4, + AID_WIDGET_UNUSED_5, + AID_WIDGET_UNUSED_6, + AID_WIDGET_UNUSED_7, + + AID_WIDGET_COMPANY_BUTTON_START, + AID_WIDGET_COMPANY_BUTTON_END = AID_WIDGET_COMPANY_BUTTON_START + 14, + }; + + static CompanyID ai_debug_company; + int redraw_timer; + + AIDebugWindow(const WindowDesc *desc, WindowNumber number) : Window(desc, number) + { + /* Disable the companies who are not active or not an AI */ + for (CompanyID i = COMPANY_FIRST; i < MAX_COMPANIES; i++) { + this->SetWidgetDisabledState(i + AID_WIDGET_COMPANY_BUTTON_START, !IsValidCompanyID(i) || !GetCompany(i)->is_ai); + } + this->DisableWidget(AID_WIDGET_RELOAD_TOGGLE); + + this->vscroll.cap = 14; + this->vscroll.pos = 0; + this->resize.step_height = 12; + + if (ai_debug_company != INVALID_COMPANY) this->LowerWidget(ai_debug_company + AID_WIDGET_COMPANY_BUTTON_START); + + this->FindWindowPlacementAndResize(desc); + } + + virtual void OnPaint() + { + /* Check if the currently selected company is still active. */ + if (ai_debug_company == INVALID_COMPANY || !IsValidCompanyID(ai_debug_company)) { + if (ai_debug_company != INVALID_COMPANY) { + /* Raise and disable the widget for the previous selection. */ + this->RaiseWidget(ai_debug_company + AID_WIDGET_COMPANY_BUTTON_START); + this->DisableWidget(ai_debug_company + AID_WIDGET_COMPANY_BUTTON_START); + + ai_debug_company = INVALID_COMPANY; + } + + for (CompanyID i = COMPANY_FIRST; i < MAX_COMPANIES; i++) { + if (IsValidCompanyID(i) && GetCompany(i)->is_ai) { + /* Lower the widget corresponding to this company. */ + this->LowerWidget(i + AID_WIDGET_COMPANY_BUTTON_START); + + ai_debug_company = i; + break; + } + } + } + + /* Update "Reload AI" button */ + this->SetWidgetDisabledState(AID_WIDGET_RELOAD_TOGGLE, ai_debug_company == INVALID_COMPANY); + + /* Draw standard stuff */ + this->DrawWidgets(); + + /* If there are no active companies, don't display anything else. */ + if (ai_debug_company == INVALID_COMPANY) return; + + /* Paint the company icons */ + for (CompanyID i = COMPANY_FIRST; i < MAX_COMPANIES; i++) { + if (!IsValidCompanyID(i) || !GetCompany(i)->is_ai) { + /* Check if we have the company as an active company */ + if (!this->IsWidgetDisabled(i + AID_WIDGET_COMPANY_BUTTON_START)) { + /* Bah, company gone :( */ + this->DisableWidget(i + AID_WIDGET_COMPANY_BUTTON_START); + + /* We need a repaint */ + this->SetDirty(); + } + continue; + } + + /* Check if we have the company marked as inactive */ + if (this->IsWidgetDisabled(i + AID_WIDGET_COMPANY_BUTTON_START)) { + /* New AI! Yippie :p */ + this->EnableWidget(i + AID_WIDGET_COMPANY_BUTTON_START); + + /* We need a repaint */ + this->SetDirty(); + } + + byte x = (i == ai_debug_company) ? 1 : 0; + DrawCompanyIcon(i, (i % 8) * 37 + 13 + x, (i < 8 ? 0 : 13) + 16 + x); + } + + /* Draw the AI name */ + AIInfo *info = GetCompany(ai_debug_company)->ai_info; + assert(info != NULL); + DoDrawString(info->GetName(), 7, 47, TC_BLACK); + + CompanyID old_company = _current_company; + _current_company = ai_debug_company; + AILog::LogData *log = (AILog::LogData *)AIObject::GetLogPointer(); + _current_company = old_company; + + SetVScrollCount(this, (log == NULL) ? 0 : log->used); + this->InvalidateWidget(AID_WIDGET_SCROLLBAR); + if (log == NULL) return; + + int y = 6; + for (int i = this->vscroll.pos; i < (this->vscroll.cap + this->vscroll.pos); i++) { + uint pos = (log->count + log->pos - i) % log->count; + if (log->lines[pos] == NULL) break; + + uint colour; + switch (log->type[pos]) { + case AILog::LOG_SQ_INFO: colour = TC_BLACK; break; + case AILog::LOG_SQ_ERROR: colour = TC_RED; break; + case AILog::LOG_INFO: colour = TC_BLACK; break; + case AILog::LOG_WARNING: colour = TC_YELLOW; break; + case AILog::LOG_ERROR: colour = TC_RED; break; + default: colour = TC_BLACK; break; + } + + DoDrawStringTruncated(log->lines[pos], 7, this->widget[AID_WIDGET_LOG_PANEL].top + y, colour, this->widget[AID_WIDGET_LOG_PANEL].right - this->widget[AID_WIDGET_LOG_PANEL].left - 14); + y += 12; + } + } + + virtual void OnClick(Point pt, int widget) + { + /* Check which button is clicked */ + if (IsInsideMM(widget, AID_WIDGET_COMPANY_BUTTON_START, AID_WIDGET_COMPANY_BUTTON_END + 1)) { + /* Is it no on disable? */ + if (!this->IsWidgetDisabled(widget)) { + this->RaiseWidget(ai_debug_company + AID_WIDGET_COMPANY_BUTTON_START); + ai_debug_company = (CompanyID)(widget - AID_WIDGET_COMPANY_BUTTON_START); + this->LowerWidget(ai_debug_company + AID_WIDGET_COMPANY_BUTTON_START); + this->SetDirty(); + } + } + if (widget == AID_WIDGET_RELOAD_TOGGLE && !this->IsWidgetDisabled(widget)) { + /* First kill the company of the AI, then start a new one. This should start the current AI again */ + DoCommandP(0, 2, ai_debug_company, CMD_COMPANY_CTRL); + DoCommandP(0, 1, 0, CMD_COMPANY_CTRL); + } + } + + virtual void OnTimeout() + { + this->RaiseWidget(AID_WIDGET_RELOAD_TOGGLE); + this->SetDirty(); + } + + virtual void OnInvalidateData(int data = 0) + { + if (data == -1 || ai_debug_company == data) this->SetDirty(); + } + + virtual void OnResize(Point new_size, Point delta) + { + this->vscroll.cap += delta.y / (int)this->resize.step_height; + } +}; + +CompanyID AIDebugWindow::ai_debug_company = INVALID_COMPANY; + +static const Widget _ai_debug_widgets[] = { +{ WWT_CLOSEBOX, RESIZE_NONE, COLOUR_GREY, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, // AID_WIDGET_CLOSEBOX +{ WWT_CAPTION, RESIZE_RIGHT, COLOUR_GREY, 11, 298, 0, 13, STR_AI_DEBUG, STR_018C_WINDOW_TITLE_DRAG_THIS}, // AID_WIDGET_CAPTION +{ WWT_PANEL, RESIZE_RIGHT, COLOUR_GREY, 0, 298, 14, 40, 0x0, STR_NULL}, // AID_WIDGET_VIEW + +{ WWT_PANEL, RESIZE_RIGHT, COLOUR_GREY, 0, 149, 41, 60, 0x0, STR_AI_DEBUG_NAME_TIP}, // AID_WIDGET_NAME_TEXT +{ WWT_PUSHTXTBTN, RESIZE_LR, COLOUR_GREY, 150, 298, 41, 60, STR_AI_DEBUG_RELOAD, STR_AI_DEBUG_RELOAD_TIP}, // AID_WIDGET_RELOAD_TOGGLE +{ WWT_PANEL, RESIZE_RB, COLOUR_GREY, 0, 286, 61, 240, 0x0, STR_NULL}, // AID_WIDGET_LOG_PANEL +{ WWT_SCROLLBAR, RESIZE_LRB, COLOUR_GREY, 287, 298, 61, 228, STR_NULL, STR_0190_SCROLL_BAR_SCROLLS_LIST}, // AID_WIDGET_SCROLLBAR +/* As this is WIP, leave the next few so we can work a bit with the GUI */ +{ WWT_EMPTY, RESIZE_NONE, COLOUR_GREY, 0, 298, 101, 120, 0x0, STR_NULL}, // AID_WIDGET_UNUSED_1 +{ WWT_EMPTY, RESIZE_NONE, COLOUR_GREY, 0, 298, 121, 140, 0x0, STR_NULL}, // AID_WIDGET_UNUSED_2 +{ WWT_EMPTY, RESIZE_NONE, COLOUR_GREY, 0, 298, 141, 160, 0x0, STR_NULL}, // AID_WIDGET_UNUSED_3 +{ WWT_EMPTY, RESIZE_NONE, COLOUR_GREY, 0, 298, 161, 180, 0x0, STR_NULL}, // AID_WIDGET_UNUSED_4 +{ WWT_EMPTY, RESIZE_NONE, COLOUR_GREY, 0, 298, 181, 200, 0x0, STR_NULL}, // AID_WIDGET_UNUSED_5 +{ WWT_EMPTY, RESIZE_NONE, COLOUR_GREY, 0, 298, 201, 220, 0x0, STR_NULL}, // AID_WIDGET_UNUSED_6 +{ WWT_EMPTY, RESIZE_NONE, COLOUR_GREY, 0, 298, 221, 240, 0x0, STR_NULL}, // AID_WIDGET_UNUSED_7 + +{ WWT_PANEL, RESIZE_NONE, COLOUR_GREY, 2, 38, 14, 26, 0x0, STR_704F_CLICK_HERE_TO_TOGGLE_COMPANY}, // AID_WIDGET_COMPANY_BUTTON_START +{ WWT_PANEL, RESIZE_NONE, COLOUR_GREY, 39, 75, 14, 26, 0x0, STR_704F_CLICK_HERE_TO_TOGGLE_COMPANY}, +{ WWT_PANEL, RESIZE_NONE, COLOUR_GREY, 76, 112, 14, 26, 0x0, STR_704F_CLICK_HERE_TO_TOGGLE_COMPANY}, +{ WWT_PANEL, RESIZE_NONE, COLOUR_GREY, 113, 149, 14, 26, 0x0, STR_704F_CLICK_HERE_TO_TOGGLE_COMPANY}, +{ WWT_PANEL, RESIZE_NONE, COLOUR_GREY, 150, 186, 14, 26, 0x0, STR_704F_CLICK_HERE_TO_TOGGLE_COMPANY}, +{ WWT_PANEL, RESIZE_NONE, COLOUR_GREY, 187, 223, 14, 26, 0x0, STR_704F_CLICK_HERE_TO_TOGGLE_COMPANY}, +{ WWT_PANEL, RESIZE_NONE, COLOUR_GREY, 224, 260, 14, 26, 0x0, STR_704F_CLICK_HERE_TO_TOGGLE_COMPANY}, +{ WWT_PANEL, RESIZE_NONE, COLOUR_GREY, 261, 297, 14, 26, 0x0, STR_704F_CLICK_HERE_TO_TOGGLE_COMPANY}, +{ WWT_PANEL, RESIZE_NONE, COLOUR_GREY, 2, 38, 27, 39, 0x0, STR_704F_CLICK_HERE_TO_TOGGLE_COMPANY}, +{ WWT_PANEL, RESIZE_NONE, COLOUR_GREY, 39, 75, 27, 39, 0x0, STR_704F_CLICK_HERE_TO_TOGGLE_COMPANY}, +{ WWT_PANEL, RESIZE_NONE, COLOUR_GREY, 76, 112, 27, 39, 0x0, STR_704F_CLICK_HERE_TO_TOGGLE_COMPANY}, +{ WWT_PANEL, RESIZE_NONE, COLOUR_GREY, 113, 149, 27, 39, 0x0, STR_704F_CLICK_HERE_TO_TOGGLE_COMPANY}, +{ WWT_PANEL, RESIZE_NONE, COLOUR_GREY, 150, 186, 27, 39, 0x0, STR_704F_CLICK_HERE_TO_TOGGLE_COMPANY}, +{ WWT_PANEL, RESIZE_NONE, COLOUR_GREY, 187, 223, 27, 39, 0x0, STR_704F_CLICK_HERE_TO_TOGGLE_COMPANY}, +{ WWT_PANEL, RESIZE_NONE, COLOUR_GREY, 224, 260, 27, 39, 0x0, STR_704F_CLICK_HERE_TO_TOGGLE_COMPANY}, // AID_WIDGET_COMPANY_BUTTON_END +{ WWT_RESIZEBOX, RESIZE_LRTB, COLOUR_GREY, 287, 298, 229, 240, STR_NULL, STR_RESIZE_BUTTON}, +{ WIDGETS_END}, +}; + +static const WindowDesc _ai_debug_desc = { + WDP_AUTO, WDP_AUTO, 299, 241, 299, 241, + WC_AI_DEBUG, WC_NONE, + WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_RESIZABLE, + _ai_debug_widgets +}; + +void ShowAIDebugWindow() +{ + if (!_networking || _network_server) { + AllocateWindowDescFront<AIDebugWindow>(&_ai_debug_desc, 0); + } else { + ShowErrorMessage(INVALID_STRING_ID, STR_AI_DEBUG_SERVER_ONLY, 0, 0); + } +} diff --git a/src/ai/ai_gui.hpp b/src/ai/ai_gui.hpp new file mode 100644 index 000000000..c5c7ad2ee --- /dev/null +++ b/src/ai/ai_gui.hpp @@ -0,0 +1,10 @@ +/* $Id$ */ + +/** @file ai_gui.hpp Window for configuring the AIs */ + +#ifndef AI_GUI_HPP +#define AI_GUI_HPP + +void ShowAIDebugWindow(); + +#endif /* AI_GUI_HPP */ diff --git a/src/ai/ai_info.cpp b/src/ai/ai_info.cpp new file mode 100644 index 000000000..0eead0302 --- /dev/null +++ b/src/ai/ai_info.cpp @@ -0,0 +1,319 @@ +/* $Id$ */ + +/** @file ai_info.cpp Implementation of AIFileInfo */ + +#include "../stdafx.h" +#include "../core/alloc_func.hpp" + +#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 "ai_info.hpp" +#include "ai_scanner.hpp" +#include "api/ai_controller.hpp" +#include "../settings_type.h" +#include "../openttd.h" + +AIFileInfo::~AIFileInfo() +{ + this->engine->ReleaseObject(this->SQ_instance); + free((void *)this->author); + free((void *)this->name); + free((void *)this->description); + free((void *)this->date); + free((void *)this->instance_name); + free(this->script_name); + free(this->dir_name); + free(this->SQ_instance); +} + +const char *AIFileInfo::GetAuthor() +{ + if (this->author == NULL) this->author = this->engine->CallStringMethodStrdup(*this->SQ_instance, "GetAuthor"); + return this->author; +} + +const char *AIFileInfo::GetName() +{ + if (this->name == NULL) this->name = this->engine->CallStringMethodStrdup(*this->SQ_instance, "GetName"); + return this->name; +} + +const char *AIFileInfo::GetDescription() +{ + if (this->description == NULL) this->description = this->engine->CallStringMethodStrdup(*this->SQ_instance, "GetDescription"); + return this->description; +} + +int AIFileInfo::GetVersion() +{ + return this->engine->CallIntegerMethod(*this->SQ_instance, "GetVersion"); +} + +void AIFileInfo::GetSettings() +{ + this->engine->CallMethod(*this->SQ_instance, "GetSettings", NULL, -1); +} + +const char *AIFileInfo::GetDate() +{ + if (this->date == NULL) this->date = this->engine->CallStringMethodStrdup(*this->SQ_instance, "GetDate"); + return this->date; +} + +const char *AIFileInfo::GetInstanceName() +{ + if (this->instance_name == NULL) this->instance_name = this->engine->CallStringMethodStrdup(*this->SQ_instance, "CreateInstance"); + return this->instance_name; +} + +bool AIFileInfo::AllowStartup() +{ + return true; +} + +const char *AIFileInfo::GetDirName() +{ + return this->dir_name; +} + +const char *AIFileInfo::GetScriptName() +{ + return this->script_name; +} + +void AIFileInfo::CheckMethods(SQInteger *res, const char *name) +{ + if (!this->engine->MethodExists(*this->SQ_instance, name)) { + char error[1024]; + snprintf(error, sizeof(error), "your AIFileInfo doesn't have the method '%s'", name); + this->engine->ThrowError(error); + *res = SQ_ERROR; + } +} + +/* static */ SQInteger AIFileInfo::Constructor(HSQUIRRELVM vm, AIFileInfo *info) +{ + SQInteger res = 0; + + /* Set some basic info from the parent */ + info->SQ_instance = MallocT<SQObject>(1); + Squirrel::GetInstance(vm, info->SQ_instance, 2); + /* Make sure the instance stays alive over time */ + sq_addref(vm, info->SQ_instance); + info->base = ((AIScanner *)Squirrel::GetGlobalPointer(vm)); + info->engine = info->base->GetEngine(); + + /* Check if all needed fields are there */ + info->CheckMethods(&res, "GetAuthor"); + info->CheckMethods(&res, "GetName"); + info->CheckMethods(&res, "GetDescription"); + info->CheckMethods(&res, "GetVersion"); + info->CheckMethods(&res, "GetDate"); + info->CheckMethods(&res, "CreateInstance"); + + /* Abort if one method was missing */ + if (res != 0) return res; + + info->script_name = strdup(info->base->GetCurrentScript()); + info->dir_name = strdup(info->base->GetCurrentDirName()); + + return 0; +} + +/* static */ SQInteger AIInfo::Constructor(HSQUIRRELVM vm) +{ + /* Get the AIInfo */ + SQUserPointer instance; + sq_getinstanceup(vm, 2, &instance, 0); + AIInfo *info = (AIInfo *)instance; + + SQInteger res = AIFileInfo::Constructor(vm, info); + if (res != 0) return res; + + AIConfigItem config; + config.name = strdup("start_date"); + config.description = strdup("The amount of months after the start of the last AI, this AI will start (give or take)."); + config.min_value = 0; + config.max_value = 120; + config.easy_value = 48; + config.medium_value = 24; + config.hard_value = 12; + config.custom_value = 24; + config.flags = AICONFIG_NONE; + info->config_list.push_back(config); + + /* Check if we have settings */ + if (info->engine->MethodExists(*info->SQ_instance, "GetSettings")) { + info->GetSettings(); + } + + /* Remove the link to the real instance, else it might get deleted by RegisterAI() */ + sq_setinstanceup(vm, 2, NULL); + /* Register the AI to the base system */ + info->base->RegisterAI(info); + return 0; +} + +/* static */ SQInteger AIInfo::DummyConstructor(HSQUIRRELVM vm) +{ + /* Get the AIInfo */ + SQUserPointer instance; + sq_getinstanceup(vm, 2, &instance, 0); + AIInfo *info = (AIInfo *)instance; + + SQInteger res = AIFileInfo::Constructor(vm, info); + if (res != 0) return res; + + /* Remove the link to the real instance, else it might get deleted by RegisterAI() */ + sq_setinstanceup(vm, 2, NULL); + /* Register the AI to the base system */ + info->base->SetDummyAI(info); + return 0; +} + +AIInfo::~AIInfo() +{ + /* Free all allocated strings */ + for (AIConfigItemList::iterator it = this->config_list.begin(); it != this->config_list.end(); it++) { + free((char *)(*it).name); + } + this->config_list.clear(); +} + +SQInteger AIInfo::AddSetting(HSQUIRRELVM vm) +{ + AIConfigItem config; + memset(&config, 0, sizeof(config)); + int items = 0; + + /* Read the table, and find all properties we care about */ + sq_pushnull(vm); + while (SQ_SUCCEEDED(sq_next(vm, -2))) { + const SQChar *sqkey; + sq_getstring(vm, -2, &sqkey); + const char *key = FS2OTTD(sqkey); + + if (strcmp(key, "name") == 0) { + const SQChar *sqvalue; + sq_getstring(vm, -1, &sqvalue); + config.name = strdup(FS2OTTD(sqvalue)); + char *s; + /* Don't allow '=' and ',' in configure setting names, as we need those + * 2 chars to nicely store the settings as a string. */ + while ((s = (char *)strchr(config.name, '=')) != NULL) *s = '_'; + while ((s = (char *)strchr(config.name, ',')) != NULL) *s = '_'; + items |= 0x001; + } else if (strcmp(key, "description") == 0) { + const SQChar *sqdescription; + sq_getstring(vm, -1, &sqdescription); + config.description = strdup(FS2OTTD(sqdescription)); + items |= 0x002; + } else if (strcmp(key, "min_value") == 0) { + SQInteger res; + sq_getinteger(vm, -1, &res); + config.min_value = res; + items |= 0x004; + } else if (strcmp(key, "max_value") == 0) { + SQInteger res; + sq_getinteger(vm, -1, &res); + config.max_value = res; + items |= 0x008; + } else if (strcmp(key, "easy_value") == 0) { + SQInteger res; + sq_getinteger(vm, -1, &res); + config.easy_value = res; + items |= 0x010; + } else if (strcmp(key, "medium_value") == 0) { + SQInteger res; + sq_getinteger(vm, -1, &res); + config.medium_value = res; + items |= 0x020; + } else if (strcmp(key, "hard_value") == 0) { + SQInteger res; + sq_getinteger(vm, -1, &res); + config.hard_value = res; + items |= 0x040; + } else if (strcmp(key, "custom_value") == 0) { + SQInteger res; + sq_getinteger(vm, -1, &res); + config.custom_value = res; + items |= 0x080; + } else if (strcmp(key, "flags") == 0) { + SQInteger res; + sq_getinteger(vm, -1, &res); + config.flags = (AIConfigFlags)res; + items |= 0x100; + } else { + char error[1024]; + snprintf(error, sizeof(error), "unknown setting property '%s'", key); + this->engine->ThrowError(error); + return SQ_ERROR; + } + + sq_pop(vm, 2); + } + sq_pop(vm, 1); + + /* Make sure all properties are defined */ + if (items != 0x1FF) { + char error[1024]; + snprintf(error, sizeof(error), "please define all properties of a setting"); + this->engine->ThrowError(error); + return SQ_ERROR; + } + + this->config_list.push_back(config); + return 0; +} + +const AIConfigItemList *AIInfo::GetConfigList() +{ + return &this->config_list; +} + +int AIInfo::GetSettingDefaultValue(const char *name) +{ + for (AIConfigItemList::iterator it = this->config_list.begin(); it != this->config_list.end(); it++) { + if (strcmp((*it).name, name) != 0) continue; + /* The default value depends on the difficulty level */ + switch ((_game_mode == GM_MENU) ? _settings_newgame.difficulty.diff_level : _settings_game.difficulty.diff_level) { + case 0: return (*it).easy_value; + case 1: return (*it).medium_value; + case 2: return (*it).hard_value; + case 3: return (*it).custom_value; + default: NOT_REACHED(); + } + } + + /* There is no such setting */ + return -1; +} + +/* static */ SQInteger AILibrary::Constructor(HSQUIRRELVM vm) +{ + /* Create a new AIFileInfo */ + AILibrary *library = new AILibrary(); + + SQInteger res = AIFileInfo::Constructor(vm, library); + if (res != 0) return res; + + /* Register the Library to the base system */ + library->base->RegisterLibrary(library); + + return 0; +} + +/* static */ SQInteger AILibrary::Import(HSQUIRRELVM vm) +{ + SQConvert::SQAutoFreePointers ptr; + const char *library = GetParam(SQConvert::ForceType<const char *>(), vm, 2, &ptr); + const char *class_name = GetParam(SQConvert::ForceType<const char *>(), vm, 3, &ptr); + int version = GetParam(SQConvert::ForceType<int>(), vm, 4, &ptr); + + if (!AI::ImportLibrary(library, class_name, version, vm)) return -1; + return 1; +} diff --git a/src/ai/ai_info.hpp b/src/ai/ai_info.hpp new file mode 100644 index 000000000..d50b89584 --- /dev/null +++ b/src/ai/ai_info.hpp @@ -0,0 +1,153 @@ +/* $Id$ */ + +/** @file ai_info.hpp AIInfo keeps track of all information of an AI, like Author, Description, ... */ + +#ifndef AI_INFO +#define AI_INFO + +#include <list> +#include "api/ai_object.hpp" + +enum AIConfigFlags { + AICONFIG_NONE = 0x0, + AICONFIG_RANDOM = 0x1, //!< When randomizing the AI, pick any value between min_value and max_value when on custom difficulty setting. + AICONFIG_BOOLEAN = 0x2, //!< This value is a boolean (either 0 (false) or 1 (true) ). +}; + +struct AIConfigItem { + const char *name; //!< The name of the configuration setting. + const char *description; //!< The description of the configuration setting. + int min_value; //!< The minimal value this configuration setting can have. + int max_value; //!< The maximal value this configuration setting can have. + int custom_value; //!< The default value on custom difficulty setting. + int easy_value; //!< The default value on easy difficulty setting. + int medium_value; //!< The default value on medium difficulty setting. + int hard_value; //!< The default value on hard difficulty setting. + AIConfigFlags flags; //!< Flags for the configuration setting. +}; + +typedef std::list<AIConfigItem> AIConfigItemList; + +class AIFileInfo : public AIObject { +public: + friend class AIInfo; + friend class AILibrary; + + AIFileInfo() : author(NULL), name(NULL), description(NULL), date(NULL), instance_name(NULL) {}; + ~AIFileInfo(); + + /** + * Get the Author of the AI. + */ + const char *GetAuthor(); + + /** + * Get the Name of the AI. + */ + const char *GetName(); + + /** + * Get the description of the AI. + */ + const char *GetDescription(); + + /** + * Get the version of the AI. + */ + int GetVersion(); + + /** + * Get the settings of the AI. + */ + void GetSettings(); + + /** + * Get the date of the AI. + */ + const char *GetDate(); + + /** + * Get the name of the instance of the AI to create. + */ + const char *GetInstanceName(); + + /** + * Check if we can start this AI. + */ + bool AllowStartup(); + + /** + * Get the name of the dir this AI is in. + */ + const char *GetDirName(); + + /** + * Get the complete script name of this AI. + */ + const char *GetScriptName(); + + /** + * Check if a given method exists. + */ + void CheckMethods(SQInteger *res, const char *name); + + /** + * Process the creation of a FileInfo object. + */ + static SQInteger Constructor(HSQUIRRELVM vm, AIFileInfo *info); + +private: + class Squirrel *engine; + HSQOBJECT *SQ_instance; + char *script_name; + char *dir_name; + class AIScanner *base; + const char *author; + const char *name; + const char *description; + const char *date; + const char *instance_name; +}; + +class AIInfo : public AIFileInfo { +public: + static const char *GetClassName() { return "AIInfo"; } + + ~AIInfo(); + + /** + * Create an AI, using this AIInfo as start-template. + */ + static SQInteger Constructor(HSQUIRRELVM vm); + static SQInteger DummyConstructor(HSQUIRRELVM vm); + + /** + * Get the config list for this AI. + */ + const AIConfigItemList *GetConfigList(); + + /** + * Set a setting. + */ + SQInteger AddSetting(HSQUIRRELVM vm); + + /** + * Get the default value for a setting. + */ + int GetSettingDefaultValue(const char *name); + +private: + AIConfigItemList config_list; +}; + +class AILibrary : public AIFileInfo { +public: + /** + * Create an AI, using this AIInfo as start-template. + */ + static SQInteger Constructor(HSQUIRRELVM vm); + + static SQInteger Import(HSQUIRRELVM vm); +}; + +#endif /* AI_INFO */ diff --git a/src/ai/ai_info_dummy.cpp b/src/ai/ai_info_dummy.cpp new file mode 100644 index 000000000..a060a0a65 --- /dev/null +++ b/src/ai/ai_info_dummy.cpp @@ -0,0 +1,66 @@ +/* $Id$ */ + +#include <squirrel.h> +#include "../stdafx.h" + +/* The reason this exists in C++, is that a user can trash his ai/ dir, + * leaving no AIs available. The complexity to solve this is insane, and + * therefor the alternative is used, and make sure there is always an AI + * available, no matter what the situation is. By defining it in C++, there + * is simply now way a user can delete it, and therefor safe to use. It has + * to be noted that this AI is complete invisible for the user, and impossible + * to select manual. It is a fail-over in case no AIs are available. + */ + +const SQChar dummy_script_info[] = _SC(" \n\ +class DummyAI extends AIInfo { \n\ + function GetAuthor() { return \"OpenTTD NoAI Developers Team\"; } \n\ + function GetName() { return \"DummyAI\"; } \n\ + function GetDescription() { return \"A Dummy AI that is loaded when your ai/ dir is empty\"; }\n\ + function GetVersion() { return 1; } \n\ + function GetDate() { return \"2008-07-26\"; } \n\ + function CreateInstance() { return \"DummyAI\"; } \n\ +} \n\ + \n\ +RegisterDummyAI(DummyAI()); \n\ +"); + +const SQChar dummy_script[] = _SC(" \n\ +class DummyAI extends AIController { \n\ + function Start() { \n\ + AILog.Error(\"No suitable AI found to load.\"); \n\ + AILog.Error(\"This AI is a dummy AI and won't do anything.\"); \n\ + AILog.Error(\"Please add one or several AIs in your ai/ directory.\"); \n\ + } \n\ +} \n\ +"); + +void AI_CreateAIInfoDummy(HSQUIRRELVM vm) +{ + sq_pushroottable(vm); + + /* Load and run the script */ + if (SQ_SUCCEEDED(sq_compilebuffer(vm, dummy_script_info, scstrlen(dummy_script_info), _SC("dummy"), SQTrue))) { + sq_push(vm, -2); + if (SQ_SUCCEEDED(sq_call(vm, 1, SQFalse, SQTrue))) { + sq_pop(vm, 1); + return; + } + } + NOT_REACHED(); +} + +void AI_CreateAIDummy(HSQUIRRELVM vm) +{ + sq_pushroottable(vm); + + /* Load and run the script */ + if (SQ_SUCCEEDED(sq_compilebuffer(vm, dummy_script, scstrlen(dummy_script), _SC("dummy"), SQTrue))) { + sq_push(vm, -2); + if (SQ_SUCCEEDED(sq_call(vm, 1, SQFalse, SQTrue))) { + sq_pop(vm, 1); + return; + } + } + NOT_REACHED(); +} 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; +} diff --git a/src/ai/ai_instance.hpp b/src/ai/ai_instance.hpp new file mode 100644 index 000000000..d953c3915 --- /dev/null +++ b/src/ai/ai_instance.hpp @@ -0,0 +1,140 @@ +/* $Id$ */ + +/** @file ai_instance.hpp The AIInstance tracks an AI. */ + +#ifndef AI_INSTANCE_HPP +#define AI_INSTANCE_HPP + +/** + * The callback function when an AI suspends. + */ +typedef void (AISuspendCallbackProc)(class AIInstance *instance); + +/** + * A throw-class that is given when the VM wants to suspend. + */ +class AI_VMSuspend { +public: + AI_VMSuspend(int time, AISuspendCallbackProc *callback) : + time(time), + callback(callback) + {} + + int GetSuspendTime() { return time; } + AISuspendCallbackProc *GetSuspendCallback() { return callback; } + +private: + int time; + AISuspendCallbackProc *callback; +}; + +class AIInstance { +public: + AIInstance(class AIInfo *info); + ~AIInstance(); + + /** + * 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(); + + /** + * Get the storage of this AI. + */ + static class AIStorage *GetStorage(); + + /** + * 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 AIController *GetController() { return controller; } + + /** + * Call the AI 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 call the AI Load function if it + * exists. + * @return True if the loading was successfull. + */ + bool Load(); + + /** + * Load and discard data from a savegame. + */ + static void LoadEmpty(); + +private: + static class AIInstance *current_instance; //!< Static current AIInstance, so we can register AIs. + + class AIController *controller; + class AIStorage *storage; + class Squirrel *engine; + SQObject *instance; + + bool is_started; + bool is_dead; + int suspend; + AISuspendCallbackProc *callback; + + /** + * Register all API functions to the VM. + */ + void RegisterAPI(); + + /** + * Tell the AI it died. + */ + void Died(); + + /** + * Save one object (int / string / arrray / table) to the savegame. + * @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 successfull. + */ + static bool SaveObject(HSQUIRRELVM vm, SQInteger index, int max_depth, bool test); + + /** + * Load all objects from a savegame. + * @return True if the loading was successfull. + */ + static bool LoadObjects(HSQUIRRELVM vm); +}; + +#endif /* AI_INSTANCE_HPP */ diff --git a/src/ai/ai_scanner.cpp b/src/ai/ai_scanner.cpp new file mode 100644 index 000000000..68d53660d --- /dev/null +++ b/src/ai/ai_scanner.cpp @@ -0,0 +1,395 @@ +/* $Id$ */ + +/** @file ai_scanner.cpp allows scanning AI scripts */ + +#include "../stdafx.h" +#include "../debug.h" +#include "../openttd.h" +#include "../string_func.h" +#include "../fileio_func.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 "../script/squirrel.hpp" +#include "../script/squirrel_helper.hpp" +#include "../script/squirrel_class.hpp" +#include "ai.hpp" +#include "ai_info.hpp" +#include "ai_scanner.hpp" +#include "api/ai_controller.hpp" + +void AIScanner::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 (S_ISDIR(sb.st_mode)) { + /* 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 (S_ISREG(sb.st_mode)) { + if (library_dir) continue; + + 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 */ + if (FioTarFirstDir(tarname) == NULL) continue; + ttd_strlcpy(script_name, "%aitar%", sizeof(tarname)); + ttd_strlcat(script_name, FioTarFirstDir(tarname), sizeof(script_name)); + FioTarAddLink(script_name, FioTarFirstDir(tarname)); + + /* 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, "[script] 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]; + char d_name_2[MAX_PATH]; + /* In case the directory has a dot in it, ignore it, as that is the + * indicator for multiple versions of the same library */ + ttd_strlcpy(d_name_2, d_name, sizeof(d_name_2)); + char *e = strrchr(d_name_2, '.'); + if (e != NULL) *e = '\0'; + + 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_2, 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, "[script] 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 AIScanner::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 AIScanner::RescanAIDir() +{ + extern void ScanForTarFiles(); + ScanForTarFiles(); + this->ScanAIDir(); +} + +AIScanner::AIScanner() +{ + this->engine = new Squirrel(); + + /* Create the AIInfo class, and add the RegisterAI function */ + DefSQClass <AIInfo> SQAIInfo("AIInfo"); + SQAIInfo.PreRegister(engine); + SQAIInfo.AddConstructor<void (AIInfo::*)(), 1>(engine, "x"); + SQAIInfo.DefSQAdvancedMethod(this->engine, &AIInfo::AddSetting, "AddSetting"); + SQAIInfo.PostRegister(engine); + 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(); + + /* Create the dummy AI */ + this->engine->AddMethod("RegisterDummyAI", &AIInfo::DummyConstructor, 2, "tx"); + this->current_script = (char *)"%_dummy"; + this->current_dir = (char *)"%_dummy"; + extern void AI_CreateAIInfoDummy(HSQUIRRELVM vm); + AI_CreateAIInfoDummy(this->engine->GetVM()); +} + +AIScanner::~AIScanner() +{ + AIInfoList::iterator it = this->info_list.begin(); + for (; it != this->info_list.end(); it++) { + AIInfo *i = (*it).second; + delete i; + } + + delete this->engine; +} + +bool AIScanner::ImportLibrary(const char *library, const char *class_name, int version, HSQUIRRELVM vm, AIController *controller) +{ + /* Internally we store libraries as 'library.version' */ + char library_name[1024]; + snprintf(library_name, sizeof(library_name), "%s.%d", library, version); + + /* Check if the library + version exists */ + AILibraryList::iterator iter = this->library_list.find(library_name); + if (iter == this->library_list.end()) { + char error[1024]; + + /* Now see if the version doesn't exist, or the library */ + iter = this->library_list.find(library); + if (iter == this->library_list.end()) { + snprintf(error, sizeof(error), "couldn't find library '%s'", library); + } else { + snprintf(error, sizeof(error), "this AI is expecting library '%s' to be version %d, but the latest available is version %d", library, version, (*iter).second->GetVersion()); + } + sq_throwerror(vm, OTTD2FS(error)); + return false; + } + + /* Get the current table/class we belong to */ + HSQOBJECT parent; + sq_getstackobj(vm, 1, &parent); + + char fake_class[1024]; + int next_number; + /* Check if we already have this library loaded.. if so, fill fake_class + * with the class-name it is nested in */ + if (!controller->LoadedLibrary(library_name, &next_number, &fake_class[0], sizeof(fake_class))) { + /* Create a new fake internal name */ + snprintf(fake_class, sizeof(fake_class), "_internalNA%d", next_number); + + /* 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' version %d", library, version); + sq_throwerror(vm, OTTD2FS(error)); + return false; + } + /* Create the fake class */ + sq_newslot(vm, -3, SQFalse); + sq_pop(vm, 1); + + controller->AddLoadedLibrary(library_name, fake_class); + } + + /* 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' version %d", (*iter).second->GetInstanceName(), library, version); + 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 AIScanner::RegisterLibrary(AILibrary *library) +{ + const char *ai_name_without_version = library->GetDirName(); + char ai_name[1024]; + snprintf(ai_name, sizeof(ai_name), "%s.%d", library->GetDirName(), library->GetVersion()); + + /* 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[strdup(ai_name)] = library; + /* Insert the global name too, so we if the library is known at all */ + if (this->library_list.find(ai_name_without_version) == this->library_list.end()) { + this->library_list[strdup(ai_name_without_version)] = library; + } else if (this->library_list[ai_name_without_version]->GetVersion() < library->GetVersion()) { + this->library_list[ai_name_without_version] = library; + } +} + +void AIScanner::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[strdup(ai_name)] = info; +} + +void AIScanner::UnregisterAI(AIInfo *info) +{ + this->info_list.erase(info->GetDirName()); +} + +AIInfo *AIScanner::SelectRandomAI() +{ + if (this->info_list.size() == 0) { + DEBUG(ai, 0, "No suitable AI found, loading 'dummy' AI."); + return this->info_dummy; + } + + /* Find a random AI */ + uint pos; + if (_networking) pos = InteractiveRandomRange((uint16)this->info_list.size()); + else pos = RandomRange((uint16)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) { + DEBUG(ai, 0, "No suitable AI found, loading 'dummy' AI."); + return this->info_dummy; + } + + i = (*it).second; + } while (!i->AllowStartup()); + } + return i; +} + +AIInfo *AIScanner::FindAI(const char *name) +{ + if (this->info_list.size() == 0) return NULL; + if (name == NULL) 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 *AIScanner::GetAIConsoleList(char *p, const char *last) +{ + p += seprintf(p, last, "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 += seprintf(p, last, "%10s: %s\n", (*it).first, i->GetDescription()); + } + p += seprintf(p, last, "\n"); + + return p; +} diff --git a/src/ai/ai_scanner.hpp b/src/ai/ai_scanner.hpp new file mode 100644 index 000000000..2801f556a --- /dev/null +++ b/src/ai/ai_scanner.hpp @@ -0,0 +1,102 @@ +/* $Id$ */ + +/** @file ai_scanner.hpp declarations of the class for AI scanner */ + +#ifndef AI_SCANNER_HPP +#define AI_SCANNER_HPP + +#include <map> + +class AIScanner { +public: + AIScanner(); + ~AIScanner(); + + /** + * Import a library inside the Squirrel VM. + */ + bool ImportLibrary(const char *library, const char *class_name, int version, HSQUIRRELVM vm, class AIController *controller); + + /** + * Register a library to be put in the available list. + */ + void RegisterLibrary(class AILibrary *library); + + /** + * Register an AI to be put in the available list. + */ + void RegisterAI(class AIInfo *info); + + void SetDummyAI(class AIInfo *info) { this->info_dummy = info; } + + /** + * Remove an AI from the available list. + */ + void UnregisterAI(class AIInfo *info); + + /** + * Select a Random AI. + */ + class AIInfo *SelectRandomAI(); + + /** + * Find an AI by name. + */ + class AIInfo *FindAI(const char *name); + + /** + * Get the list of available AIs for the console. + */ + char *GetAIConsoleList(char *p, const char *last); + + /** + * Get the list of all registered AIs. + */ + const AIInfoList *GetAIInfoList() { return &this->info_list; } + + /** + * Get the engine of the main squirrel handler (it indexes all avialable squirrels). + */ + class Squirrel *GetEngine() { return this->engine; } + + /** + * Get the current script the ScanDir is looking at. + */ + const char *GetCurrentScript() { return this->current_script; } + + /** + * Get the directory of the current script the ScanDir is looking at. + */ + const char *GetCurrentDirName() { return this->current_dir; } + + /** + * Rescan the AI dir for scripts. + */ + void RescanAIDir(); + +private: + typedef std::map<const char *, class AILibrary *, ltstr> AILibraryList; + + /** + * Scan the AI dir for scripts. + */ + void ScanAIDir(); + + /** + * Scan a dir for AIs. + * For non-library-scan, if an AI is found, AIInfo is created, and the AI + * is registered to the central system. + * For library-scan, if a library is found, AILibrary is created, and the + * library is registered to the central system. + */ + void ScanDir(const char *dirname, bool library_dir, char *library_depth = NULL); + + AIInfoList info_list; + AIInfo *info_dummy; + AILibraryList library_list; + class Squirrel *engine; + char *current_script; + char *current_dir; +}; + +#endif /* AI_SCANNER_HPP */ diff --git a/src/ai/ai_storage.hpp b/src/ai/ai_storage.hpp new file mode 100644 index 000000000..2e85046b4 --- /dev/null +++ b/src/ai/ai_storage.hpp @@ -0,0 +1,78 @@ +/* $Id$ */ + +/** @file ai_storage.hpp Defines AIStorage and includes all files required for it. */ + +#ifndef AI_STORAGE_HPP +#define AI_STORAGE_HPP + +#include "../command_func.h" +#include "../map_func.h" +#include "../network/network.h" +#include "../company_func.h" +#include "../signs_func.h" +#include "../tunnelbridge.h" +#include "../vehicle_func.h" +#include "../group.h" + +#include <vector> + +/** + * The callback function for Mode-classes. + */ +typedef bool (AIModeProc)(TileIndex tile, uint32 p1, uint32 p2, uint procc, CommandCost costs); + +/** + * The storage for each AI. It keeps track of important information. + */ +class AIStorage { +friend class AIObject; +private: + AIModeProc *mode; //!< The current build mode we are int. + class AIObject *mode_instance; //!< The instance belonging to the current build mode. + + uint delay; //!< The ticks of delay each DoCommand has. + bool allow_do_command; //!< Is the usage of DoCommands restricted? + + CommandCost costs; //!< The costs the AI is tracking. + Money last_cost; //!< The last cost of the command. + uint last_error; //!< The last error of the command. + bool last_command_res; //!< The last result of the command. + + VehicleID new_vehicle_id; //!< The ID of the new Vehicle. + SignID new_sign_id; //!< The ID of the new Sign. + TileIndex new_tunnel_endtile; //!< The TileIndex of the new Tunnel. + GroupID new_group_id; //!< The ID of the new Group. + + std::vector<int> callback_value; //!< The values which need to survive a callback. + + RoadType road_type; //!< The current roadtype we build. + RailType rail_type; //!< The current railtype we build. + + void *event_data; //!< Pointer to the event data storage. + void *log_data; //!< Pointer to the log data storage. + +public: + AIStorage() : + mode (NULL), + mode_instance (NULL), + delay (1), + allow_do_command (true), + /* costs (can't be set) */ + last_cost (0), + last_error (STR_NULL), + last_command_res (true), + new_vehicle_id (0), + new_sign_id (0), + new_tunnel_endtile(INVALID_TILE), + new_group_id (0), + /* calback_value (can't be set) */ + road_type (INVALID_ROADTYPE), + rail_type (INVALID_RAILTYPE), + event_data (NULL), + log_data (NULL) + { } + + ~AIStorage(); +}; + +#endif /* AI_STORAGE_HPP */ diff --git a/src/ai/api/Doxyfile b/src/ai/api/Doxyfile new file mode 100644 index 000000000..ff5d7a511 --- /dev/null +++ b/src/ai/api/Doxyfile @@ -0,0 +1,244 @@ +# Doxyfile 1.5.4 + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +DOXYFILE_ENCODING = UTF-8 +PROJECT_NAME = "OpenTTD NoAI API " +PROJECT_NUMBER = +OUTPUT_DIRECTORY = ../../../docs/aidocs/ +CREATE_SUBDIRS = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = "The $name class " \ + "The $name widget " \ + "The $name file " \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = ./ +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = YES +QT_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +DETAILS_AT_TOP = NO +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 2 +ALIASES = +OPTIMIZE_OUTPUT_FOR_C = YES +OPTIMIZE_OUTPUT_JAVA = NO +BUILTIN_STL_SUPPORT = NO +CPP_CLI_SUPPORT = NO +SIP_SUPPORT = NO +DISTRIBUTE_GROUP_DOC = NO +SUBGROUPING = YES +TYPEDEF_HIDES_STRUCT = NO +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = NO +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = YES +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = YES +EXTRACT_ANON_NSPACES = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = YES +INTERNAL_DOCS = YES +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +SHOW_INCLUDE_FILES = NO +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = NO +SORT_BY_SCOPE_NAME = NO +GENERATE_TODOLIST = NO +GENERATE_TESTLIST = NO +GENERATE_BUGLIST = NO +GENERATE_DEPRECATEDLIST= NO +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = NO +SHOW_DIRECTORIES = NO +FILE_VERSION_FILTER = +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = YES +WARN_FORMAT = "$file:$line: $text " +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = . +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = *.h \ + *.hpp +RECURSIVE = YES +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXCLUDE_SYMBOLS = GetClassName DECLARE_ENUM_AS_BIT_SET DECLARE_POSTFIX_INCREMENT +EXAMPLE_PATH = +EXAMPLE_PATTERNS = * +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO +REFERENCES_LINK_SOURCE = YES +USE_HTAGS = NO +VERBATIM_HEADERS = NO +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = NO +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_ALIGN_MEMBERS = YES +GENERATE_HTMLHELP = NO +HTML_DYNAMIC_SECTIONS = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +BINARY_TOC = NO +TOC_EXPAND = NO +DISABLE_INDEX = NO +ENUM_VALUES_PER_LINE = 1 +GENERATE_TREEVIEW = NO +TREEVIEW_WIDTH = 250 +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4wide +EXTRA_PACKAGES = +LATEX_HEADER = +PDF_HYPERLINKS = NO +USE_PDFLATEX = NO +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = NO +XML_OUTPUT = xml +XML_SCHEMA = +XML_DTD = +XML_PROGRAMLISTING = YES +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = YES +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = DOXYGEN_SKIP +EXPAND_AS_DEFINED = DEF_COMMAND +SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = openttd.tag +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +PERL_PATH = /usr/bin/perl +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = YES +MSCGEN_PATH = +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = NO +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = NO +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DOT_IMAGE_FORMAT = png +DOT_PATH = +DOTFILE_DIRS = +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 1000 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = NO +DOT_CLEANUP = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- +SEARCHENGINE = NO diff --git a/src/ai/api/ai_abstractlist.cpp b/src/ai/api/ai_abstractlist.cpp new file mode 100644 index 000000000..d447311ac --- /dev/null +++ b/src/ai/api/ai_abstractlist.cpp @@ -0,0 +1,818 @@ +/* $Id$ */ + +/** @file ai_abstractlist.cpp Implementation of AIAbstractList. */ + +#include <squirrel.h> +#include "ai_abstractlist.hpp" +#include "../../debug.h" +#include "../../core/alloc_func.hpp" + +/** + * Base class for any AIAbstractList sorter. + */ +class AIAbstractListSorter { +protected: + AIAbstractList *list; + +public: + /** + * Virtual dtor, needed to mute warnings. + */ + virtual ~AIAbstractListSorter() { } + + /** + * Get the first item of the sorter. + */ + virtual int32 Begin() = 0; + + /** + * Stop iterating a sorter. + */ + virtual void End() = 0; + + /** + * Get the next item of the sorter. + */ + virtual int32 Next() = 0; + + /** + * See if there is a next item of the sorter. + */ + virtual bool HasNext() = 0; + + /** + * Callback from the list if an item gets removed. + */ + virtual void Remove(int item) = 0; +}; + +/** + * Sort by value, ascending. + */ +class AIAbstractListSorterValueAscending : public AIAbstractListSorter { +private: + AIAbstractList::AIAbstractListBucket::iterator bucket_iter; + AIAbstractList::AIItemList *bucket_list; + AIAbstractList::AIItemList::iterator bucket_list_iter; + bool has_no_more_items; + int32 item_next; + +public: + AIAbstractListSorterValueAscending(AIAbstractList *list) + { + this->list = list; + this->End(); + } + + int32 Begin() + { + if (this->list->buckets.empty()) return 0; + this->has_no_more_items = false; + + this->bucket_iter = this->list->buckets.begin(); + this->bucket_list = &(*this->bucket_iter).second; + this->bucket_list_iter = this->bucket_list->begin(); + this->item_next = *this->bucket_list_iter; + + int32 item_current = this->item_next; + FindNext(); + return item_current; + } + + void End() + { + this->bucket_list = NULL; + this->has_no_more_items = true; + this->item_next = 0; + } + + void FindNext() + { + if (this->bucket_list == NULL) { + this->has_no_more_items = true; + return; + } + + this->bucket_list_iter++; + if (this->bucket_list_iter == this->bucket_list->end()) { + this->bucket_iter++; + if (this->bucket_iter == this->list->buckets.end()) { + this->bucket_list = NULL; + return; + } + this->bucket_list = &(*this->bucket_iter).second; + this->bucket_list_iter = this->bucket_list->begin(); + } + this->item_next = *this->bucket_list_iter; + } + + int32 Next() + { + if (!this->HasNext()) return 0; + + int32 item_current = this->item_next; + FindNext(); + return item_current; + } + + void Remove(int item) + { + if (!this->HasNext()) return; + + /* If we remove the 'next' item, skip to the next */ + if (item == this->item_next) { + FindNext(); + return; + } + } + + bool HasNext() + { + return !(this->list->buckets.empty() || this->has_no_more_items); + } +}; + +/** + * Sort by value, descending. + */ +class AIAbstractListSorterValueDescending : public AIAbstractListSorter { +private: + AIAbstractList::AIAbstractListBucket::iterator bucket_iter; + AIAbstractList::AIItemList *bucket_list; + AIAbstractList::AIItemList::iterator bucket_list_iter; + bool has_no_more_items; + int32 item_next; + +public: + AIAbstractListSorterValueDescending(AIAbstractList *list) + { + this->list = list; + this->End(); + } + + int32 Begin() + { + if (this->list->buckets.empty()) return 0; + this->has_no_more_items = false; + + /* Go to the end of the bucket-list */ + this->bucket_iter = this->list->buckets.begin(); + for (size_t i = this->list->buckets.size(); i > 1; i--) this->bucket_iter++; + this->bucket_list = &(*this->bucket_iter).second; + + /* Go to the end of the items in the bucket */ + this->bucket_list_iter = this->bucket_list->begin(); + for (size_t i = this->bucket_list->size(); i > 1; i--) this->bucket_list_iter++; + this->item_next = *this->bucket_list_iter; + + int32 item_current = this->item_next; + FindNext(); + return item_current; + } + + void End() { + this->bucket_list = NULL; + this->has_no_more_items = true; + this->item_next = 0; + } + + void FindNext() + { + if (this->bucket_list == NULL) { + this->has_no_more_items = true; + return; + } + + if (this->bucket_list_iter == this->bucket_list->begin()) { + if (this->bucket_iter == this->list->buckets.begin()) { + this->bucket_list = NULL; + return; + } + this->bucket_iter--; + this->bucket_list = &(*this->bucket_iter).second; + /* Go to the end of the items in the bucket */ + this->bucket_list_iter = this->bucket_list->begin(); + for (size_t i = this->bucket_list->size(); i > 1; i--) this->bucket_list_iter++; + } else { + this->bucket_list_iter--; + } + this->item_next = *this->bucket_list_iter; + } + + int32 Next() + { + if (!this->HasNext()) return 0; + + int32 item_current = this->item_next; + FindNext(); + return item_current; + } + + void Remove(int item) + { + if (!this->HasNext()) return; + + /* If we remove the 'next' item, skip to the next */ + if (item == this->item_next) { + FindNext(); + return; + } + } + + bool HasNext() + { + return !(this->list->buckets.empty() || this->has_no_more_items); + } +}; + +/** + * Sort by item, ascending. + */ +class AIAbstractListSorterItemAscending : public AIAbstractListSorter { +private: + AIAbstractList::AIAbstractListMap::iterator item_iter; + bool has_no_more_items; + int32 item_next; + +public: + AIAbstractListSorterItemAscending(AIAbstractList *list) + { + this->list = list; + this->End(); + } + + int32 Begin() + { + if (this->list->items.empty()) return 0; + this->has_no_more_items = false; + + this->item_iter = this->list->items.begin(); + this->item_next = (*this->item_iter).first; + + int32 item_current = this->item_next; + FindNext(); + return item_current; + } + + void End() + { + this->has_no_more_items = true; + } + + void FindNext() + { + if (this->item_iter == this->list->items.end()) { + this->has_no_more_items = true; + return; + } + this->item_iter++; + if (this->item_iter != this->list->items.end()) item_next = (*this->item_iter).first; + } + + int32 Next() + { + if (!this->HasNext()) return 0; + + int32 item_current = this->item_next; + FindNext(); + return item_current; + } + + void Remove(int item) { + if (!this->HasNext()) return; + + /* If we remove the 'next' item, skip to the next */ + if (item == this->item_next) { + FindNext(); + return; + } + } + + bool HasNext() + { + return !(this->list->items.empty() || this->has_no_more_items); + } +}; + +/** + * Sort by item, descending. + */ +class AIAbstractListSorterItemDescending : public AIAbstractListSorter { +private: + AIAbstractList::AIAbstractListMap::iterator item_iter; + bool has_no_more_items; + int32 item_next; + +public: + AIAbstractListSorterItemDescending(AIAbstractList *list) + { + this->list = list; + this->End(); + } + + int32 Begin() + { + if (this->list->items.empty()) return 0; + this->has_no_more_items = false; + + this->item_iter = this->list->items.begin(); + for (size_t i = this->list->items.size(); i > 1; i--) this->item_iter++; + this->item_next = (*this->item_iter).first; + + int32 item_current = this->item_next; + FindNext(); + return item_current; + } + + void End() + { + this->has_no_more_items = true; + } + + void FindNext() + { + if (this->item_iter == this->list->items.end()) { + this->has_no_more_items = true; + return; + } + this->item_iter--; + if (this->item_iter != this->list->items.end()) item_next = (*this->item_iter).first; + } + + int32 Next() + { + if (!this->HasNext()) return 0; + + int32 item_current = this->item_next; + FindNext(); + return item_current; + } + + void Remove(int item) + { + if (!this->HasNext()) return; + + /* If we remove the 'next' item, skip to the next */ + if (item == this->item_next) { + FindNext(); + return; + } + } + + bool HasNext() + { + return !(this->list->items.empty() || this->has_no_more_items); + } +}; + + + +AIAbstractList::AIAbstractList() +{ + /* Default sorter */ + this->sorter = new AIAbstractListSorterValueDescending(this); + this->sorter_type = SORT_BY_VALUE; + this->sort_ascending = false; + this->initialized = false; +} + +AIAbstractList::~AIAbstractList() +{ + delete this->sorter; +} + +bool AIAbstractList::HasItem(int32 item) +{ + return this->items.count(item) == 1; +} + +void AIAbstractList::Clear() +{ + this->items.clear(); + this->buckets.clear(); + this->sorter->End(); +} + +void AIAbstractList::AddItem(int32 item) +{ + if (this->HasItem(item)) return; + + this->items[item] = 0; + this->buckets[0].insert(item); +} + +void AIAbstractList::RemoveItem(int32 item) +{ + if (!this->HasItem(item)) return; + + int32 value = this->GetValue(item); + + this->sorter->Remove(item); + this->buckets[value].erase(item); + if (this->buckets[value].empty()) this->buckets.erase(value); + this->items.erase(item); +} + +int32 AIAbstractList::Begin() +{ + this->initialized = true; + return this->sorter->Begin(); +} + +int32 AIAbstractList::Next() +{ + if (this->initialized == false) { + DEBUG(ai, 0, "ERROR: Next() is invalid as Begin() is never called"); + return false; + } + return this->sorter->Next(); +} + +bool AIAbstractList::IsEmpty() +{ + return this->items.empty(); +} + +bool AIAbstractList::HasNext() +{ + if (this->initialized == false) { + DEBUG(ai, 0, "ERROR: HasNext() is invalid as Begin() is never called"); + return false; + } + return this->sorter->HasNext(); +} + +int32 AIAbstractList::Count() +{ + return (int32)this->items.size(); +} + +int32 AIAbstractList::GetValue(int32 item) +{ + if (!this->HasItem(item)) return 0; + + return this->items[item]; +} + +bool AIAbstractList::SetValue(int32 item, int32 value) +{ + if (!this->HasItem(item)) return false; + + int32 value_old = this->GetValue(item); + + this->sorter->Remove(item); + this->buckets[value_old].erase(item); + if (this->buckets[value_old].empty()) this->buckets.erase(value_old); + this->items[item] = value; + this->buckets[value].insert(item); + + return true; +} + +void AIAbstractList::Sort(SorterType sorter, bool ascending) +{ + if (sorter != SORT_BY_VALUE && sorter != SORT_BY_ITEM) return; + if (sorter == this->sorter_type && ascending == this->sort_ascending) return; + + delete this->sorter; + switch (sorter) { + case SORT_BY_ITEM: + if (ascending) this->sorter = new AIAbstractListSorterItemAscending(this); + else this->sorter = new AIAbstractListSorterItemDescending(this); + break; + + case SORT_BY_VALUE: + if (ascending) this->sorter = new AIAbstractListSorterValueAscending(this); + else this->sorter = new AIAbstractListSorterValueDescending(this); + break; + + default: + this->Sort(SORT_BY_ITEM, false); + return; + } + this->sorter_type = sorter; + this->sort_ascending = ascending; +} + +void AIAbstractList::AddList(AIAbstractList *list) +{ + AIAbstractListMap *list_items = &list->items; + for (AIAbstractListMap::iterator iter = list_items->begin(); iter != list_items->end(); iter++) { + this->AddItem((*iter).first); + this->SetValue((*iter).first, (*iter).second); + } +} + +void AIAbstractList::RemoveAboveValue(int32 value) +{ + for (AIAbstractListMap::iterator next_iter, iter = this->items.begin(); iter != this->items.end(); iter = next_iter) { + next_iter = iter; next_iter++; + if ((*iter).second > value) this->items.erase(iter); + } + + for (AIAbstractListBucket::iterator next_iter, iter = this->buckets.begin(); iter != this->buckets.end(); iter = next_iter) { + next_iter = iter; next_iter++; + if ((*iter).first > value) this->buckets.erase(iter); + } +} + +void AIAbstractList::RemoveBelowValue(int32 value) +{ + for (AIAbstractListMap::iterator next_iter, iter = this->items.begin(); iter != this->items.end(); iter = next_iter) { + next_iter = iter; next_iter++; + if ((*iter).second < value) this->items.erase(iter); + } + + for (AIAbstractListBucket::iterator next_iter, iter = this->buckets.begin(); iter != this->buckets.end(); iter = next_iter) { + next_iter = iter; next_iter++; + if ((*iter).first < value) this->buckets.erase(iter); + } +} + +void AIAbstractList::RemoveBetweenValue(int32 start, int32 end) +{ + for (AIAbstractListMap::iterator next_iter, iter = this->items.begin(); iter != this->items.end(); iter = next_iter) { + next_iter = iter; next_iter++; + if ((*iter).second > start && (*iter).second < end) this->items.erase(iter); + } + + for (AIAbstractListBucket::iterator next_iter, iter = this->buckets.begin(); iter != this->buckets.end(); iter = next_iter) { + next_iter = iter; next_iter++; + if ((*iter).first > start && (*iter).first < end) this->buckets.erase(iter); + } +} + +void AIAbstractList::RemoveValue(int32 value) +{ + for (AIAbstractListMap::iterator next_iter, iter = this->items.begin(); iter != this->items.end(); iter = next_iter) { + next_iter = iter; next_iter++; + if ((*iter).second == value) this->items.erase(iter); + } + + for (AIAbstractListBucket::iterator next_iter, iter = this->buckets.begin(); iter != this->buckets.end(); iter = next_iter) { + next_iter = iter; next_iter++; + if ((*iter).first == value) this->buckets.erase(iter); + } +} + +void AIAbstractList::RemoveTop(int32 count) +{ + if (!this->sort_ascending) { + this->Sort(this->sorter_type, !this->sort_ascending); + this->RemoveBottom(count); + this->Sort(this->sorter_type, !this->sort_ascending); + return; + } + + switch (this->sorter_type) { + default: NOT_REACHED(); + case SORT_BY_VALUE: + for (AIAbstractListBucket::iterator iter = this->buckets.begin(); iter != this->buckets.end(); iter = this->buckets.begin()) { + AIItemList *items = &(*iter).second; + size_t size = items->size(); + for (AIItemList::iterator iter = items->begin(); iter != items->end(); iter = items->begin()) { + if (--count < 0) return; + this->RemoveItem(*iter); + /* When the last item is removed from the bucket, the bucket itself is removed. + * This means that the iterators can be invalid after a call to RemoveItem. + */ + if (--size == 0) break; + } + } + break; + + case SORT_BY_ITEM: + for (AIAbstractListMap::iterator iter = this->items.begin(); iter != this->items.end(); iter = this->items.begin()) { + if (--count < 0) return; + this->RemoveItem((*iter).first); + } + break; + } +} + +void AIAbstractList::RemoveBottom(int32 count) +{ + if (!this->sort_ascending) { + this->Sort(this->sorter_type, !this->sort_ascending); + this->RemoveTop(count); + this->Sort(this->sorter_type, !this->sort_ascending); + return; + } + + switch (this->sorter_type) { + default: NOT_REACHED(); + case SORT_BY_VALUE: + for (AIAbstractListBucket::reverse_iterator iter = this->buckets.rbegin(); iter != this->buckets.rend(); iter = this->buckets.rbegin()) { + AIItemList *items = &(*iter).second; + size_t size = items->size(); + for (AIItemList::reverse_iterator iter = items->rbegin(); iter != items->rend(); iter = items->rbegin()) { + if (--count < 0) return; + this->RemoveItem(*iter); + /* When the last item is removed from the bucket, the bucket itself is removed. + * This means that the iterators can be invalid after a call to RemoveItem. + */ + if (--size == 0) break; + } + } + + case SORT_BY_ITEM: + for (AIAbstractListMap::reverse_iterator iter = this->items.rbegin(); iter != this->items.rend(); iter = this->items.rbegin()) { + if (--count < 0) return; + this->RemoveItem((*iter).first); + } + break; + } +} + +void AIAbstractList::RemoveList(AIAbstractList *list) +{ + AIAbstractListMap *list_items = &list->items; + for (AIAbstractListMap::iterator iter = list_items->begin(); iter != list_items->end(); iter++) { + this->RemoveItem((*iter).first); + } +} + +void AIAbstractList::KeepAboveValue(int32 value) +{ + for (AIAbstractListMap::iterator next_iter, iter = this->items.begin(); iter != this->items.end(); iter = next_iter) { + next_iter = iter; next_iter++; + if ((*iter).second <= value) this->items.erase(iter); + } + + for (AIAbstractListBucket::iterator next_iter, iter = this->buckets.begin(); iter != this->buckets.end(); iter = next_iter) { + next_iter = iter; next_iter++; + if ((*iter).first <= value) this->buckets.erase(iter); + } +} + +void AIAbstractList::KeepBelowValue(int32 value) +{ + for (AIAbstractListMap::iterator next_iter, iter = this->items.begin(); iter != this->items.end(); iter = next_iter) { + next_iter = iter; next_iter++; + if ((*iter).second >= value) this->items.erase(iter); + } + + for (AIAbstractListBucket::iterator next_iter, iter = this->buckets.begin(); iter != this->buckets.end(); iter = next_iter) { + next_iter = iter; next_iter++; + if ((*iter).first >= value) this->buckets.erase(iter); + } +} + +void AIAbstractList::KeepBetweenValue(int32 start, int32 end) +{ + for (AIAbstractListMap::iterator next_iter, iter = this->items.begin(); iter != this->items.end(); iter = next_iter) { + next_iter = iter; next_iter++; + if ((*iter).second <= start || (*iter).second >= end) this->items.erase(iter); + } + + for (AIAbstractListBucket::iterator next_iter, iter = this->buckets.begin(); iter != this->buckets.end(); iter = next_iter) { + next_iter = iter; next_iter++; + if ((*iter).first <= start || (*iter).first >= end) this->buckets.erase(iter); + } +} + +void AIAbstractList::KeepValue(int32 value) +{ + for (AIAbstractListMap::iterator next_iter, iter = this->items.begin(); iter != this->items.end(); iter = next_iter) { + next_iter = iter; next_iter++; + if ((*iter).second != value) this->items.erase(iter); + } + + for (AIAbstractListBucket::iterator next_iter, iter = this->buckets.begin(); iter != this->buckets.end(); iter = next_iter) { + next_iter = iter; next_iter++; + if ((*iter).first != value) this->buckets.erase(iter); + } +} + +void AIAbstractList::KeepTop(int32 count) +{ + this->RemoveBottom(this->Count() - count); +} + +void AIAbstractList::KeepBottom(int32 count) +{ + this->RemoveTop(this->Count() - count); +} + +void AIAbstractList::KeepList(AIAbstractList *list) +{ + AIAbstractList tmp; + for (AIAbstractListMap::iterator iter = this->items.begin(); iter != this->items.end(); iter++) { + tmp.AddItem((*iter).first); + tmp.SetValue((*iter).first, (*iter).second); + } + + tmp.RemoveList(list); + this->RemoveList(&tmp); +} + +SQInteger AIAbstractList::_get(HSQUIRRELVM vm) { + if (sq_gettype(vm, 2) != OT_INTEGER) return SQ_ERROR; + + SQInteger idx; + sq_getinteger(vm, 2, &idx); + + if (!this->HasItem(idx)) return SQ_ERROR; + + sq_pushinteger(vm, this->GetValue(idx)); + return 1; +} + +SQInteger AIAbstractList::_nexti(HSQUIRRELVM vm) { + if (sq_gettype(vm, 2) == OT_NULL) { + if (this->IsEmpty()) { + sq_pushnull(vm); + return 1; + } + sq_pushinteger(vm, this->Begin()); + return 1; + } + + SQInteger idx; + sq_getinteger(vm, 2, &idx); + + int val = this->Next(); + if (!this->HasNext()) { + sq_pushnull(vm); + return 1; + } + + sq_pushinteger(vm, val); + return 1; +} + +SQInteger AIAbstractList::Valuate(HSQUIRRELVM vm) { + int nparam = sq_gettop(vm) - 2; + + /* Get the list instance and the function to call */ + HSQOBJECT obj_list, obj_func; + sq_getstackobj(vm, 1, &obj_list); + sq_getstackobj(vm, 2, &obj_func); + + if (sq_isclass(obj_list)) { + return sq_throwerror(vm, _SC("parameter 1 has an invalid type (expected instance)")); + } + if (sq_isfunction(obj_func)) { + return sq_throwerror(vm, _SC("parameter 2 has an invalid type (expected function)")); + } + + sq_addref(vm, &obj_func); + + /* Read the params */ + HSQOBJECT *obj_params = AllocaM(HSQOBJECT, nparam); + for (int i = 0; i < nparam; i++) { + sq_getstackobj(vm, i + 3, &obj_params[i]); + sq_addref(vm, &obj_params[i]); + } + /* Remove all unneeded stuff */ + sq_pop(vm, nparam + 1); + + /* Walk all items, and query the result */ + this->buckets.clear(); + for (AIAbstractListMap::iterator iter = this->items.begin(); iter != this->items.end(); iter++) { + /* The function to call */ + sq_pushobject(vm, obj_func); + /* The 'list' instance; this is most likely wrong, but we need to send something ;) */ + sq_pushobject(vm, obj_list); + + /* Now send the params */ + sq_pushinteger(vm, (*iter).first); + for (int i = 0; i < nparam; i++) { + sq_pushobject(vm, obj_params[i]); + } + + /* Call the function */ + if (SQ_FAILED(sq_call(vm, nparam + 2, SQTrue, SQTrue))) return SQ_ERROR; + + /* Retreive the return value */ + SQInteger value; + switch (sq_gettype(vm, -1)) { + case OT_INTEGER: { + sq_getinteger(vm, -1, &value); + } break; + + case OT_BOOL: { + SQBool v; + sq_getbool(vm, -1, &v); + value = v ? 1 : 0; + } break; + + default: { + sq_pop(vm, 3); + sq_release(vm, &obj_func); + for (int i = 0; i < nparam; i++) sq_release(vm, &obj_params[i]); + + return sq_throwerror(vm, _SC("return value of valuator is not valid (not integer/bool)")); + } + } + /* Remove junk */ + sq_pop(vm, 2); + + (*iter).second = (int32)value; + this->buckets[(int32)value].insert((*iter).first); + } + + sq_release(vm, &obj_func); + for (int i = 0; i < nparam; i++) sq_release(vm, &obj_params[i]); + return 0; +} diff --git a/src/ai/api/ai_abstractlist.hpp b/src/ai/api/ai_abstractlist.hpp new file mode 100644 index 000000000..55a4559f8 --- /dev/null +++ b/src/ai/api/ai_abstractlist.hpp @@ -0,0 +1,263 @@ +/* $Id$ */ + +/** @file ai_abstractlist.hpp A list which can keep item/value pairs, which you can walk. */ +/** @defgroup AIList Classes that create a list of items. */ + +#ifndef AI_ABSTRACTLIST_HPP +#define AI_ABSTRACTLIST_HPP + +#include "ai_object.hpp" +#include <map> +#include <set> + +class AIAbstractListSorter; + +/** + * Class that creates a list which can keep item/value pairs, which you can walk. + */ +class AIAbstractList : public AIObject { +public: + static const char *GetClassName() { return "AIAbstractList"; } + + /** Type of sorter */ + enum SorterType { + SORT_BY_VALUE, ///< Sort the list based on the value of the item. + SORT_BY_ITEM, ///< Sort the list based on the item itself. + }; + +private: + AIAbstractListSorter *sorter; + SorterType sorter_type; + bool sort_ascending; + bool initialized; + +public: + typedef std::set<int32> AIItemList; //!< The list of items inside the bucket + typedef std::map<int32, AIItemList> AIAbstractListBucket; //!< The bucket list per value + typedef std::map<int32, int32> AIAbstractListMap; //!< List per item + + AIAbstractListMap items; //!< The items in the list + AIAbstractListBucket buckets; //!< The items in the list, sorted by value + +protected: + /** + * Add a single item to the list. + * @param item the item to add. Should be unique, otherwise it is ignored. + * @note the value is set to 0 by default. + */ + void AddItem(int32 item); + + /** + * Remove a single item from the list. + * @param item the item to remove. If not existing, it is ignored. + */ + void RemoveItem(int32 item); + +public: + AIAbstractList(); + ~AIAbstractList(); + + /** + * Clear the list, making Count() returning 0 and IsEmpty() returning true. + */ + void Clear(); + + /** + * Check if an item is in the list. + * @param item the item to check for. + * @return true if the item is in the list. + */ + bool HasItem(int32 item); + + /** + * Go to the beginning of the list. + * @return the item value of the first item. + */ + int32 Begin(); + + /** + * Go to the next item in the list. + * @return the item value of the next item. + * @note returns 0 if beyond end-of-list. Use HasNext() to check for end-of-list. + */ + int32 Next(); + + /** + * Check if a list is empty. + * @return true if the list is empty. + */ + bool IsEmpty(); + + /** + * Check if there is a next element. In other words, if this is true, + * Next() will return a valid item. + * @return true if there is a next item. + */ + bool HasNext(); + + /** + * Returns the amount of items in the list. + * @return amount of items in the list. + */ + int32 Count(); + + /** + * Get the value that belongs to this item. + * @param item the item to get the value from + * @return the value that belongs to this item. + */ + int32 GetValue(int32 item); + + /** + * Set a value of an item directly. + * @param item the item to set the value for. + * @param value the value to give to the item + * @return true if we could set the item to value, false otherwise. + * @note Changing values of items while looping through a list might cause + * entries to be skipped. Be very careful with such operations. + */ + bool SetValue(int32 item, int32 value); + + /** + * Sort this list by the given sorter and direction. + * @param sorter the type of sorter to use + * @param ascending if true, lowest value is on top, else at bottom. + * @note the current item stays at the same place. + */ + void Sort(SorterType sorter, bool ascending); + + /** + * Add one list to an other one. + * @param list The list that will be added to the caller. + * @post The list to be added ('list') stays unmodified. + * @note All added items keep their value as it was in 'list'. + * @note If the item already exists inside the caller, the value of the + * list that is added is set on the item. + */ + void AddList(AIAbstractList *list); + + /** + * Removes all items with a higher value than 'value'. + * @param value the value above which all items are removed. + */ + void RemoveAboveValue(int32 value); + + /** + * Removes all items with a lower value than 'value'. + * @param value the value below which all items are removed. + */ + void RemoveBelowValue(int32 value); + + /** + * Removes all items with a value above start and below end. + * @param start the lower bound of the to be removed values (exclusive). + * @param end the upper bound of the to be removed valuens (exclusive). + */ + void RemoveBetweenValue(int32 start, int32 end); + + /** + * Remove all items with this value. + * @param value the value to remove. + */ + void RemoveValue(int32 value); + + /** + * Remove the first count items. + * @param count the amount of items to remove. + */ + void RemoveTop(int32 count); + + /** + * Remove the last count items. + * @param count the amount of items to remove. + */ + void RemoveBottom(int32 count); + + /** + * Remove everything that is in the given list from this list (same item index that is). + * @param list the list of items to remove. + * @pre list != NULL + */ + void RemoveList(AIAbstractList *list); + + /** + * Keep all items with a higher value than 'value'. + * @param value the value above which all items are kept. + */ + void KeepAboveValue(int32 value); + + /** + * Keep all items with a lower value than 'value'. + * @param value the value below which all items are kept. + */ + void KeepBelowValue(int32 value); + + /** + * Keep all items with a value above start and below end. + * @param start the lower bound of the to be kept values (exclusive). + * @param end the upper bound of the to be kept values (exclusive). + */ + void KeepBetweenValue(int32 start, int32 end); + + /** + * Keep all items with this value. + * @param value the value to keep. + **/ + void KeepValue(int32 value); + + /** + * Keep the first count items, i.e. remove everything except the first count items. + * @param count the amount of items to keep. + */ + void KeepTop(int32 count); + + /** + * Keep the last count items, i.e. remove everything except the last count items. + * @param count the amount of items to keep. + */ + void KeepBottom(int32 count); + + /** + * Keeps everything that is in the given list from this list (same item index that is). + * @param list the list of items to keep. + * @pre list != NULL + */ + void KeepList(AIAbstractList *list); + +#ifndef DOXYGEN_SKIP + /** + * Used for 'foreach()' and [] get from Squirrel. + */ + SQInteger _get(HSQUIRRELVM vm); + + /** + * Used for 'foreach()' from Squirrel. + */ + SQInteger _nexti(HSQUIRRELVM vm); + + /** + * The Valuate() wrapper from Squirrel. + */ + SQInteger Valuate(HSQUIRRELVM vm); +#else + /** + * Give all items a value defined by the valuator you give. + * @param valuator_function The function which will be doing the valuation. + * @param params The params to give to the valuators (minus the first param, + * which is always the index-value we are valuating). + * @note You can write your own valuators and use them. Just remember that + * the first parameter should be the index-value, and it should return + * an integer. + * @note Example: + * list.Valuate(AIBridge.GetPrice, 5); + * list.Valuate(AIBridge.GetMaxLength); + * function MyVal(bridge_id, myparam) { + * return myparam * bridge_id; // This is silly + * } + * list.Valuate(MyVal, 12); + */ + void Valuate(void *valuator_function, int params, ...); +#endif /* DOXYGEN_SKIP */ +}; + +#endif /* AI_LIST_HPP */ diff --git a/src/ai/api/ai_abstractlist.hpp.sq b/src/ai/api/ai_abstractlist.hpp.sq new file mode 100644 index 000000000..25d9f3904 --- /dev/null +++ b/src/ai/api/ai_abstractlist.hpp.sq @@ -0,0 +1,59 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_abstractlist.hpp" + +namespace SQConvert { + /* Allow enums to be used as Squirrel parameters */ + template <> AIAbstractList::SorterType GetParam(ForceType<AIAbstractList::SorterType>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AIAbstractList::SorterType)tmp; } + template <> int Return<AIAbstractList::SorterType>(HSQUIRRELVM vm, AIAbstractList::SorterType res) { sq_pushinteger(vm, (int32)res); return 1; } + + /* Allow AIAbstractList to be used as Squirrel parameter */ + template <> AIAbstractList *GetParam(ForceType<AIAbstractList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIAbstractList *)instance; } + template <> AIAbstractList &GetParam(ForceType<AIAbstractList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIAbstractList *)instance; } + template <> const AIAbstractList *GetParam(ForceType<const AIAbstractList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIAbstractList *)instance; } + template <> const AIAbstractList &GetParam(ForceType<const AIAbstractList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIAbstractList *)instance; } + template <> int Return<AIAbstractList *>(HSQUIRRELVM vm, AIAbstractList *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIAbstractList", res, NULL, DefSQDestructorCallback<AIAbstractList>); return 1; } +}; // namespace SQConvert + +void SQAIAbstractList_Register(Squirrel *engine) { + DefSQClass <AIAbstractList> SQAIAbstractList("AIAbstractList"); + SQAIAbstractList.PreRegister(engine); + SQAIAbstractList.AddConstructor<void (AIAbstractList::*)(), 1>(engine, "x"); + + SQAIAbstractList.DefSQConst(engine, AIAbstractList::SORT_BY_VALUE, "SORT_BY_VALUE"); + SQAIAbstractList.DefSQConst(engine, AIAbstractList::SORT_BY_ITEM, "SORT_BY_ITEM"); + + SQAIAbstractList.DefSQStaticMethod(engine, &AIAbstractList::GetClassName, "GetClassName", 1, "x"); + + SQAIAbstractList.DefSQMethod(engine, &AIAbstractList::Clear, "Clear", 1, "x"); + SQAIAbstractList.DefSQMethod(engine, &AIAbstractList::HasItem, "HasItem", 2, "xi"); + SQAIAbstractList.DefSQMethod(engine, &AIAbstractList::Begin, "Begin", 1, "x"); + SQAIAbstractList.DefSQMethod(engine, &AIAbstractList::Next, "Next", 1, "x"); + SQAIAbstractList.DefSQMethod(engine, &AIAbstractList::IsEmpty, "IsEmpty", 1, "x"); + SQAIAbstractList.DefSQMethod(engine, &AIAbstractList::HasNext, "HasNext", 1, "x"); + SQAIAbstractList.DefSQMethod(engine, &AIAbstractList::Count, "Count", 1, "x"); + SQAIAbstractList.DefSQMethod(engine, &AIAbstractList::GetValue, "GetValue", 2, "xi"); + SQAIAbstractList.DefSQMethod(engine, &AIAbstractList::SetValue, "SetValue", 3, "xii"); + SQAIAbstractList.DefSQMethod(engine, &AIAbstractList::Sort, "Sort", 3, "xib"); + SQAIAbstractList.DefSQMethod(engine, &AIAbstractList::AddList, "AddList", 2, "xx"); + SQAIAbstractList.DefSQMethod(engine, &AIAbstractList::RemoveAboveValue, "RemoveAboveValue", 2, "xi"); + SQAIAbstractList.DefSQMethod(engine, &AIAbstractList::RemoveBelowValue, "RemoveBelowValue", 2, "xi"); + SQAIAbstractList.DefSQMethod(engine, &AIAbstractList::RemoveBetweenValue, "RemoveBetweenValue", 3, "xii"); + SQAIAbstractList.DefSQMethod(engine, &AIAbstractList::RemoveValue, "RemoveValue", 2, "xi"); + SQAIAbstractList.DefSQMethod(engine, &AIAbstractList::RemoveTop, "RemoveTop", 2, "xi"); + SQAIAbstractList.DefSQMethod(engine, &AIAbstractList::RemoveBottom, "RemoveBottom", 2, "xi"); + SQAIAbstractList.DefSQMethod(engine, &AIAbstractList::RemoveList, "RemoveList", 2, "xx"); + SQAIAbstractList.DefSQMethod(engine, &AIAbstractList::KeepAboveValue, "KeepAboveValue", 2, "xi"); + SQAIAbstractList.DefSQMethod(engine, &AIAbstractList::KeepBelowValue, "KeepBelowValue", 2, "xi"); + SQAIAbstractList.DefSQMethod(engine, &AIAbstractList::KeepBetweenValue, "KeepBetweenValue", 3, "xii"); + SQAIAbstractList.DefSQMethod(engine, &AIAbstractList::KeepValue, "KeepValue", 2, "xi"); + SQAIAbstractList.DefSQMethod(engine, &AIAbstractList::KeepTop, "KeepTop", 2, "xi"); + SQAIAbstractList.DefSQMethod(engine, &AIAbstractList::KeepBottom, "KeepBottom", 2, "xi"); + SQAIAbstractList.DefSQMethod(engine, &AIAbstractList::KeepList, "KeepList", 2, "xx"); + SQAIAbstractList.DefSQAdvancedMethod(engine, &AIAbstractList::_get, "_get"); + SQAIAbstractList.DefSQAdvancedMethod(engine, &AIAbstractList::_nexti, "_nexti"); + SQAIAbstractList.DefSQAdvancedMethod(engine, &AIAbstractList::Valuate, "Valuate"); + + SQAIAbstractList.PostRegister(engine); +} diff --git a/src/ai/api/ai_accounting.cpp b/src/ai/api/ai_accounting.cpp new file mode 100644 index 000000000..2134d39b0 --- /dev/null +++ b/src/ai/api/ai_accounting.cpp @@ -0,0 +1,26 @@ +/* $Id$ */ + +/** @file ai_accounting.cpp Implementation of AIAccounting. */ + +#include "ai_accounting.hpp" + +Money AIAccounting::GetCosts() +{ + return this->GetDoCommandCosts(); +} + +void AIAccounting::ResetCosts() +{ + this->SetDoCommandCosts(0); +} + +AIAccounting::AIAccounting() +{ + this->last_costs = this->GetDoCommandCosts(); + this->SetDoCommandCosts(0); +} + +AIAccounting::~AIAccounting() +{ + this->SetDoCommandCosts(this->last_costs); +} diff --git a/src/ai/api/ai_accounting.hpp b/src/ai/api/ai_accounting.hpp new file mode 100644 index 000000000..bffa051f1 --- /dev/null +++ b/src/ai/api/ai_accounting.hpp @@ -0,0 +1,54 @@ +/* $Id$ */ + +/** @file ai_accounting.hpp Everything to handle AI accounting things. */ + +#ifndef AI_ACCOUNTING_HPP +#define AI_ACCOUNTING_HPP + +#include "ai_object.hpp" + +/** + * Class that keeps track of the costs, so you can request how much a block of + * commands did cost in total. Works in both Execute as in Test mode. + * Example: + * { + * local costs = AIAccounting(); + * BuildRoad(from_here, to_here); + * BuildRoad(from_there, to_there); + * print("Costs for route is: " + costs.GetCosts()); + * } + */ +class AIAccounting : public AIObject { +public: + static const char *GetClassName() { return "AIAccounting"; } + + /** + * Creating instance of this class starts counting the costs of commands + * from zero. + * @note when the instance is destroyed, he restores the costs that was + * current when the instance was created! + */ + AIAccounting(); + + /** + * Destroying this instance reset the costs to the value it was + * in when the instance was created. + */ + ~AIAccounting(); + + /** + * Get the current value of the costs. + * @return The current costs. + */ + Money GetCosts(); + + /** + * Reset the costs to zero. + */ + void ResetCosts(); + +private: + Money last_costs; +}; + +#endif /* AI_ACCOUNTING_HPP */ diff --git a/src/ai/api/ai_accounting.hpp.sq b/src/ai/api/ai_accounting.hpp.sq new file mode 100644 index 000000000..18922b24c --- /dev/null +++ b/src/ai/api/ai_accounting.hpp.sq @@ -0,0 +1,26 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_accounting.hpp" + +namespace SQConvert { + /* Allow AIAccounting to be used as Squirrel parameter */ + template <> AIAccounting *GetParam(ForceType<AIAccounting *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIAccounting *)instance; } + template <> AIAccounting &GetParam(ForceType<AIAccounting &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIAccounting *)instance; } + template <> const AIAccounting *GetParam(ForceType<const AIAccounting *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIAccounting *)instance; } + template <> const AIAccounting &GetParam(ForceType<const AIAccounting &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIAccounting *)instance; } + template <> int Return<AIAccounting *>(HSQUIRRELVM vm, AIAccounting *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIAccounting", res, NULL, DefSQDestructorCallback<AIAccounting>); return 1; } +}; // namespace SQConvert + +void SQAIAccounting_Register(Squirrel *engine) { + DefSQClass <AIAccounting> SQAIAccounting("AIAccounting"); + SQAIAccounting.PreRegister(engine); + SQAIAccounting.AddConstructor<void (AIAccounting::*)(), 1>(engine, "x"); + + SQAIAccounting.DefSQStaticMethod(engine, &AIAccounting::GetClassName, "GetClassName", 1, "x"); + + SQAIAccounting.DefSQMethod(engine, &AIAccounting::GetCosts, "GetCosts", 1, "x"); + SQAIAccounting.DefSQMethod(engine, &AIAccounting::ResetCosts, "ResetCosts", 1, "x"); + + SQAIAccounting.PostRegister(engine); +} diff --git a/src/ai/api/ai_airport.cpp b/src/ai/api/ai_airport.cpp new file mode 100644 index 000000000..3e435d7bc --- /dev/null +++ b/src/ai/api/ai_airport.cpp @@ -0,0 +1,130 @@ +/* $Id$ */ + +/** @file ai_airport.cpp Implementation of AIAirport. */ + +#include "ai_airport.hpp" +#include "ai_station.hpp" +#include "ai_error.hpp" +#include "../../openttd.h" +#include "../../variables.h" +#include "../../station_map.h" +#include "../../company_func.h" +#include "../../settings_type.h" +#include "../../command_type.h" +#include "../../town.h" + +/* static */ bool AIAirport::IsValidAirportType(AirportType type) +{ + return type >= AT_SMALL && type <= AT_HELISTATION; +} + +/* static */ bool AIAirport::IsHangarTile(TileIndex tile) +{ + if (!::IsValidTile(tile)) return false; + + return ::IsTileType(tile, MP_STATION) && ::IsHangar(tile); +} + +/* static */ bool AIAirport::IsAirportTile(TileIndex tile) +{ + if (!::IsValidTile(tile)) return false; + + return ::IsTileType(tile, MP_STATION) && ::IsAirport(tile); +} + +/* static */ bool AIAirport::AirportAvailable(AirportType type) +{ + if (!IsValidAirportType(type)) return false; + + return HasBit(::GetValidAirports(), type); +} + +/* static */ int32 AIAirport::GetAirportWidth(AirportType type) +{ + if (!IsValidAirportType(type)) return -1; + + return ::GetAirport(type)->size_x; +} + +/* static */ int32 AIAirport::GetAirportHeight(AirportType type) +{ + if (!IsValidAirportType(type)) return -1; + + return ::GetAirport(type)->size_y; +} + +/* static */ int32 AIAirport::GetAirportCoverageRadius(AirportType type) +{ + if (!IsValidAirportType(type)) return -1; + + return _settings_game.station.modified_catchment ? ::GetAirport(type)->catchment : (uint)CA_UNMODIFIED; +} + +/* static */ bool AIAirport::BuildAirport(TileIndex tile, AirportType type, bool join_adjacent) +{ + EnforcePrecondition(false, ::IsValidTile(tile)); + EnforcePrecondition(false, IsValidAirportType(type)); + + return AIObject::DoCommand(tile, type, (INVALID_STATION << 16) | (join_adjacent ? 0 : 1), CMD_BUILD_AIRPORT); +} + +/* static */ bool AIAirport::RemoveAirport(TileIndex tile) +{ + EnforcePrecondition(false, ::IsValidTile(tile)) + EnforcePrecondition(false, IsAirportTile(tile) || IsHangarTile(tile)); + + return AIObject::DoCommand(tile, 0, 0, CMD_LANDSCAPE_CLEAR); +} + +/* static */ int32 AIAirport::GetNumHangars(TileIndex tile) +{ + if (!::IsValidTile(tile)) return -1; + if (!::IsTileType(tile, MP_STATION)) return -1; + + const Station *st = ::GetStationByTile(tile); + if (st->owner != _current_company) return -1; + if ((st->facilities & FACIL_AIRPORT) == 0) return -1; + + return st->Airport()->nof_depots; +} + +/* static */ TileIndex AIAirport::GetHangarOfAirport(TileIndex tile) +{ + if (!::IsValidTile(tile)) return INVALID_TILE; + if (!::IsTileType(tile, MP_STATION)) return INVALID_TILE; + if (GetNumHangars(tile) < 1) return INVALID_TILE; + + const Station *st = ::GetStationByTile(tile); + if (st->owner != _current_company) return INVALID_TILE; + if ((st->facilities & FACIL_AIRPORT) == 0) return INVALID_TILE; + + return ::ToTileIndexDiff(st->Airport()->airport_depots[0]) + st->xy; +} + +/* static */ AIAirport::AirportType AIAirport::GetAirportType(TileIndex tile) +{ + if (!AITile::IsStationTile(tile)) return AT_INVALID; + + StationID station_id = ::GetStationIndex(tile); + + if (!AIStation::HasStationType(station_id, AIStation::STATION_AIRPORT)) return AT_INVALID; + + return (AirportType)::GetStation(station_id)->airport_type; +} + + +/* static */ int AIAirport::GetNoiseLevelIncrease(TileIndex tile, AirportType type) +{ + extern uint8 GetAirportNoiseLevelForTown(const AirportFTAClass *afc, TileIndex town_tile, TileIndex tile); + + if (!::IsValidTile(tile)) return -1; + if (!IsValidAirportType(type)) return -1; + + if (_settings_game.economy.station_noise_level) { + const AirportFTAClass *afc = ::GetAirport(type); + const Town *t = ::ClosestTownFromTile(tile, UINT_MAX); + return GetAirportNoiseLevelForTown(afc, t->xy, tile); + } + + return 1; +} diff --git a/src/ai/api/ai_airport.hpp b/src/ai/api/ai_airport.hpp new file mode 100644 index 000000000..d1d18714b --- /dev/null +++ b/src/ai/api/ai_airport.hpp @@ -0,0 +1,164 @@ +/* $Id$ */ + +/** @file ai_airport.hpp Everything to query and build airports. */ + +#ifndef AI_AIRPORT_HPP +#define AI_AIRPORT_HPP + +#include "ai_object.hpp" + +/** + * Class that handles all airport related functions. + */ +class AIAirport : public AIObject { +public: + static const char *GetClassName() { return "AIAirport"; } + + /** + * The types of airports available in the game. + */ + enum AirportType { + /* Note: the values _are_ important as they represent an in-game value */ + AT_SMALL = 0, //!< The small airport. + AT_LARGE = 1, //!< The large airport. + AT_METROPOLITAN = 3, //!< The metropolitan airport. + AT_INTERNATIONAL = 4, //!< The international airport. + AT_COMMUTER = 5, //!< The commuter airport. + AT_INTERCON = 7, //!< The intercontinental airport. + + /* Next are the airports which only have helicopter platforms */ + AT_HELIPORT = 2, //!< The heliport. + AT_HELISTATION = 8, //!< The helistation. + AT_HELIDEPOT = 6, //!< The helidepot. + + AT_INVALID = 255, //!< Invalid airport. + }; + + /** + * All plane types available. + */ + enum PlaneType { + /* Note: the values _are_ important as they represent an in-game value */ + PT_HELICOPTER = 0, //!< A helicopter. + PT_SMALL_PLANE = 1, //!< A small plane. + PT_BIG_PLANE = 3, //!< A big plane. + + PT_INVALID = -1, //!< An invalid PlaneType + }; + + /** + * Checks whether the given AirportType is valid. + * @param type The AirportType to check. + * @return True if and only if the AirportTypeis valid. + */ + static bool IsValidAirportType(AirportType type); + + /** + * Checks whether the given tile is actually a tile with a hangar. + * @param tile The tile to check. + * @pre AIMap::IsValidTile(tile). + * @return True if and only if the tile has a hangar. + */ + static bool IsHangarTile(TileIndex tile); + + /** + * Checks whether the given tile is actually a tile with a airport. + * @param tile The tile to check. + * @pre AIMap::IsValidTile(tile). + * @return True if and only if the tile has a airport. + */ + static bool IsAirportTile(TileIndex tile); + + /** + * Check if a certain airport type is already available. + * @param type The type of airport to check. + */ + static bool AirportAvailable(AirportType type); + + /** + * Get the width of this type of airport. + * @param type The type of airport. + * @return The width in tiles. + */ + static int32 GetAirportWidth(AirportType type); + + /** + * Get the height of this type of airport. + * @param type The type of airport. + * @return The height in tiles. + */ + static int32 GetAirportHeight(AirportType type); + + /** + * Get the coverage radius of this type of airport. + * @param type The type of airport. + * @return The radius in tiles. + */ + static int32 GetAirportCoverageRadius(AirportType type); + + /** + * Get the number of hangars of the airport. + * @param tile Any tile of the airport. + * @pre AIMap::IsValidTile(tile). + * @return The number of hangars of the airport. + */ + static int32 GetNumHangars(TileIndex tile); + + /** + * Get the first hanger tile of the airport. + * @param tile Any tile of the airport. + * @pre AIMap::IsValidTile(tile). + * @pre GetNumHangars(tile) > 0. + * @return The first hanger tile of the airport. + * @note Possible there are more hangars, but you won't be able to find them + * without walking over all the tiles of the airport and using + * IsHangarTile() on them. + */ + static TileIndex GetHangarOfAirport(TileIndex tile); + + /** + * Builds a airport with tile at the topleft corner. + * @param tile The topleft corner of the airport. + * @param type The type of airport to build. + * @param join_adjacent When building next to an other station, don't create a new station when this flag is true. + * @pre AIMap::IsValidTile(tile). + * @pre AirportAvailable(type). + * @exception AIError::ERR_AREA_NOT_CLEAR + * @exception AIError::ERR_FLAT_LAND_REQUIRED + * @exception AIError::ERR_LOCAL_AUTHORITY_REFUSES + * @exception AIStation::ERR_STATION_TOO_LARGE + * @exception AIStation::ERR_STATION_TOO_CLOSE_TO_OTHER_STATION + * @return Whether the airport has been/can be build or not. + */ + static bool BuildAirport(TileIndex tile, AirportType type, bool join_adjacent); + + /** + * Removes a airport. + * @param tile Any tile of the airport. + * @pre AIMap::IsValidTile(tile). + * @exception AIError::ERR_OWNED_BY_ANOTHER_COMPANY + * @return Whether the airport has been/can be removed or not. + */ + static bool RemoveAirport(TileIndex tile); + + /** + * Get the AirportType of an existing airport. + * @param tile Any tile of the airport. + * @pre AITile::IsStationTile(tile). + * @pre AIStation::HasStationType(AIStation.GetStationID(tile), AIStation::STATION_AIRPORT). + * @return The AirportType of the airport. + */ + static AirportType GetAirportType(TileIndex tile); + + /** + * Get the noise that will be added to the nearest town if an airport was + * built at this tile. + * @param tile The tile to check. + * @param type The AirportType to check. + * @return The TownID of the town closest to the tile. + * @note The noise will be added to the town with TownID AITile.GetClosestTown(tile). + */ + static int GetNoiseLevelIncrease(TileIndex tile, AirportType type); +}; + +#endif /* AI_AIRPORT_HPP */ diff --git a/src/ai/api/ai_airport.hpp.sq b/src/ai/api/ai_airport.hpp.sq new file mode 100644 index 000000000..355a2ab12 --- /dev/null +++ b/src/ai/api/ai_airport.hpp.sq @@ -0,0 +1,57 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_airport.hpp" + +namespace SQConvert { + /* Allow enums to be used as Squirrel parameters */ + template <> AIAirport::AirportType GetParam(ForceType<AIAirport::AirportType>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AIAirport::AirportType)tmp; } + template <> int Return<AIAirport::AirportType>(HSQUIRRELVM vm, AIAirport::AirportType res) { sq_pushinteger(vm, (int32)res); return 1; } + template <> AIAirport::PlaneType GetParam(ForceType<AIAirport::PlaneType>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AIAirport::PlaneType)tmp; } + template <> int Return<AIAirport::PlaneType>(HSQUIRRELVM vm, AIAirport::PlaneType res) { sq_pushinteger(vm, (int32)res); return 1; } + + /* Allow AIAirport to be used as Squirrel parameter */ + template <> AIAirport *GetParam(ForceType<AIAirport *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIAirport *)instance; } + template <> AIAirport &GetParam(ForceType<AIAirport &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIAirport *)instance; } + template <> const AIAirport *GetParam(ForceType<const AIAirport *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIAirport *)instance; } + template <> const AIAirport &GetParam(ForceType<const AIAirport &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIAirport *)instance; } + template <> int Return<AIAirport *>(HSQUIRRELVM vm, AIAirport *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIAirport", res, NULL, DefSQDestructorCallback<AIAirport>); return 1; } +}; // namespace SQConvert + +void SQAIAirport_Register(Squirrel *engine) { + DefSQClass <AIAirport> SQAIAirport("AIAirport"); + SQAIAirport.PreRegister(engine); + SQAIAirport.AddConstructor<void (AIAirport::*)(), 1>(engine, "x"); + + SQAIAirport.DefSQConst(engine, AIAirport::AT_SMALL, "AT_SMALL"); + SQAIAirport.DefSQConst(engine, AIAirport::AT_LARGE, "AT_LARGE"); + SQAIAirport.DefSQConst(engine, AIAirport::AT_METROPOLITAN, "AT_METROPOLITAN"); + SQAIAirport.DefSQConst(engine, AIAirport::AT_INTERNATIONAL, "AT_INTERNATIONAL"); + SQAIAirport.DefSQConst(engine, AIAirport::AT_COMMUTER, "AT_COMMUTER"); + SQAIAirport.DefSQConst(engine, AIAirport::AT_INTERCON, "AT_INTERCON"); + SQAIAirport.DefSQConst(engine, AIAirport::AT_HELIPORT, "AT_HELIPORT"); + SQAIAirport.DefSQConst(engine, AIAirport::AT_HELISTATION, "AT_HELISTATION"); + SQAIAirport.DefSQConst(engine, AIAirport::AT_HELIDEPOT, "AT_HELIDEPOT"); + SQAIAirport.DefSQConst(engine, AIAirport::AT_INVALID, "AT_INVALID"); + SQAIAirport.DefSQConst(engine, AIAirport::PT_HELICOPTER, "PT_HELICOPTER"); + SQAIAirport.DefSQConst(engine, AIAirport::PT_SMALL_PLANE, "PT_SMALL_PLANE"); + SQAIAirport.DefSQConst(engine, AIAirport::PT_BIG_PLANE, "PT_BIG_PLANE"); + SQAIAirport.DefSQConst(engine, AIAirport::PT_INVALID, "PT_INVALID"); + + SQAIAirport.DefSQStaticMethod(engine, &AIAirport::GetClassName, "GetClassName", 1, "x"); + SQAIAirport.DefSQStaticMethod(engine, &AIAirport::IsValidAirportType, "IsValidAirportType", 2, "xi"); + SQAIAirport.DefSQStaticMethod(engine, &AIAirport::IsHangarTile, "IsHangarTile", 2, "xi"); + SQAIAirport.DefSQStaticMethod(engine, &AIAirport::IsAirportTile, "IsAirportTile", 2, "xi"); + SQAIAirport.DefSQStaticMethod(engine, &AIAirport::AirportAvailable, "AirportAvailable", 2, "xi"); + SQAIAirport.DefSQStaticMethod(engine, &AIAirport::GetAirportWidth, "GetAirportWidth", 2, "xi"); + SQAIAirport.DefSQStaticMethod(engine, &AIAirport::GetAirportHeight, "GetAirportHeight", 2, "xi"); + SQAIAirport.DefSQStaticMethod(engine, &AIAirport::GetAirportCoverageRadius, "GetAirportCoverageRadius", 2, "xi"); + SQAIAirport.DefSQStaticMethod(engine, &AIAirport::GetNumHangars, "GetNumHangars", 2, "xi"); + SQAIAirport.DefSQStaticMethod(engine, &AIAirport::GetHangarOfAirport, "GetHangarOfAirport", 2, "xi"); + SQAIAirport.DefSQStaticMethod(engine, &AIAirport::BuildAirport, "BuildAirport", 4, "xiib"); + SQAIAirport.DefSQStaticMethod(engine, &AIAirport::RemoveAirport, "RemoveAirport", 2, "xi"); + SQAIAirport.DefSQStaticMethod(engine, &AIAirport::GetAirportType, "GetAirportType", 2, "xi"); + SQAIAirport.DefSQStaticMethod(engine, &AIAirport::GetNoiseLevelIncrease, "GetNoiseLevelIncrease", 3, "xii"); + + SQAIAirport.PostRegister(engine); +} diff --git a/src/ai/api/ai_base.cpp b/src/ai/api/ai_base.cpp new file mode 100644 index 000000000..fa04bac36 --- /dev/null +++ b/src/ai/api/ai_base.cpp @@ -0,0 +1,43 @@ +/* $Id$ */ + +/** @file ai_base.cpp Implementation of AIBase. */ + +#include "ai_base.hpp" +#include "../../network/network.h" +#include "../../core/random_func.hpp" + +/* static */ uint32 AIBase::Rand() +{ + /* We pick RandomRange if we are in SP (so when saved, we do the same over and over) + * but we pick InteractiveRandomRange if we are a network_server or network-client. */ + if (_networking) return ::InteractiveRandom(); + return ::Random(); +} + +/* static */ uint32 AIBase::RandItem(int unused_param) +{ + return AIBase::Rand(); +} + +/* static */ uint AIBase::RandRange(uint max) +{ + /* We pick RandomRange if we are in SP (so when saved, we do the same over and over) + * but we pick InteractiveRandomRange if we are a network_server or network-client. */ + if (_networking) return ::InteractiveRandomRange(max); + return ::RandomRange(max); +} + +/* static */ uint32 AIBase::RandRangeItem(int unused_param, uint max) +{ + return AIBase::RandRange(max); +} + +/* static */ bool AIBase::Chance(uint out, uint max) +{ + return (uint16)Rand() <= (uint16)((65536 * out) / max); +} + +/* static */ bool AIBase::ChanceItem(int unused_param, uint out, uint max) +{ + return AIBase::Chance(out, max); +} diff --git a/src/ai/api/ai_base.hpp b/src/ai/api/ai_base.hpp new file mode 100644 index 000000000..9a3aad742 --- /dev/null +++ b/src/ai/api/ai_base.hpp @@ -0,0 +1,71 @@ +/* $Id$ */ + +/** @file ai_base.hpp Everything to query basic things. */ + +#ifndef AI_BASE_HPP +#define AI_BASE_HPP + +#include "ai_object.hpp" + +/** + * Class that handles some basic functions. + * + * @note The random functions are not called Random and RandomRange, because + * RANDOM_DEBUG does some tricky stuff, which messes with those names. + * @note In MP we cannot use Random because that will cause desyncs (AIs are + * ran on the server only, not on all clients). This means that + * we use InteractiveRandom in MP. Rand() takes care of this for you. + */ +class AIBase : public AIObject { +public: + static const char *GetClassName() { return "AIBase"; } + + /** + * Get a random value. + * @return A random value between 0 and MAX(uint32). + */ + static uint32 Rand(); + + /** + * Get a random value. + * @param unused_param This param is not used, but is needed to work with lists. + * @return A random value between 0 and MAX(uint32). + */ + static uint32 RandItem(int unused_param); + + /** + * Get a random value in a range. + * @param max The first number this function will never return (the maximum it returns is max - 1). + * @return A random value between 0 .. max - 1. + */ + static uint RandRange(uint max); + + /** + * Get a random value in a range. + * @param unused_param This param is not used, but is needed to work with lists. + * @param max The first number this function will never return (the maximum it returns is max - 1). + * @return A random value between 0 .. max - 1. + */ + static uint RandRangeItem(int unused_param, uint max); + + /** + * Returns approximatelly 'out' times true when called 'max' times. + * After all, it is a random function. + * @param out How many times it should return true. + * @param max Out of this many times. + * @return True if the chance worked out. + */ + static bool Chance(uint out, uint max); + + /** + * Returns approximatelly 'out' times true when called 'max' times. + * After all, it is a random function. + * @param unused_param This param is not used, but is needed to work with lists. + * @param out How many times it should return true. + * @param max Out of this many times. + * @return True if the chance worked out. + */ + static bool ChanceItem(int unused_param, uint out, uint max); +}; + +#endif /* AI_BASE_HPP */ diff --git a/src/ai/api/ai_base.hpp.sq b/src/ai/api/ai_base.hpp.sq new file mode 100644 index 000000000..577301936 --- /dev/null +++ b/src/ai/api/ai_base.hpp.sq @@ -0,0 +1,29 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_base.hpp" + +namespace SQConvert { + /* Allow AIBase to be used as Squirrel parameter */ + template <> AIBase *GetParam(ForceType<AIBase *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIBase *)instance; } + template <> AIBase &GetParam(ForceType<AIBase &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIBase *)instance; } + template <> const AIBase *GetParam(ForceType<const AIBase *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIBase *)instance; } + template <> const AIBase &GetParam(ForceType<const AIBase &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIBase *)instance; } + template <> int Return<AIBase *>(HSQUIRRELVM vm, AIBase *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIBase", res, NULL, DefSQDestructorCallback<AIBase>); return 1; } +}; // namespace SQConvert + +void SQAIBase_Register(Squirrel *engine) { + DefSQClass <AIBase> SQAIBase("AIBase"); + SQAIBase.PreRegister(engine); + SQAIBase.AddConstructor<void (AIBase::*)(), 1>(engine, "x"); + + SQAIBase.DefSQStaticMethod(engine, &AIBase::GetClassName, "GetClassName", 1, "x"); + SQAIBase.DefSQStaticMethod(engine, &AIBase::Rand, "Rand", 1, "x"); + SQAIBase.DefSQStaticMethod(engine, &AIBase::RandItem, "RandItem", 2, "xi"); + SQAIBase.DefSQStaticMethod(engine, &AIBase::RandRange, "RandRange", 2, "xi"); + SQAIBase.DefSQStaticMethod(engine, &AIBase::RandRangeItem, "RandRangeItem", 3, "xii"); + SQAIBase.DefSQStaticMethod(engine, &AIBase::Chance, "Chance", 3, "xii"); + SQAIBase.DefSQStaticMethod(engine, &AIBase::ChanceItem, "ChanceItem", 4, "xiii"); + + SQAIBase.PostRegister(engine); +} diff --git a/src/ai/api/ai_bridge.cpp b/src/ai/api/ai_bridge.cpp new file mode 100644 index 000000000..86eb5f6f1 --- /dev/null +++ b/src/ai/api/ai_bridge.cpp @@ -0,0 +1,177 @@ +/* $Id$ */ + +/** @file ai_bridge.cpp Implementation of AIBridge. */ + +#include "ai_bridge.hpp" +#include "ai_rail.hpp" +#include "../ai_instance.hpp" +#include "../../openttd.h" +#include "../../bridge_map.h" +#include "../../strings_func.h" +#include "../../core/alloc_func.hpp" +#include "../../economy_func.h" +#include "../../settings_type.h" +#include "../../road_map.h" +#include "table/strings.h" + +/* static */ bool AIBridge::IsValidBridge(BridgeID bridge_id) +{ + return bridge_id < MAX_BRIDGES; +} + +/* static */ bool AIBridge::IsBridgeTile(TileIndex tile) +{ + if (!::IsValidTile(tile)) return false; + return ::IsBridgeTile(tile); +} + +static void _DoCommandReturnBuildBridge2(class AIInstance *instance) +{ + if (!AIBridge::_BuildBridgeRoad2()) { + AIObject::SetLastCommandRes(false); + AIInstance::DoCommandReturn(instance); + return; + } + + /* This can never happen, as in test-mode this callback is never executed, + * and in execute-mode, the other callback is called. */ + NOT_REACHED(); +} + +static void _DoCommandReturnBuildBridge1(class AIInstance *instance) +{ + if (!AIBridge::_BuildBridgeRoad1()) { + AIObject::SetLastCommandRes(false); + AIInstance::DoCommandReturn(instance); + return; + } + + /* This can never happen, as in test-mode this callback is never executed, + * and in execute-mode, the other callback is called. */ + NOT_REACHED(); +} + +/* static */ bool AIBridge::BuildBridge(AIVehicle::VehicleType vehicle_type, BridgeID bridge_id, TileIndex start, TileIndex end) +{ + EnforcePrecondition(false, start != end); + EnforcePrecondition(false, ::IsValidTile(start) && ::IsValidTile(end)); + EnforcePrecondition(false, TileX(start) == TileX(end) || TileY(start) == TileY(end)); + EnforcePrecondition(false, vehicle_type == AIVehicle::VEHICLE_ROAD || vehicle_type == AIVehicle::VEHICLE_RAIL || vehicle_type == AIVehicle::VEHICLE_WATER); + EnforcePrecondition(false, vehicle_type != AIVehicle::VEHICLE_RAIL || AIRail::IsRailTypeAvailable(AIRail::GetCurrentRailType())); + + uint type = 0; + switch (vehicle_type) { + case AIVehicle::VEHICLE_ROAD: + type |= (TRANSPORT_ROAD << 15); + type |= (RoadTypeToRoadTypes((::RoadType)AIObject::GetRoadType()) << 8); + break; + case AIVehicle::VEHICLE_RAIL: + type |= (TRANSPORT_RAIL << 15); + type |= (AIRail::GetCurrentRailType() << 8); + break; + case AIVehicle::VEHICLE_WATER: + type |= (TRANSPORT_WATER << 15); + break; + default: NOT_REACHED(); + } + + /* For rail and water we do nothing special */ + if (vehicle_type == AIVehicle::VEHICLE_RAIL || vehicle_type == AIVehicle::VEHICLE_WATER) { + return AIObject::DoCommand(end, start, type | bridge_id, CMD_BUILD_BRIDGE); + } + + AIObject::SetCallbackVariable(0, start); + if (!AIObject::DoCommand(end, start, type | bridge_id, CMD_BUILD_BRIDGE, NULL, &_DoCommandReturnBuildBridge1)) return false; + + /* In case of test-mode, test if we can build both road pieces */ + return _BuildBridgeRoad1(); +} + +/* static */ bool AIBridge::_BuildBridgeRoad1() +{ + /* Build the piece of road on the 'start' side of the bridge */ + TileIndex end = AIObject::GetCallbackVariable(0); + TileIndex start = AIBridge::GetOtherBridgeEnd(end); + + DiagDirection dir_1 = (DiagDirection)((::TileX(start) == ::TileX(end)) ? (::TileY(start) < ::TileY(end) ? DIAGDIR_NW : DIAGDIR_SE) : (::TileX(start) < ::TileX(end) ? DIAGDIR_NE : DIAGDIR_SW)); + DiagDirection dir_2 = ::ReverseDiagDir(dir_1); + + if (!AIObject::DoCommand(start + ::TileOffsByDiagDir(dir_1), ::DiagDirToRoadBits(dir_2) | (AIObject::GetRoadType() << 4), 0, CMD_BUILD_ROAD, NULL, &_DoCommandReturnBuildBridge2)) return false; + + /* In case of test-mode, test the other road piece too */ + return _BuildBridgeRoad2(); +} + +/* static */ bool AIBridge::_BuildBridgeRoad2() +{ + /* Build the piece of road on the 'end' side of the bridge */ + TileIndex end = AIObject::GetCallbackVariable(0); + TileIndex start = AIBridge::GetOtherBridgeEnd(end); + + DiagDirection dir_1 = (DiagDirection)((::TileX(start) == ::TileX(end)) ? (::TileY(start) < ::TileY(end) ? DIAGDIR_NW : DIAGDIR_SE) : (::TileX(start) < ::TileX(end) ? DIAGDIR_NE : DIAGDIR_SW)); + DiagDirection dir_2 = ::ReverseDiagDir(dir_1); + + return AIObject::DoCommand(end + ::TileOffsByDiagDir(dir_2), ::DiagDirToRoadBits(dir_1) | (AIObject::GetRoadType() << 4), 0, CMD_BUILD_ROAD); +} + +/* static */ bool AIBridge::RemoveBridge(TileIndex tile) +{ + EnforcePrecondition(false, IsBridgeTile(tile)); + return AIObject::DoCommand(tile, 0, 0, CMD_LANDSCAPE_CLEAR); +} + +/* static */ const char *AIBridge::GetName(BridgeID bridge_id) +{ + if (!IsValidBridge(bridge_id)) return NULL; + + static const int len = 64; + char *bridge_name = MallocT<char>(len); + + ::GetString(bridge_name, ::GetBridgeSpec(bridge_id)->transport_name[0], &bridge_name[len - 1]); + return bridge_name; +} + +/* static */ int32 AIBridge::GetMaxSpeed(BridgeID bridge_id) +{ + if (!IsValidBridge(bridge_id)) return -1; + + return ::GetBridgeSpec(bridge_id)->speed; +} + +/* static */ Money AIBridge::GetPrice(BridgeID bridge_id, uint length) +{ + if (!IsValidBridge(bridge_id)) return -1; + + return length * _price.build_bridge * ::GetBridgeSpec(bridge_id)->price >> 8; +} + +/* static */ int32 AIBridge::GetMaxLength(BridgeID bridge_id) +{ + if (!IsValidBridge(bridge_id)) return -1; + + uint max = ::GetBridgeSpec(bridge_id)->max_length; + if (max >= 16 && _settings_game.construction.longbridges) max = 100; + return max + 2; +} + +/* static */ int32 AIBridge::GetMinLength(BridgeID bridge_id) +{ + if (!IsValidBridge(bridge_id)) return -1; + + return ::GetBridgeSpec(bridge_id)->min_length + 2; +} + +/* static */ int32 AIBridge::GetYearAvailable(BridgeID bridge_id) +{ + if (!IsValidBridge(bridge_id)) return -1; + + return ::GetBridgeSpec(bridge_id)->avail_year; +} + +/* static */ TileIndex AIBridge::GetOtherBridgeEnd(TileIndex tile) +{ + if (!::IsValidTile(tile)) return INVALID_TILE; + if (!IsBridgeTile(tile)) return INVALID_TILE; + + return ::GetOtherBridgeEnd(tile); +} diff --git a/src/ai/api/ai_bridge.hpp b/src/ai/api/ai_bridge.hpp new file mode 100644 index 000000000..7bd637277 --- /dev/null +++ b/src/ai/api/ai_bridge.hpp @@ -0,0 +1,164 @@ +/* $Id$ */ + +/** @file ai_bridge.hpp Everything to query and build bridges. */ + +#ifndef AI_BRIDGE_HPP +#define AI_BRIDGE_HPP + +#include "ai_object.hpp" +#include "ai_vehicle.hpp" +#include "ai_error.hpp" + +/** + * Class that handles all bridge related functions. + */ +class AIBridge : public AIObject { +public: + /** + * All bridge related error messages. + */ + enum ErrorMessages { + /** Base for bridge related errors */ + ERR_BRIDGE_BASE = AIError::ERR_CAT_BRIDGE << AIError::ERR_CAT_BIT_SIZE, + + /** + * The bridge you want to build is not available yet, + * or it is not available for the requested length. + */ + ERR_BRIDGE_TYPE_UNAVAILABLE, // [STR_5015_CAN_T_BUILD_BRIDGE_HERE] + + /** One (or more) of the bridge head(s) ends in water. */ + ERR_BRIDGE_CANNOT_END_IN_WATER, // [STR_02A0_ENDS_OF_BRIDGE_MUST_BOTH] + + /** The bride heads need to be on the same height */ + ERR_BRIDGE_HEADS_NOT_ON_SAME_HEIGHT, // [STR_BRIDGEHEADS_NOT_SAME_HEIGHT] + }; + + static const char *GetClassName() { return "AIBridge"; } + + /** + * Checks whether the given bridge type is valid. + * @param bridge_id The bridge to check. + * @return True if and only if the bridge type is valid. + */ + static bool IsValidBridge(BridgeID bridge_id); + + /** + * Checks whether the given tile is actually a bridge start or end tile. + * @param tile The tile to check. + * @pre AIMap::IsValidTile(tile). + * @return True if and only if the tile is the beginning or end of a bridge. + */ + static bool IsBridgeTile(TileIndex tile); + + /** + * Get the name of a bridge. + * @param bridge_id The bridge to get the name of. + * @pre IsValidBridge(bridge_id). + * @return The name the bridge has. + */ + static const char *GetName(BridgeID bridge_id); + + /** + * Get the maximum speed of a bridge (in km/h). + * @param bridge_id The bridge to get the maximum speed of. + * @pre IsValidBridge(bridge_id). + * @return The maximum speed the bridge has. + */ + static int32 GetMaxSpeed(BridgeID bridge_id); + + /** + * Get the new cost of a bridge. + * @param bridge_id The bridge to get the new cost of. + * @param length The length of the bridge. + * @pre IsValidBridge(bridge_id). + * @return The new cost the bridge has. + */ + static Money GetPrice(BridgeID bridge_id, uint length); + + /** + * Get the maximum length of a bridge. + * @param bridge_id The bridge to get the maximum length of. + * @pre IsValidBridge(bridge_id). + * @returns The maximum length the bridge has. + */ + static int32 GetMaxLength(BridgeID bridge_id); + + /** + * Get the minimum length of a bridge. + * @param bridge_id The bridge to get the minimum length of. + * @pre IsValidBridge(bridge_id). + * @returns The minimum length the bridge has. + */ + static int32 GetMinLength(BridgeID bridge_id); + + /** + * Get the year in which a bridge becomes available. + * @param bridge_id The bridge to get the year of availability of. + * @pre IsValidBridge(bridge_id). + * @returns The year of availability the bridge has. + * @note Years are like 2010, -10 (10 B.C.), 1950, .. + */ + static int32 GetYearAvailable(BridgeID bridge_id); + +#ifndef DOXYGEN_SKIP + /** + * Internal function to help BuildBridge in case of road. + */ + static bool _BuildBridgeRoad1(); + + /** + * Internal function to help BuildBridge in case of road. + */ + static bool _BuildBridgeRoad2(); +#endif + + /** + * Build a bridge from one tile to the other. + * As an extra for road, this functions builds two half-pieces of road on + * each end of the bridge, making it easier for you to connect it to your + * network. + * @param vehicle_type The vehicle-type of bridge to build. + * @param bridge_id The bridge-type to build. + * @param start Where to start the bridge. + * @param end Where to end the bridge. + * @pre AIMap::IsValidTile(start). + * @pre AIMap::IsValidTile(end). + * @pre 'start' and 'end' are in a straight line, i.e. + * AIMap::GetTileX(start) == AIMap::GetTileX(end) or + * AIMap::GetTileY(start) == AIMap::GetTileY(end). + * @pre vehicle_type == AIVehicle::VEHICLE_ROAD || (vehicle_type == AIVehicle::VEHICLE_RAIL && + * AIRail::IsRailTypeAvailable(AIRail::GetCurrentRailType())) || vehicle_type == AIVehicle::VEHICLE_WATER. + * @exception AIError::ERR_ALREADY_BUILT + * @exception AIError::ERR_AREA_NOT_CLEAR + * @exception AIError::ERR_LAND_SLOPED_WRONG + * @exception AIError::ERR_VEHICLE_IN_THE_WAY + * @exception AIBridge::ERR_BRIDGE_TYPE_UNAVAILABLE + * @exception AIBridge::ERR_BRIDGE_CANNOT_END_IN_WATER + * @exception AIBridge::ERR_BRIDGE_HEADS_NOT_ON_SAME_HEIGHT + * @return Whether the bridge has been/can be build or not. + * @note No matter if the road pieces were build or not, if building the + * bridge succeeded, this function returns true. + */ + static bool BuildBridge(AIVehicle::VehicleType vehicle_type, BridgeID bridge_id, TileIndex start, TileIndex end); + + /** + * Removes a bridge, by executing it on either the start or end tile. + * @param tile An end or start tile of the bridge. + * @pre AIMap::IsValidTile(tile). + * @exception AIError::ERR_OWNED_BY_ANOTHER_COMPANY + * @return Whether the bridge has been/can be removed or not. + */ + static bool RemoveBridge(TileIndex tile); + + /** + * Get the tile that is on the other end of a bridge starting at tile. + * @param tile The tile that is an end of a bridge. + * @pre AIMap::IsValidTile(tile). + * @pre IsBridgeTile(tile). + * @return The TileIndex that is the other end of the bridge. + */ + static TileIndex GetOtherBridgeEnd(TileIndex tile); +}; + +#endif /* AI_BRIDGE_HPP */ diff --git a/src/ai/api/ai_bridge.hpp.sq b/src/ai/api/ai_bridge.hpp.sq new file mode 100644 index 000000000..6388d82cf --- /dev/null +++ b/src/ai/api/ai_bridge.hpp.sq @@ -0,0 +1,51 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_bridge.hpp" + +namespace SQConvert { + /* Allow enums to be used as Squirrel parameters */ + template <> AIBridge::ErrorMessages GetParam(ForceType<AIBridge::ErrorMessages>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AIBridge::ErrorMessages)tmp; } + template <> int Return<AIBridge::ErrorMessages>(HSQUIRRELVM vm, AIBridge::ErrorMessages res) { sq_pushinteger(vm, (int32)res); return 1; } + + /* Allow AIBridge to be used as Squirrel parameter */ + template <> AIBridge *GetParam(ForceType<AIBridge *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIBridge *)instance; } + template <> AIBridge &GetParam(ForceType<AIBridge &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIBridge *)instance; } + template <> const AIBridge *GetParam(ForceType<const AIBridge *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIBridge *)instance; } + template <> const AIBridge &GetParam(ForceType<const AIBridge &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIBridge *)instance; } + template <> int Return<AIBridge *>(HSQUIRRELVM vm, AIBridge *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIBridge", res, NULL, DefSQDestructorCallback<AIBridge>); return 1; } +}; // namespace SQConvert + +void SQAIBridge_Register(Squirrel *engine) { + DefSQClass <AIBridge> SQAIBridge("AIBridge"); + SQAIBridge.PreRegister(engine); + SQAIBridge.AddConstructor<void (AIBridge::*)(), 1>(engine, "x"); + + SQAIBridge.DefSQConst(engine, AIBridge::ERR_BRIDGE_BASE, "ERR_BRIDGE_BASE"); + SQAIBridge.DefSQConst(engine, AIBridge::ERR_BRIDGE_TYPE_UNAVAILABLE, "ERR_BRIDGE_TYPE_UNAVAILABLE"); + SQAIBridge.DefSQConst(engine, AIBridge::ERR_BRIDGE_CANNOT_END_IN_WATER, "ERR_BRIDGE_CANNOT_END_IN_WATER"); + SQAIBridge.DefSQConst(engine, AIBridge::ERR_BRIDGE_HEADS_NOT_ON_SAME_HEIGHT, "ERR_BRIDGE_HEADS_NOT_ON_SAME_HEIGHT"); + + AIError::RegisterErrorMap(STR_5015_CAN_T_BUILD_BRIDGE_HERE, AIBridge::ERR_BRIDGE_TYPE_UNAVAILABLE); + AIError::RegisterErrorMap(STR_02A0_ENDS_OF_BRIDGE_MUST_BOTH, AIBridge::ERR_BRIDGE_CANNOT_END_IN_WATER); + AIError::RegisterErrorMap(STR_BRIDGEHEADS_NOT_SAME_HEIGHT, AIBridge::ERR_BRIDGE_HEADS_NOT_ON_SAME_HEIGHT); + + AIError::RegisterErrorMapString(AIBridge::ERR_BRIDGE_TYPE_UNAVAILABLE, "ERR_BRIDGE_TYPE_UNAVAILABLE"); + AIError::RegisterErrorMapString(AIBridge::ERR_BRIDGE_CANNOT_END_IN_WATER, "ERR_BRIDGE_CANNOT_END_IN_WATER"); + AIError::RegisterErrorMapString(AIBridge::ERR_BRIDGE_HEADS_NOT_ON_SAME_HEIGHT, "ERR_BRIDGE_HEADS_NOT_ON_SAME_HEIGHT"); + + SQAIBridge.DefSQStaticMethod(engine, &AIBridge::GetClassName, "GetClassName", 1, "x"); + SQAIBridge.DefSQStaticMethod(engine, &AIBridge::IsValidBridge, "IsValidBridge", 2, "xi"); + SQAIBridge.DefSQStaticMethod(engine, &AIBridge::IsBridgeTile, "IsBridgeTile", 2, "xi"); + SQAIBridge.DefSQStaticMethod(engine, &AIBridge::GetName, "GetName", 2, "xi"); + SQAIBridge.DefSQStaticMethod(engine, &AIBridge::GetMaxSpeed, "GetMaxSpeed", 2, "xi"); + SQAIBridge.DefSQStaticMethod(engine, &AIBridge::GetPrice, "GetPrice", 3, "xii"); + SQAIBridge.DefSQStaticMethod(engine, &AIBridge::GetMaxLength, "GetMaxLength", 2, "xi"); + SQAIBridge.DefSQStaticMethod(engine, &AIBridge::GetMinLength, "GetMinLength", 2, "xi"); + SQAIBridge.DefSQStaticMethod(engine, &AIBridge::GetYearAvailable, "GetYearAvailable", 2, "xi"); + SQAIBridge.DefSQStaticMethod(engine, &AIBridge::BuildBridge, "BuildBridge", 5, "xiiii"); + SQAIBridge.DefSQStaticMethod(engine, &AIBridge::RemoveBridge, "RemoveBridge", 2, "xi"); + SQAIBridge.DefSQStaticMethod(engine, &AIBridge::GetOtherBridgeEnd, "GetOtherBridgeEnd", 2, "xi"); + + SQAIBridge.PostRegister(engine); +} diff --git a/src/ai/api/ai_bridgelist.cpp b/src/ai/api/ai_bridgelist.cpp new file mode 100644 index 000000000..4f2fb2719 --- /dev/null +++ b/src/ai/api/ai_bridgelist.cpp @@ -0,0 +1,25 @@ +/* $Id$ */ + +/** @file ai_bridgelist.cpp Implementation of AIBridgeList and friends. */ + +#include "ai_bridgelist.hpp" +#include "ai_bridge.hpp" +#include "../../openttd.h" +#include "../../bridge.h" +#include "../../date_func.h" + +AIBridgeList::AIBridgeList() +{ + /* Add all bridges, no matter if they are available or not */ + for (byte j = 0; j < MAX_BRIDGES; j++) + if (::GetBridgeSpec(j)->avail_year <= _cur_year) + this->AddItem(j); +} + +AIBridgeList_Length::AIBridgeList_Length(uint length) +{ + for (byte j = 0; j < MAX_BRIDGES; j++) + if (::GetBridgeSpec(j)->avail_year <= _cur_year) + if (length >= (uint)AIBridge::GetMinLength(j) && length <= (uint)AIBridge::GetMaxLength(j)) + this->AddItem(j); +} diff --git a/src/ai/api/ai_bridgelist.hpp b/src/ai/api/ai_bridgelist.hpp new file mode 100644 index 000000000..b3ba39058 --- /dev/null +++ b/src/ai/api/ai_bridgelist.hpp @@ -0,0 +1,34 @@ +/* $Id$ */ + +/** @file ai_bridgelist.hpp List all the bridges. */ + +#ifndef AI_BRIDGELIST_HPP +#define AI_BRIDGELIST_HPP + +#include "ai_abstractlist.hpp" + +/** + * Create a list of bridges. + * @ingroup AIList + */ +class AIBridgeList : public AIAbstractList { +public: + static const char *GetClassName() { return "AIBridgeList"; } + AIBridgeList(); +}; + +/** + * Create a list of bridges that can be built on a specific length. + * @ingroup AIList + */ +class AIBridgeList_Length : public AIAbstractList { +public: + static const char *GetClassName() { return "AIBridgeList_Length"; } + + /** + * @param length The length of the bridge you want to build. + */ + AIBridgeList_Length(uint length); +}; + +#endif /* AI_BRIDGELIST_HPP */ diff --git a/src/ai/api/ai_bridgelist.hpp.sq b/src/ai/api/ai_bridgelist.hpp.sq new file mode 100644 index 000000000..f34b95518 --- /dev/null +++ b/src/ai/api/ai_bridgelist.hpp.sq @@ -0,0 +1,42 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_bridgelist.hpp" + +namespace SQConvert { + /* Allow AIBridgeList to be used as Squirrel parameter */ + template <> AIBridgeList *GetParam(ForceType<AIBridgeList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIBridgeList *)instance; } + template <> AIBridgeList &GetParam(ForceType<AIBridgeList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIBridgeList *)instance; } + template <> const AIBridgeList *GetParam(ForceType<const AIBridgeList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIBridgeList *)instance; } + template <> const AIBridgeList &GetParam(ForceType<const AIBridgeList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIBridgeList *)instance; } + template <> int Return<AIBridgeList *>(HSQUIRRELVM vm, AIBridgeList *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIBridgeList", res, NULL, DefSQDestructorCallback<AIBridgeList>); return 1; } +}; // namespace SQConvert + +void SQAIBridgeList_Register(Squirrel *engine) { + DefSQClass <AIBridgeList> SQAIBridgeList("AIBridgeList"); + SQAIBridgeList.PreRegister(engine, "AIAbstractList"); + SQAIBridgeList.AddConstructor<void (AIBridgeList::*)(), 1>(engine, "x"); + + SQAIBridgeList.DefSQStaticMethod(engine, &AIBridgeList::GetClassName, "GetClassName", 1, "x"); + + SQAIBridgeList.PostRegister(engine); +} + +namespace SQConvert { + /* Allow AIBridgeList_Length to be used as Squirrel parameter */ + template <> AIBridgeList_Length *GetParam(ForceType<AIBridgeList_Length *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIBridgeList_Length *)instance; } + template <> AIBridgeList_Length &GetParam(ForceType<AIBridgeList_Length &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIBridgeList_Length *)instance; } + template <> const AIBridgeList_Length *GetParam(ForceType<const AIBridgeList_Length *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIBridgeList_Length *)instance; } + template <> const AIBridgeList_Length &GetParam(ForceType<const AIBridgeList_Length &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIBridgeList_Length *)instance; } + template <> int Return<AIBridgeList_Length *>(HSQUIRRELVM vm, AIBridgeList_Length *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIBridgeList_Length", res, NULL, DefSQDestructorCallback<AIBridgeList_Length>); return 1; } +}; // namespace SQConvert + +void SQAIBridgeList_Length_Register(Squirrel *engine) { + DefSQClass <AIBridgeList_Length> SQAIBridgeList_Length("AIBridgeList_Length"); + SQAIBridgeList_Length.PreRegister(engine, "AIAbstractList"); + SQAIBridgeList_Length.AddConstructor<void (AIBridgeList_Length::*)(uint length), 2>(engine, "xi"); + + SQAIBridgeList_Length.DefSQStaticMethod(engine, &AIBridgeList_Length::GetClassName, "GetClassName", 1, "x"); + + SQAIBridgeList_Length.PostRegister(engine); +} diff --git a/src/ai/api/ai_cargo.cpp b/src/ai/api/ai_cargo.cpp new file mode 100644 index 000000000..6843f7134 --- /dev/null +++ b/src/ai/api/ai_cargo.cpp @@ -0,0 +1,57 @@ +/* $Id$ */ + +/** @file ai_cargo.cpp Implementation of AICargo. */ + +#include "ai_cargo.hpp" +#include "../../openttd.h" +#include "../../cargotype.h" +#include "../../economy_func.h" +#include "../../core/alloc_func.hpp" +#include "../../core/bitmath_func.hpp" +#include "../../newgrf_cargo.h" + +/* static */ bool AICargo::IsValidCargo(CargoID cargo_type) +{ + return (cargo_type < NUM_CARGO && ::GetCargo(cargo_type)->IsValid()); +} + +/* static */ const char *AICargo::GetCargoLabel(CargoID cargo_type) +{ + if (!IsValidCargo(cargo_type)) return NULL; + const CargoSpec *cargo = ::GetCargo(cargo_type); + + /* cargo->label is a uint32 packing a 4 character non-terminated string, + * like "PASS", "COAL", "OIL_". New ones can be defined by NewGRFs */ + char *cargo_label = MallocT<char>(sizeof(cargo->label) + 1); + for (uint i = 0; i < sizeof(cargo->label); i++) { + cargo_label[i] = GB(cargo->label, (uint8)(sizeof(cargo->label) - i - 1) * 8, 8); + } + cargo_label[sizeof(cargo->label)] = '\0'; + return cargo_label; +} + +/* static */ bool AICargo::IsFreight(CargoID cargo_type) +{ + if (!IsValidCargo(cargo_type)) return false; + const CargoSpec *cargo = ::GetCargo(cargo_type); + return cargo->is_freight; +} + +/* static */ bool AICargo::HasCargoClass(CargoID cargo_type, CargoClass cargo_class) +{ + if (!IsValidCargo(cargo_type)) return false; + return ::IsCargoInClass(cargo_type, (::CargoClass)cargo_class); +} + +/* static */ AICargo::TownEffect AICargo::GetTownEffect(CargoID cargo_type) +{ + if (!IsValidCargo(cargo_type)) return TE_NONE; + + return (AICargo::TownEffect)GetCargo(cargo_type)->town_effect; +} + +/* static */ Money AICargo::GetCargoIncome(CargoID cargo_type, uint32 distance, uint32 days_in_transit) +{ + if (!IsValidCargo(cargo_type)) return -1; + return ::GetTransportedGoodsIncome(1, distance, Clamp(days_in_transit * 2 / 5, 0, 255), cargo_type); +} diff --git a/src/ai/api/ai_cargo.hpp b/src/ai/api/ai_cargo.hpp new file mode 100644 index 000000000..bde235758 --- /dev/null +++ b/src/ai/api/ai_cargo.hpp @@ -0,0 +1,94 @@ +/* $Id$ */ + +/** @file ai_cargo.hpp Everything to query cargos. */ + +#ifndef AI_CARGO_HPP +#define AI_CARGO_HPP + +#include "ai_object.hpp" + +/** + * Class that handles all cargo related functions. + */ +class AICargo : public AIObject { +public: + static const char *GetClassName() { return "AICargo"; } + + /** + * The classes of cargo (from newgrf_cargo.h). + */ + enum CargoClass { + CC_PASSENGERS = 1 << 0, ///< Passengers + CC_MAIL = 1 << 1, ///< Mail + CC_EXPRESS = 1 << 2, ///< Express cargo (Goods, Food, Candy, but also possible for passengers) + CC_ARMOURED = 1 << 3, ///< Armoured cargo (Valuables, Gold, Diamonds) + CC_BULK = 1 << 4, ///< Bulk cargo (Coal, Grain etc., Ores, Fruit) + CC_PIECE_GOODS = 1 << 5, ///< Piece goods (Livestock, Wood, Steel, Paper) + CC_LIQUID = 1 << 6, ///< Liquids (Oil, Water, Rubber) + CC_REFRIGERATED = 1 << 7, ///< Refrigerated cargo (Food, Fruit) + CC_HAZARDOUS = 1 << 8, ///< Hazardous cargo (Nuclear Fuel, Explosives, etc.) + CC_COVERED = 1 << 9, ///< Covered/Sheltered Freight (Transporation in Box Vans, Silo Wagons, etc.) + }; + + /** + * The effects a cargo can have on a town. + */ + enum TownEffect { + TE_NONE = 0, ///< This cargo has no effect on a town + TE_PASSENGERS = 1, ///< This cargo supplies passengers to a town + TE_MAIL = 2, ///< This cargo supplies mail to a town + TE_GOODS = 3, ///< This cargo supplies goods to a town + TE_WATER = 4, ///< This cargo supplies water to a town + TE_FOOD = 5, ///< This cargo supplies food to a town + }; + + /** + * Checks whether the given cargo type is valid. + * @param cargo_type The cargo to check. + * @return True if and only if the cargo type is valid. + */ + static bool IsValidCargo(CargoID cargo_type); + + /** + * Gets the string representation of the cargo label. + * @param cargo_type The cargo to get the string representation of. + * @return The cargo label. + * @note Never use this to check if it is a certain cargo. NewGRF can + * redefine all of the names. + */ + static const char *GetCargoLabel(CargoID cargo_type); + + /** + * Checks whether the give cargo is a freight or not. + * @param cargo_type The cargo to check on. + * @return True if and only if the cargo is freight. + */ + static bool IsFreight(CargoID cargo_type); + + /** + * Check if this cargo is in the requested cargo class. + * @param cargo_type The cargo to check on. + * @param cargo_class The class to check for. + * @return True if and only if the cargo is in the cargo class. + */ + static bool HasCargoClass(CargoID cargo_type, CargoClass cargo_class); + + /** + * Get the effect this cargo has on a town. + * @param cargo_type The cargo to check on. + * @return The effect this cargo has on a town, or TE_NONE if it has no effect. + */ + static TownEffect GetTownEffect(CargoID cargo_type); + + /** + * Get the income for transporting a piece of cargo over the + * given distance within the specified time. + * @param cargo_type The cargo to transport. + * @param distance The distance the cargo travels from begin to end. + * @param days_in_transit Amount of (game) days the cargo is in transit. The max value of this variable is 637. Any value higher returns the same as 637 would. + * @return The amount of money that would be earned by this trip. + */ + static Money GetCargoIncome(CargoID cargo_type, uint32 distance, uint32 days_in_transit); +}; + +#endif /* AI_CARGO_HPP */ diff --git a/src/ai/api/ai_cargo.hpp.sq b/src/ai/api/ai_cargo.hpp.sq new file mode 100644 index 000000000..015d112b7 --- /dev/null +++ b/src/ai/api/ai_cargo.hpp.sq @@ -0,0 +1,52 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_cargo.hpp" + +namespace SQConvert { + /* Allow enums to be used as Squirrel parameters */ + template <> AICargo::CargoClass GetParam(ForceType<AICargo::CargoClass>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AICargo::CargoClass)tmp; } + template <> int Return<AICargo::CargoClass>(HSQUIRRELVM vm, AICargo::CargoClass res) { sq_pushinteger(vm, (int32)res); return 1; } + template <> AICargo::TownEffect GetParam(ForceType<AICargo::TownEffect>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AICargo::TownEffect)tmp; } + template <> int Return<AICargo::TownEffect>(HSQUIRRELVM vm, AICargo::TownEffect res) { sq_pushinteger(vm, (int32)res); return 1; } + + /* Allow AICargo to be used as Squirrel parameter */ + template <> AICargo *GetParam(ForceType<AICargo *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AICargo *)instance; } + template <> AICargo &GetParam(ForceType<AICargo &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AICargo *)instance; } + template <> const AICargo *GetParam(ForceType<const AICargo *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AICargo *)instance; } + template <> const AICargo &GetParam(ForceType<const AICargo &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AICargo *)instance; } + template <> int Return<AICargo *>(HSQUIRRELVM vm, AICargo *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AICargo", res, NULL, DefSQDestructorCallback<AICargo>); return 1; } +}; // namespace SQConvert + +void SQAICargo_Register(Squirrel *engine) { + DefSQClass <AICargo> SQAICargo("AICargo"); + SQAICargo.PreRegister(engine); + SQAICargo.AddConstructor<void (AICargo::*)(), 1>(engine, "x"); + + SQAICargo.DefSQConst(engine, AICargo::CC_PASSENGERS, "CC_PASSENGERS"); + SQAICargo.DefSQConst(engine, AICargo::CC_MAIL, "CC_MAIL"); + SQAICargo.DefSQConst(engine, AICargo::CC_EXPRESS, "CC_EXPRESS"); + SQAICargo.DefSQConst(engine, AICargo::CC_ARMOURED, "CC_ARMOURED"); + SQAICargo.DefSQConst(engine, AICargo::CC_BULK, "CC_BULK"); + SQAICargo.DefSQConst(engine, AICargo::CC_PIECE_GOODS, "CC_PIECE_GOODS"); + SQAICargo.DefSQConst(engine, AICargo::CC_LIQUID, "CC_LIQUID"); + SQAICargo.DefSQConst(engine, AICargo::CC_REFRIGERATED, "CC_REFRIGERATED"); + SQAICargo.DefSQConst(engine, AICargo::CC_HAZARDOUS, "CC_HAZARDOUS"); + SQAICargo.DefSQConst(engine, AICargo::CC_COVERED, "CC_COVERED"); + SQAICargo.DefSQConst(engine, AICargo::TE_NONE, "TE_NONE"); + SQAICargo.DefSQConst(engine, AICargo::TE_PASSENGERS, "TE_PASSENGERS"); + SQAICargo.DefSQConst(engine, AICargo::TE_MAIL, "TE_MAIL"); + SQAICargo.DefSQConst(engine, AICargo::TE_GOODS, "TE_GOODS"); + SQAICargo.DefSQConst(engine, AICargo::TE_WATER, "TE_WATER"); + SQAICargo.DefSQConst(engine, AICargo::TE_FOOD, "TE_FOOD"); + + SQAICargo.DefSQStaticMethod(engine, &AICargo::GetClassName, "GetClassName", 1, "x"); + SQAICargo.DefSQStaticMethod(engine, &AICargo::IsValidCargo, "IsValidCargo", 2, "xi"); + SQAICargo.DefSQStaticMethod(engine, &AICargo::GetCargoLabel, "GetCargoLabel", 2, "xi"); + SQAICargo.DefSQStaticMethod(engine, &AICargo::IsFreight, "IsFreight", 2, "xi"); + SQAICargo.DefSQStaticMethod(engine, &AICargo::HasCargoClass, "HasCargoClass", 3, "xii"); + SQAICargo.DefSQStaticMethod(engine, &AICargo::GetTownEffect, "GetTownEffect", 2, "xi"); + SQAICargo.DefSQStaticMethod(engine, &AICargo::GetCargoIncome, "GetCargoIncome", 4, "xiii"); + + SQAICargo.PostRegister(engine); +} diff --git a/src/ai/api/ai_cargolist.cpp b/src/ai/api/ai_cargolist.cpp new file mode 100644 index 000000000..e480895fc --- /dev/null +++ b/src/ai/api/ai_cargolist.cpp @@ -0,0 +1,46 @@ +/* $Id$ */ + +/** @file ai_cargolist.cpp Implementation of AICargoList and friends. */ + +#include "ai_cargolist.hpp" +#include "ai_industry.hpp" +#include "../../openttd.h" +#include "../../cargotype.h" +#include "../../tile_type.h" +#include "../../industry.h" + +AICargoList::AICargoList() +{ + for (byte i = 0; i < NUM_CARGO; i++) { + const CargoSpec *c = ::GetCargo(i); + if (c->IsValid()) { + this->AddItem(i); + } + } +} + +AICargoList_IndustryAccepting::AICargoList_IndustryAccepting(IndustryID industry_id) +{ + if (!AIIndustry::IsValidIndustry(industry_id)) return; + + Industry *ind = ::GetIndustry(industry_id); + for (uint i = 0; i < lengthof(ind->accepts_cargo); i++) { + CargoID cargo_id = ind->accepts_cargo[i]; + if (cargo_id != CT_INVALID) { + this->AddItem(cargo_id); + } + } +} + +AICargoList_IndustryProducing::AICargoList_IndustryProducing(IndustryID industry_id) +{ + if (!AIIndustry::IsValidIndustry(industry_id)) return; + + Industry *ind = ::GetIndustry(industry_id); + for (uint i = 0; i < lengthof(ind->produced_cargo); i++) { + CargoID cargo_id = ind->produced_cargo[i]; + if (cargo_id != CT_INVALID) { + this->AddItem(cargo_id); + } + } +} diff --git a/src/ai/api/ai_cargolist.hpp b/src/ai/api/ai_cargolist.hpp new file mode 100644 index 000000000..86a99a393 --- /dev/null +++ b/src/ai/api/ai_cargolist.hpp @@ -0,0 +1,48 @@ +/* $Id$ */ + +/** @file ai_cargolist.hpp List all the cargos. */ + +#ifndef AI_CARGOLIST_HPP +#define AI_CARGOLIST_HPP + +#include "ai_abstractlist.hpp" + +/** + * Creates a list of cargos that can be produced in the current game. + * @ingroup AIList + */ +class AICargoList : public AIAbstractList { +public: + static const char *GetClassName() { return "AICargoList"; } + AICargoList(); +}; + +/** + * Creates a list of cargos that the given industry accepts. + * @ingroup AIList + */ +class AICargoList_IndustryAccepting : public AIAbstractList { +public: + static const char *GetClassName() { return "AICargoList_IndustryAccepting"; } + + /** + * @param industry_id The industry to get the list of cargos it accepts from. + */ + AICargoList_IndustryAccepting(IndustryID industry_id); +}; + +/** + * Creates a list of cargos that the given industry can produce. + * @ingroup AIList + */ +class AICargoList_IndustryProducing : public AIAbstractList { +public: + static const char *GetClassName() { return "AICargoList_IndustryProducing"; } + + /** + * @param industry_id The industry to get the list of cargos it produces from. + */ + AICargoList_IndustryProducing(IndustryID industry_id); +}; + +#endif /* AI_CARGOLIST_HPP */ diff --git a/src/ai/api/ai_cargolist.hpp.sq b/src/ai/api/ai_cargolist.hpp.sq new file mode 100644 index 000000000..379afed9b --- /dev/null +++ b/src/ai/api/ai_cargolist.hpp.sq @@ -0,0 +1,61 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_cargolist.hpp" + +namespace SQConvert { + /* Allow AICargoList to be used as Squirrel parameter */ + template <> AICargoList *GetParam(ForceType<AICargoList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AICargoList *)instance; } + template <> AICargoList &GetParam(ForceType<AICargoList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AICargoList *)instance; } + template <> const AICargoList *GetParam(ForceType<const AICargoList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AICargoList *)instance; } + template <> const AICargoList &GetParam(ForceType<const AICargoList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AICargoList *)instance; } + template <> int Return<AICargoList *>(HSQUIRRELVM vm, AICargoList *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AICargoList", res, NULL, DefSQDestructorCallback<AICargoList>); return 1; } +}; // namespace SQConvert + +void SQAICargoList_Register(Squirrel *engine) { + DefSQClass <AICargoList> SQAICargoList("AICargoList"); + SQAICargoList.PreRegister(engine, "AIAbstractList"); + SQAICargoList.AddConstructor<void (AICargoList::*)(), 1>(engine, "x"); + + SQAICargoList.DefSQStaticMethod(engine, &AICargoList::GetClassName, "GetClassName", 1, "x"); + + SQAICargoList.PostRegister(engine); +} + +namespace SQConvert { + /* Allow AICargoList_IndustryAccepting to be used as Squirrel parameter */ + template <> AICargoList_IndustryAccepting *GetParam(ForceType<AICargoList_IndustryAccepting *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AICargoList_IndustryAccepting *)instance; } + template <> AICargoList_IndustryAccepting &GetParam(ForceType<AICargoList_IndustryAccepting &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AICargoList_IndustryAccepting *)instance; } + template <> const AICargoList_IndustryAccepting *GetParam(ForceType<const AICargoList_IndustryAccepting *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AICargoList_IndustryAccepting *)instance; } + template <> const AICargoList_IndustryAccepting &GetParam(ForceType<const AICargoList_IndustryAccepting &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AICargoList_IndustryAccepting *)instance; } + template <> int Return<AICargoList_IndustryAccepting *>(HSQUIRRELVM vm, AICargoList_IndustryAccepting *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AICargoList_IndustryAccepting", res, NULL, DefSQDestructorCallback<AICargoList_IndustryAccepting>); return 1; } +}; // namespace SQConvert + +void SQAICargoList_IndustryAccepting_Register(Squirrel *engine) { + DefSQClass <AICargoList_IndustryAccepting> SQAICargoList_IndustryAccepting("AICargoList_IndustryAccepting"); + SQAICargoList_IndustryAccepting.PreRegister(engine, "AIAbstractList"); + SQAICargoList_IndustryAccepting.AddConstructor<void (AICargoList_IndustryAccepting::*)(IndustryID industry_id), 2>(engine, "xi"); + + SQAICargoList_IndustryAccepting.DefSQStaticMethod(engine, &AICargoList_IndustryAccepting::GetClassName, "GetClassName", 1, "x"); + + SQAICargoList_IndustryAccepting.PostRegister(engine); +} + +namespace SQConvert { + /* Allow AICargoList_IndustryProducing to be used as Squirrel parameter */ + template <> AICargoList_IndustryProducing *GetParam(ForceType<AICargoList_IndustryProducing *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AICargoList_IndustryProducing *)instance; } + template <> AICargoList_IndustryProducing &GetParam(ForceType<AICargoList_IndustryProducing &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AICargoList_IndustryProducing *)instance; } + template <> const AICargoList_IndustryProducing *GetParam(ForceType<const AICargoList_IndustryProducing *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AICargoList_IndustryProducing *)instance; } + template <> const AICargoList_IndustryProducing &GetParam(ForceType<const AICargoList_IndustryProducing &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AICargoList_IndustryProducing *)instance; } + template <> int Return<AICargoList_IndustryProducing *>(HSQUIRRELVM vm, AICargoList_IndustryProducing *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AICargoList_IndustryProducing", res, NULL, DefSQDestructorCallback<AICargoList_IndustryProducing>); return 1; } +}; // namespace SQConvert + +void SQAICargoList_IndustryProducing_Register(Squirrel *engine) { + DefSQClass <AICargoList_IndustryProducing> SQAICargoList_IndustryProducing("AICargoList_IndustryProducing"); + SQAICargoList_IndustryProducing.PreRegister(engine, "AIAbstractList"); + SQAICargoList_IndustryProducing.AddConstructor<void (AICargoList_IndustryProducing::*)(IndustryID industry_id), 2>(engine, "xi"); + + SQAICargoList_IndustryProducing.DefSQStaticMethod(engine, &AICargoList_IndustryProducing::GetClassName, "GetClassName", 1, "x"); + + SQAICargoList_IndustryProducing.PostRegister(engine); +} diff --git a/src/ai/api/ai_company.cpp b/src/ai/api/ai_company.cpp new file mode 100644 index 000000000..9a426bd2b --- /dev/null +++ b/src/ai/api/ai_company.cpp @@ -0,0 +1,200 @@ +/* $Id$ */ + +/** @file ai_company.cpp Implementation of AICompany. */ + +#include "ai_company.hpp" +#include "ai_error.hpp" +#include "ai_log.hpp" +#include "../../openttd.h" +#include "../../command_func.h" +#include "../../company_func.h" +#include "../../company_base.h" +#include "../../economy_func.h" +#include "../../strings_func.h" +#include "../../tile_map.h" +#include "../../variables.h" +#include "../../core/alloc_func.hpp" +#include "../../string_func.h" +#include "table/strings.h" + +/* static */ AICompany::CompanyID AICompany::ResolveCompanyID(AICompany::CompanyID company) +{ + if (company == MY_COMPANY) return (CompanyID)((byte)_current_company); + + return ::IsValidCompanyID((::CompanyID)company) ? company : INVALID_COMPANY; +} + +/* static */ bool AICompany::IsMine(AICompany::CompanyID company) +{ + return ResolveCompanyID(company) == ResolveCompanyID(MY_COMPANY); +} + +/* static */ bool AICompany::SetCompanyName(const char *name) +{ + AILog::Error("AICompany::SetCompanyName is obsolete. Use AICompany::SetName instead."); + return AICompany::SetName(name); +} + +/* static */ bool AICompany::SetName(const char *name) +{ + EnforcePrecondition(false, !::StrEmpty(name)); + EnforcePreconditionCustomError(false, ::strlen(name) < MAX_LENGTH_COMPANY_NAME_BYTES, AIError::ERR_PRECONDITION_STRING_TOO_LONG); + + return AIObject::DoCommand(0, 0, 0, CMD_RENAME_COMPANY, name); +} + +/* static */ const char *AICompany::GetCompanyName(AICompany::CompanyID company) +{ + AILog::Error("AICompany::GetCompanyName is obsolete. Use AICompany::GetName instead."); + return AICompany::GetName(company); +} + +/* static */ const char *AICompany::GetName(AICompany::CompanyID company) +{ + company = ResolveCompanyID(company); + if (company == INVALID_COMPANY) return NULL; + + static const int len = 64; + char *company_name = MallocT<char>(len); + + ::SetDParam(0, company); + ::GetString(company_name, STR_COMPANY_NAME, &company_name[len - 1]); + return company_name; +} + +/* static */ bool AICompany::SetPresidentName(const char *name) +{ + EnforcePrecondition(false, !::StrEmpty(name)); + + return AIObject::DoCommand(0, 0, 0, CMD_RENAME_PRESIDENT, name); +} + +/* static */ const char *AICompany::GetPresidentName(AICompany::CompanyID company) +{ + company = ResolveCompanyID(company); + + static const int len = 64; + char *president_name = MallocT<char>(len); + if (company != INVALID_COMPANY) { + ::SetDParam(0, company); + ::GetString(president_name, STR_PRESIDENT_NAME, &president_name[len - 1]); + } else { + *president_name = '\0'; + } + + return president_name; +} + +/* static */ Money AICompany::GetCompanyValue(AICompany::CompanyID company) +{ + company = ResolveCompanyID(company); + if (company == INVALID_COMPANY) return -1; + + return ::CalculateCompanyValue(::GetCompany((CompanyID)company)); +} + +/* static */ Money AICompany::GetBankBalance(AICompany::CompanyID company) +{ + company = ResolveCompanyID(company); + if (company == INVALID_COMPANY) return -1; + + return ::GetCompany((CompanyID)company)->money; +} + +/* static */ Money AICompany::GetLoanAmount() +{ + return ::GetCompany(_current_company)->current_loan; +} + +/* static */ Money AICompany::GetMaxLoanAmount() +{ + return _economy.max_loan; +} + +/* static */ Money AICompany::GetLoanInterval() +{ + return LOAN_INTERVAL; +} + +/* static */ bool AICompany::SetLoanAmount(int32 loan) +{ + EnforcePrecondition(false, loan >= 0); + EnforcePrecondition(false, (loan % GetLoanInterval()) == 0); + EnforcePrecondition(false, loan <= GetMaxLoanAmount()); + EnforcePrecondition(false, (loan - GetLoanAmount() + GetBankBalance(MY_COMPANY)) >= 0); + + if (loan == GetLoanAmount()) return true; + + return AIObject::DoCommand(0, + abs(loan - GetLoanAmount()), 2, + (loan > GetLoanAmount()) ? CMD_INCREASE_LOAN : CMD_DECREASE_LOAN); +} + +/* static */ bool AICompany::SetMinimumLoanAmount(int32 loan) +{ + EnforcePrecondition(false, loan >= 0); + + int32 over_interval = loan % GetLoanInterval(); + if (over_interval != 0) loan += GetLoanInterval() - over_interval; + + EnforcePrecondition(false, loan <= GetMaxLoanAmount()); + + SetLoanAmount(loan); + + return GetLoanAmount() == loan; +} + +/* static */ bool AICompany::BuildCompanyHQ(TileIndex tile) +{ + EnforcePrecondition(false, ::IsValidTile(tile)); + + return AIObject::DoCommand(tile, 0, 0, CMD_BUILD_COMPANY_HQ); +} + +/* static */ TileIndex AICompany::GetCompanyHQ(CompanyID company) +{ + company = ResolveCompanyID(company); + if (company == INVALID_COMPANY) return INVALID_TILE; + + TileIndex loc = ::GetCompany((CompanyID)company)->location_of_HQ; + return (loc == 0) ? INVALID_TILE : loc; +} + +/* static */ bool AICompany::SetAutoRenewStatus(bool autorenew) +{ + return AIObject::DoCommand(0, 0, autorenew ? 1 : 0, CMD_SET_AUTOREPLACE); +} + +/* static */ bool AICompany::GetAutoRenewStatus(CompanyID company) +{ + company = ResolveCompanyID(company); + if (company == INVALID_COMPANY) return false; + + return ::GetCompany((CompanyID)company)->engine_renew; +} + +/* static */ bool AICompany::SetAutoRenewMonths(int16 months) +{ + return AIObject::DoCommand(0, 1, months, CMD_SET_AUTOREPLACE); +} + +/* static */ int16 AICompany::GetAutoRenewMonths(CompanyID company) +{ + company = ResolveCompanyID(company); + if (company == INVALID_COMPANY) return 0; + + return ::GetCompany((CompanyID)company)->engine_renew_months; +} + +/* static */ bool AICompany::SetAutoRenewMoney(uint32 money) +{ + return AIObject::DoCommand(0, 2, money, CMD_SET_AUTOREPLACE); +} + +/* static */ uint32 AICompany::GetAutoRenewMoney(CompanyID company) +{ + company = ResolveCompanyID(company); + if (company == INVALID_COMPANY) return 0; + + return ::GetCompany((CompanyID)company)->engine_renew_money; +} diff --git a/src/ai/api/ai_company.hpp b/src/ai/api/ai_company.hpp new file mode 100644 index 000000000..2497e43b7 --- /dev/null +++ b/src/ai/api/ai_company.hpp @@ -0,0 +1,214 @@ +/* $Id$ */ + +/** @file ai_company.hpp Everything to query a company's financials and statistics or build company related buildings. */ + +#ifndef AI_COMPANY_HPP +#define AI_COMPANY_HPP + +#include "ai_object.hpp" + +/** + * Class that handles all company related functions. + */ +class AICompany : public AIObject { +public: + static const char *GetClassName() { return "AICompany"; } + + /** Different constants related to CompanyID. */ + enum CompanyID { + INVALID_COMPANY = -1, //!< An invalid company. + FIRST_COMPANY = 0, //!< The first available company. + LAST_COMPANY = 7, //!< The last available company. + MY_COMPANY = 8, //!< Constant that gets resolved to the correct company index for your company. + }; + + /** + * Resolved the given company index to the correct index for the company. If + * the company index was MY_COMPANY it will be resolved to the index of + * your company. If the company with the given index does not exist it will + * return INVALID_COMPANY. + * @param company The company index to resolve. + * @return The resolved company index. + */ + static CompanyID ResolveCompanyID(CompanyID company); + + /** + * Check if a CompanyID is your CompanyID, to ease up checks. + * @param company The company index to check. + * @return True if and only if this company is your CompanyID. + */ + static bool IsMine(CompanyID company); + + /** + * Obsolete, use AICompany::SetName instead. + */ + static bool SetCompanyName(const char *name); + + /** + * Set the name of your company. + * @param name The new name of the company. + * @pre 'name' must have at least one character. + * @pre 'name' must have at most 30 characters. + * @exception AIError::ERR_NAME_IS_NOT_UNIQUE + * @return True if the name was changed. + */ + static bool SetName(const char *name); + + /** + * Obsolete, use AICompany::GetName instead. + */ + static const char *GetCompanyName(CompanyID company); + + /** + * Get the name of the given company. + * @param company The company to get the name for. + * @pre ResolveCompanyID(company) != INVALID_COMPANY + * @return The name of the given company. + */ + static const char *GetName(CompanyID company); + + /** + * Set the name of your president. + * @param name The new name of the president. + * @pre 'name' must have at least one character. + * @exception AIError::ERR_NAME_IS_NOT_UNIQUE + * @return True if the name was changed. + */ + static bool SetPresidentName(const char *name); + + /** + * Get the name of the president of the given company. + * @param company The company to get the president's name for. + * @pre ResolveCompanyID(company) != INVALID_COMPANY + * @return The name of the president of the given company. + */ + static const char *GetPresidentName(CompanyID company); + + /** + * Sets the amount to loan. + * @param loan The amount to loan (multiplier of GetLoanInterval()). + * @pre 'loan' must be non-negative. + * @pre GetLoanInterval() must be a multiplier of GetLoanInterval(). + * @pre 'loan' must be below GetMaxLoanAmount(). + * @pre 'loan' - GetLoanAmount() + GetBankBalance() must be non-negative. + * @return True if the loan could be set to your requested amount. + */ + static bool SetLoanAmount(int32 loan); + + /** + * Sets the minimum amount to loan, i.e. the given amount of loan rounded up. + * @param loan The amount to loan (any positive number). + * @pre 'loan' must be non-negative. + * @pre 'loan' must be below GetMaxLoanAmount(). + * @return True if we could allocate a minimum of "loan" loan. + */ + static bool SetMinimumLoanAmount(int32 loan); + + /** + * Gets the amount your company have loaned. + * @return The amount loaned money. + * @post The return value is always non-negative. + * @post GetLoanInterval() is always a multiplier of the return value. + */ + static Money GetLoanAmount(); + + /** + * Gets the maximum amount your company can loan. + * @return The maximum amount your company can loan. + * @post The return value is always non-negative. + * @post GetLoanInterval() is always a multiplier of the return value. + */ + static Money GetMaxLoanAmount(); + + /** + * Gets the interval/loan step. + * @return The loan step. + * @post Return value is always positive. + */ + static Money GetLoanInterval(); + + /** + * Gets the current value of the given company. + * @param company The company to get the company value of. + * @pre ResolveCompanyID(company) != INVALID_COMPANY + * @return The current value of the given company. + */ + static Money GetCompanyValue(CompanyID company); + + /** + * Gets the bank balance. In other words, the amount of money the given company can spent. + * @param company The company to get the bank balance of. + * @pre ResolveCompanyID(company) != INVALID_COMPANY + * @return The actual bank balance. + */ + static Money GetBankBalance(CompanyID company); + + /** + * Build your company's HQ on the given tile. + * @param tile The tile to build your HQ on, this tile is the most nothern tile of your HQ. + * @pre AIMap::IsValidTile(tile). + * @exception AIError::ERR_AREA_NOT_CLEAR + * @exception AIError::ERR_FLAT_LAND_REQUIRED + * @return True if the HQ could be build. + * @note An HQ can not be removed, only by water or rebuilding; If an HQ is + * build again, the old one is removed. + */ + static bool BuildCompanyHQ(TileIndex tile); + + /** + * Return the location of a company's HQ. + * @param company The company the get the HQ of. + * @pre ResolveCompanyID(company) != INVALID_COMPANY. + * @return The tile of the company's HQ, this tile is the most nothern tile of that HQ, or INVALID_TILE if there is no HQ yet. + */ + static TileIndex GetCompanyHQ(CompanyID company); + + /** + * Set whether autorenew is enabled for your company. + * @param autorenew The new autorenew status. + * @return True if autorenew status has been modified. + */ + static bool SetAutoRenewStatus(bool autorenew); + + /** + * Return whether autorenew is enabled for a company. + * @param company The company to get the autorenew status of. + * @pre ResolveCompanyID(company) != INVALID_COMPANY. + * @return True if autorenew is enabled. + */ + static bool GetAutoRenewStatus(CompanyID company); + + /** + * Set the number of months before/after max age to autorenew an engine for your company. + * @param months The new months between autorenew. + * @return True if autorenew months has been modified. + */ + static bool SetAutoRenewMonths(int16 months); + + /** + * Return the number of months before/after max age to autorenew an engine for a company. + * @param company The company to get the autorenew months of. + * @pre ResolveCompanyID(company) != INVALID_COMPANY. + * @return The months before/after max age of engine. + */ + static int16 GetAutoRenewMonths(CompanyID company); + + /** + * Set the minimum money needed to autorenew an engine for your company. + * @param money The new minimum required money for autorenew to work. + * @return True if autorenew money has been modified. + */ + static bool SetAutoRenewMoney(uint32 money); + + /** + * Return the minimum money needed to autorenew an engine for a company. + * @param company The company to get the autorenew money of. + * @pre ResolveCompanyID(company) != INVALID_COMPANY. + * @return The minimum required money for autorenew to work. + */ + static uint32 GetAutoRenewMoney(CompanyID company); +}; + +DECLARE_POSTFIX_INCREMENT(AICompany::CompanyID); + +#endif /* AI_COMPANY_HPP */ diff --git a/src/ai/api/ai_company.hpp.sq b/src/ai/api/ai_company.hpp.sq new file mode 100644 index 000000000..0f0bb5ccf --- /dev/null +++ b/src/ai/api/ai_company.hpp.sq @@ -0,0 +1,55 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_company.hpp" + +namespace SQConvert { + /* Allow enums to be used as Squirrel parameters */ + template <> AICompany::CompanyID GetParam(ForceType<AICompany::CompanyID>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AICompany::CompanyID)tmp; } + template <> int Return<AICompany::CompanyID>(HSQUIRRELVM vm, AICompany::CompanyID res) { sq_pushinteger(vm, (int32)res); return 1; } + + /* Allow AICompany to be used as Squirrel parameter */ + template <> AICompany *GetParam(ForceType<AICompany *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AICompany *)instance; } + template <> AICompany &GetParam(ForceType<AICompany &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AICompany *)instance; } + template <> const AICompany *GetParam(ForceType<const AICompany *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AICompany *)instance; } + template <> const AICompany &GetParam(ForceType<const AICompany &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AICompany *)instance; } + template <> int Return<AICompany *>(HSQUIRRELVM vm, AICompany *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AICompany", res, NULL, DefSQDestructorCallback<AICompany>); return 1; } +}; // namespace SQConvert + +void SQAICompany_Register(Squirrel *engine) { + DefSQClass <AICompany> SQAICompany("AICompany"); + SQAICompany.PreRegister(engine); + SQAICompany.AddConstructor<void (AICompany::*)(), 1>(engine, "x"); + + SQAICompany.DefSQConst(engine, AICompany::INVALID_COMPANY, "INVALID_COMPANY"); + SQAICompany.DefSQConst(engine, AICompany::FIRST_COMPANY, "FIRST_COMPANY"); + SQAICompany.DefSQConst(engine, AICompany::LAST_COMPANY, "LAST_COMPANY"); + SQAICompany.DefSQConst(engine, AICompany::MY_COMPANY, "MY_COMPANY"); + + SQAICompany.DefSQStaticMethod(engine, &AICompany::GetClassName, "GetClassName", 1, "x"); + SQAICompany.DefSQStaticMethod(engine, &AICompany::ResolveCompanyID, "ResolveCompanyID", 2, "xi"); + SQAICompany.DefSQStaticMethod(engine, &AICompany::IsMine, "IsMine", 2, "xi"); + SQAICompany.DefSQStaticMethod(engine, &AICompany::SetCompanyName, "SetCompanyName", 2, "xs"); + SQAICompany.DefSQStaticMethod(engine, &AICompany::SetName, "SetName", 2, "xs"); + SQAICompany.DefSQStaticMethod(engine, &AICompany::GetCompanyName, "GetCompanyName", 2, "xi"); + SQAICompany.DefSQStaticMethod(engine, &AICompany::GetName, "GetName", 2, "xi"); + SQAICompany.DefSQStaticMethod(engine, &AICompany::SetPresidentName, "SetPresidentName", 2, "xs"); + SQAICompany.DefSQStaticMethod(engine, &AICompany::GetPresidentName, "GetPresidentName", 2, "xi"); + SQAICompany.DefSQStaticMethod(engine, &AICompany::SetLoanAmount, "SetLoanAmount", 2, "xi"); + SQAICompany.DefSQStaticMethod(engine, &AICompany::SetMinimumLoanAmount, "SetMinimumLoanAmount", 2, "xi"); + SQAICompany.DefSQStaticMethod(engine, &AICompany::GetLoanAmount, "GetLoanAmount", 1, "x"); + SQAICompany.DefSQStaticMethod(engine, &AICompany::GetMaxLoanAmount, "GetMaxLoanAmount", 1, "x"); + SQAICompany.DefSQStaticMethod(engine, &AICompany::GetLoanInterval, "GetLoanInterval", 1, "x"); + SQAICompany.DefSQStaticMethod(engine, &AICompany::GetCompanyValue, "GetCompanyValue", 2, "xi"); + SQAICompany.DefSQStaticMethod(engine, &AICompany::GetBankBalance, "GetBankBalance", 2, "xi"); + SQAICompany.DefSQStaticMethod(engine, &AICompany::BuildCompanyHQ, "BuildCompanyHQ", 2, "xi"); + SQAICompany.DefSQStaticMethod(engine, &AICompany::GetCompanyHQ, "GetCompanyHQ", 2, "xi"); + SQAICompany.DefSQStaticMethod(engine, &AICompany::SetAutoRenewStatus, "SetAutoRenewStatus", 2, "xb"); + SQAICompany.DefSQStaticMethod(engine, &AICompany::GetAutoRenewStatus, "GetAutoRenewStatus", 2, "xi"); + SQAICompany.DefSQStaticMethod(engine, &AICompany::SetAutoRenewMonths, "SetAutoRenewMonths", 2, "xi"); + SQAICompany.DefSQStaticMethod(engine, &AICompany::GetAutoRenewMonths, "GetAutoRenewMonths", 2, "xi"); + SQAICompany.DefSQStaticMethod(engine, &AICompany::SetAutoRenewMoney, "SetAutoRenewMoney", 2, "xi"); + SQAICompany.DefSQStaticMethod(engine, &AICompany::GetAutoRenewMoney, "GetAutoRenewMoney", 2, "xi"); + + SQAICompany.PostRegister(engine); +} diff --git a/src/ai/api/ai_controller.cpp b/src/ai/api/ai_controller.cpp new file mode 100644 index 000000000..cb1fb6819 --- /dev/null +++ b/src/ai/api/ai_controller.cpp @@ -0,0 +1,85 @@ +/* $Id$ */ + +/** @file ai_controller.cpp Implementation of AIControler. */ + +#include "../../stdafx.h" +#include "../../openttd.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 "../ai.hpp" +#include "ai_controller.hpp" +#include "../ai_info.hpp" +#include "../ai_storage.hpp" +#include "../ai_instance.hpp" +#include "../ai_config.hpp" +#include "ai_log.hpp" + +/* static */ void AIController::SetCommandDelay(int ticks) +{ + if (ticks <= 0) return; + AIObject::SetDoCommandDelay(ticks); +} + +/* static */ void AIController::Sleep(int ticks) +{ + if (ticks <= 0) { + AILog::Warning("Sleep() value should be > 0. Assuming value 1."); + ticks = 1; + } + + throw AI_VMSuspend(ticks, NULL); +} + +/* static */ void AIController::Print(bool error_msg, const char *message) +{ + AILog::Log(error_msg ? AILog::LOG_SQ_ERROR : AILog::LOG_SQ_INFO, message); +} + +AIController::AIController() : + ticks(0), + loaded_library_count(0) +{ +} + +AIController::~AIController() +{ + for (LoadedLibraryList::iterator iter = this->loaded_library.begin(); iter != this->loaded_library.end(); iter++) { + free((void *)(*iter).second); + free((void *)(*iter).first); + } + + this->loaded_library.clear(); +} + +uint AIController::GetTick() +{ + return this->ticks; +} + +int AIController::GetSetting(const char *name) +{ + return AIConfig::GetConfig(_current_company)->GetSetting(name); +} + +bool AIController::LoadedLibrary(const char *library_name, int *next_number, char *fake_class_name, int fake_class_name_len) +{ + LoadedLibraryList::iterator iter = this->loaded_library.find(library_name); + if (iter == this->loaded_library.end()) { + *next_number = ++this->loaded_library_count; + return false; + } + + ttd_strlcpy(fake_class_name, (*iter).second, fake_class_name_len); + return true; +} + +void AIController::AddLoadedLibrary(const char *library_name, const char *fake_class_name) +{ + this->loaded_library[strdup(library_name)] = strdup(fake_class_name); +} diff --git a/src/ai/api/ai_controller.hpp b/src/ai/api/ai_controller.hpp new file mode 100644 index 000000000..1f1df743e --- /dev/null +++ b/src/ai/api/ai_controller.hpp @@ -0,0 +1,117 @@ +/* $Id$ */ + +/** @file ai_controller.hpp The controller of the AI. */ + +#ifndef AI_CONTROLLER_HPP +#define AI_CONTROLLER_HPP + +#include <map> +#ifndef AI_HPP +struct ltstr { bool operator()(const char *s1, const char *s2) const { return strcmp(s1, s2) < 0; } }; +#endif /* AI_HPP */ + +/** + * The Controller, the class each AI should extend. It creates the AI, makes + * sure the logic kicks in correctly, and that GetTick() has a valid value. + */ +class AIController { + friend class AIScanner; + friend class AIInstance; + +public: + static const char *GetClassName() { return "AIController"; } + + /** + * Initializer of the AIController. + */ + AIController(); + + /** + * Destructor of the AIController. + */ + ~AIController(); + + /** + * This function is called to start your AI. Your AI starts here. If you + * return from this function, your AI dies, so make sure that doesn't + * happen. + * @note Cannot be called from within your AI. + */ + void Start(); + + /** + * Find at which tick your AI currently is. + * @return returns the current tick. + */ + uint GetTick(); + + /** + * Get the value of one of your settings you set via info.nut. + * @param name The name of the setting. + * @return the value for the setting, or -1 if the setting is not known. + */ + int GetSetting(const char *name); + + /** + * Change the minimum amount of time the AI should be put in suspend mode + * when you execute a command. Normally in SP this is 1, and in MP it is + * what ever delay the server has been programmed to delay commands + * (normally between 1 and 5). To give a more 'real' effect to your AI, + * you can control that number here. + * @param ticks The minimum amount of ticks to wait. + * @pre Ticks should be positive. Too big values will influence performance of the AI. + * @note If the number is lower then the MP setting, the MP setting wins. + */ + static void SetCommandDelay(int ticks); + + /** + * Sleep for X ticks. The code continues after this line when the X AI ticks + * are passed. Mind that an AI tick is different from in-game ticks and + * differ per AI speed. + * @param ticks the ticks to wait + * @pre ticks > 0. + * @post the value of GetTick() will be changed exactly 'ticks' in value after + * calling this. + */ + static void Sleep(int ticks); + + /** + * When Squirrel triggers a print, this function is called. + * Squirrel calls this when 'print' is used, or when the script made an error. + * @param error_msg If true, it is a Squirrel error message. + * @param message The message Squirrel logged. + * @note Use AILog.Info/Warning/Error instead of 'print'. + */ + static void Print(bool error_msg, const char *message); + +private: + typedef std::map<const char *, const char *, ltstr> LoadedLibraryList; + + uint ticks; + LoadedLibraryList loaded_library; + int loaded_library_count; + + /** + * Register all classes that are known inside the NoAI API. + */ + void RegisterClasses(); + + /** + * Check if a library is already loaded. If found, fake_class_name is filled + * with the fake class name as given via AddLoadedLibrary. If not found, + * next_number is set to the next number available for the fake namespace. + * @param library_name The library to check if already loaded. + * @param next_number The next available number for a library if not already loaded. + * @param fake_class_name The name the library has if already loaded. + * @param fake_class_name_len The maximum length of fake_class_name. + * @return True if the library is already loaded. + */ + bool LoadedLibrary(const char *library_name, int *next_number, char *fake_class_name, int fake_class_name_len); + + /** + * Add a library as loaded. + */ + void AddLoadedLibrary(const char *library_name, const char *fake_class_name); +}; + +#endif /* AI_CONTROLLER_HPP */ diff --git a/src/ai/api/ai_controller.hpp.sq b/src/ai/api/ai_controller.hpp.sq new file mode 100644 index 000000000..d72574be7 --- /dev/null +++ b/src/ai/api/ai_controller.hpp.sq @@ -0,0 +1,12 @@ +#include "ai_controller.hpp" + +void SQAIController_Register(Squirrel *engine) { + DefSQClass <AIController> SQAIController("AIController"); + SQAIController.PreRegister(engine); + SQAIController.DefSQMethod(engine, &AIController::GetTick, "GetTick", 1, "x"); + SQAIController.DefSQStaticMethod(engine, &AIController::SetCommandDelay, "SetCommandDelay", 2, "xi"); + SQAIController.DefSQStaticMethod(engine, &AIController::Sleep, "Sleep", 2, "xi"); + SQAIController.DefSQStaticMethod(engine, &AIController::GetSetting, "GetSetting", 2, "xs"); + SQAIController.DefSQStaticMethod(engine, &AIController::Print, "Print", 3, "xbs"); + SQAIController.PostRegister(engine); +} diff --git a/src/ai/api/ai_date.cpp b/src/ai/api/ai_date.cpp new file mode 100644 index 000000000..b3ad552c9 --- /dev/null +++ b/src/ai/api/ai_date.cpp @@ -0,0 +1,47 @@ +/* $Id$ */ + +/** @file ai_date.cpp Implementation of AIDate. */ + +#include "ai_date.hpp" +#include "../../date_func.h" + +/* static */ int32 AIDate::GetCurrentDate() +{ + return ::_date; +} + +/* static */ int32 AIDate::GetYear(int32 date) +{ + if (date < 0) return -1; + + ::YearMonthDay ymd; + ::ConvertDateToYMD(date, &ymd); + return ymd.year; +} + +/* static */ int32 AIDate::GetMonth(int32 date) +{ + if (date < 0) return -1; + + ::YearMonthDay ymd; + ::ConvertDateToYMD(date, &ymd); + return ymd.month + 1; +} + +/* static */ int32 AIDate::GetDayOfMonth(int32 date) +{ + if (date < 0) return -1; + + ::YearMonthDay ymd; + ::ConvertDateToYMD(date, &ymd); + return ymd.day; +} + +/* static */ int32 AIDate::GetDate(int32 year, int32 month, int32 day_of_month) +{ + if (month < 1 || month > 12) return -1; + if (day_of_month < 1 || day_of_month > 31) return -1; + if (year < 0 || year > MAX_YEAR) return -1; + + return ::ConvertYMDToDate(year, month - 1, day_of_month); +} diff --git a/src/ai/api/ai_date.hpp b/src/ai/api/ai_date.hpp new file mode 100644 index 000000000..973b1b116 --- /dev/null +++ b/src/ai/api/ai_date.hpp @@ -0,0 +1,64 @@ +/* $Id$ */ + +/** @file ai_date.hpp Everything to query and manipulate date related information. */ + +#ifndef AI_DATE_HPP +#define AI_DATE_HPP + +#include "ai_object.hpp" + +/** + * Class that handles all date related (calculation) functions. + * + * @note Months and days of month are 1-based; the first month of the + * year is 1 and the first day of the month is also 1. + * @note Years are zero based; they start with the year 0. + * @note Dates can be used to determine the number of days between + * two different moments in time because they count the number + * of days since the year 0. + */ +class AIDate : public AIObject { +public: + static const char *GetClassName() { return "AIDate"; } + + /** + * Get the current date. + * This is the number of days since epoch under the assumption that + * there is a leap year every 4 years, except when dividable by + * 100 but not by 400. + * @return The current date. + */ + static int32 GetCurrentDate(); + + /** + * Get the year of the given date. + * @param date The date to get the year of. + * @return The year. + */ + static int32 GetYear(int32 date); + + /** + * Get the month of the given date. + * @param date The date to get the month of. + * @return The month. + */ + static int32 GetMonth(int32 date); + + /** + * Get the day (of the month) of the given date. + * @param date The date to get the day of. + * @return The day. + */ + static int32 GetDayOfMonth(int32 date); + + /** + * Get the date given a year, month and day of month. + * @param year The year of the to-be determined date. + * @param month The month of the to-be determined date. + * @param day_of_month The day of month of the to-be determined date. + * @return The date. + */ + static int32 GetDate(int32 year, int32 month, int32 day_of_month); +}; + +#endif /* AI_DATE_HPP */ diff --git a/src/ai/api/ai_date.hpp.sq b/src/ai/api/ai_date.hpp.sq new file mode 100644 index 000000000..5e6d7797d --- /dev/null +++ b/src/ai/api/ai_date.hpp.sq @@ -0,0 +1,28 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_date.hpp" + +namespace SQConvert { + /* Allow AIDate to be used as Squirrel parameter */ + template <> AIDate *GetParam(ForceType<AIDate *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIDate *)instance; } + template <> AIDate &GetParam(ForceType<AIDate &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIDate *)instance; } + template <> const AIDate *GetParam(ForceType<const AIDate *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIDate *)instance; } + template <> const AIDate &GetParam(ForceType<const AIDate &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIDate *)instance; } + template <> int Return<AIDate *>(HSQUIRRELVM vm, AIDate *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIDate", res, NULL, DefSQDestructorCallback<AIDate>); return 1; } +}; // namespace SQConvert + +void SQAIDate_Register(Squirrel *engine) { + DefSQClass <AIDate> SQAIDate("AIDate"); + SQAIDate.PreRegister(engine); + SQAIDate.AddConstructor<void (AIDate::*)(), 1>(engine, "x"); + + SQAIDate.DefSQStaticMethod(engine, &AIDate::GetClassName, "GetClassName", 1, "x"); + SQAIDate.DefSQStaticMethod(engine, &AIDate::GetCurrentDate, "GetCurrentDate", 1, "x"); + SQAIDate.DefSQStaticMethod(engine, &AIDate::GetYear, "GetYear", 2, "xi"); + SQAIDate.DefSQStaticMethod(engine, &AIDate::GetMonth, "GetMonth", 2, "xi"); + SQAIDate.DefSQStaticMethod(engine, &AIDate::GetDayOfMonth, "GetDayOfMonth", 2, "xi"); + SQAIDate.DefSQStaticMethod(engine, &AIDate::GetDate, "GetDate", 4, "xiii"); + + SQAIDate.PostRegister(engine); +} diff --git a/src/ai/api/ai_depotlist.cpp b/src/ai/api/ai_depotlist.cpp new file mode 100644 index 000000000..b93ec3aef --- /dev/null +++ b/src/ai/api/ai_depotlist.cpp @@ -0,0 +1,42 @@ +/* $Id$ */ + +/** @file ai_depotlist.cpp Implementation of AIDepotList and friends. */ + +#include "ai_depotlist.hpp" +#include "../../core/math_func.hpp" +#include "../../tile_map.h" +#include "../../company_func.h" +#include "../../depot_base.h" +#include "../../station_base.h" + +AIDepotList::AIDepotList(AITile::TransportType transport_type) +{ + ::TileType tile_type; + switch (transport_type) { + default: return; + + case AITile::TRANSPORT_ROAD: tile_type = ::MP_ROAD; break; + case AITile::TRANSPORT_RAIL: tile_type = ::MP_RAILWAY; break; + case AITile::TRANSPORT_WATER: tile_type = ::MP_WATER; break; + + case AITile::TRANSPORT_AIR: { + /* Hangars are not seen as real depots by the depot code. */ + const Station *st; + FOR_ALL_STATIONS(st) { + if (st->owner == ::_current_company) { + const AirportFTAClass *afc = st->Airport(); + for (uint i = 0; i < afc->nof_depots; i++) { + this->AddItem(st->xy + ToTileIndexDiff(afc->airport_depots[i])); + } + } + } + return; + } + } + + /* Handle 'standard' depots. */ + const Depot *depot; + FOR_ALL_DEPOTS(depot) { + if (::GetTileOwner(depot->xy) == ::_current_company && ::IsTileType(depot->xy, tile_type)) this->AddItem(depot->xy); + } +} diff --git a/src/ai/api/ai_depotlist.hpp b/src/ai/api/ai_depotlist.hpp new file mode 100644 index 000000000..0715b45bb --- /dev/null +++ b/src/ai/api/ai_depotlist.hpp @@ -0,0 +1,25 @@ +/* $Id$ */ + +/** @file ai_depotlist.hpp List all the depots (you own). */ + +#ifndef AI_DEPOTLIST_HPP +#define AI_DEPOTLIST_HPP + +#include "ai_abstractlist.hpp" +#include "ai_tile.hpp" + +/** + * Creates a list of the locations of the depots (and hangars) of which you are the owner. + * @ingroup AIList + */ +class AIDepotList : public AIAbstractList { +public: + static const char *GetClassName() { return "AIDepotList"; } + + /** + * @param transport_type The type of transport to make a list of depots for. + */ + AIDepotList(AITile::TransportType transport_type); +}; + +#endif /* AI_DEPOTLIST_HPP */ diff --git a/src/ai/api/ai_depotlist.hpp.sq b/src/ai/api/ai_depotlist.hpp.sq new file mode 100644 index 000000000..51f799490 --- /dev/null +++ b/src/ai/api/ai_depotlist.hpp.sq @@ -0,0 +1,23 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_depotlist.hpp" + +namespace SQConvert { + /* Allow AIDepotList to be used as Squirrel parameter */ + template <> AIDepotList *GetParam(ForceType<AIDepotList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIDepotList *)instance; } + template <> AIDepotList &GetParam(ForceType<AIDepotList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIDepotList *)instance; } + template <> const AIDepotList *GetParam(ForceType<const AIDepotList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIDepotList *)instance; } + template <> const AIDepotList &GetParam(ForceType<const AIDepotList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIDepotList *)instance; } + template <> int Return<AIDepotList *>(HSQUIRRELVM vm, AIDepotList *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIDepotList", res, NULL, DefSQDestructorCallback<AIDepotList>); return 1; } +}; // namespace SQConvert + +void SQAIDepotList_Register(Squirrel *engine) { + DefSQClass <AIDepotList> SQAIDepotList("AIDepotList"); + SQAIDepotList.PreRegister(engine, "AIAbstractList"); + SQAIDepotList.AddConstructor<void (AIDepotList::*)(AITile::TransportType transport_type), 2>(engine, "xi"); + + SQAIDepotList.DefSQStaticMethod(engine, &AIDepotList::GetClassName, "GetClassName", 1, "x"); + + SQAIDepotList.PostRegister(engine); +} diff --git a/src/ai/api/ai_engine.cpp b/src/ai/api/ai_engine.cpp new file mode 100644 index 000000000..5215c95f3 --- /dev/null +++ b/src/ai/api/ai_engine.cpp @@ -0,0 +1,295 @@ +/* $Id$ */ + +/** @file ai_engine.cpp Implementation of AIEngine. */ + +#include "ai_engine.hpp" +#include "ai_cargo.hpp" +#include "ai_rail.hpp" +#include "../../openttd.h" +#include "../../company_func.h" +#include "../../strings_func.h" +#include "../../roadveh.h" +#include "../../train.h" +#include "../../ship.h" +#include "../../aircraft.h" +#include "../../vehicle_func.h" +#include "../../core/alloc_func.hpp" +#include "../../economy_func.h" +#include "../../core/bitmath_func.hpp" +#include "../../settings_type.h" +#include "../../articulated_vehicles.h" +#include "table/strings.h" + +/* static */ bool AIEngine::IsValidEngine(EngineID engine_id) +{ + return ::IsEngineIndex(engine_id) && HasBit(::GetEngine(engine_id)->company_avail, _current_company); +} + +/* static */ const char *AIEngine::GetName(EngineID engine_id) +{ + if (!IsValidEngine(engine_id)) return NULL; + + static const int len = 64; + char *engine_name = MallocT<char>(len); + + ::SetDParam(0, engine_id); + ::GetString(engine_name, STR_ENGINE_NAME, &engine_name[len - 1]); + return engine_name; +} + +/* static */ CargoID AIEngine::GetCargoType(EngineID engine_id) +{ + if (!IsValidEngine(engine_id)) return CT_INVALID; + + switch (::GetEngine(engine_id)->type) { + case VEH_ROAD: { + const RoadVehicleInfo *vi = ::RoadVehInfo(engine_id); + return vi->cargo_type; + } break; + + case VEH_TRAIN: { + const RailVehicleInfo *vi = ::RailVehInfo(engine_id); + return vi->cargo_type; + } break; + + case VEH_SHIP: { + const ShipVehicleInfo *vi = ::ShipVehInfo(engine_id); + return vi->cargo_type; + } break; + + case VEH_AIRCRAFT: { + return CT_PASSENGERS; + } break; + + default: NOT_REACHED(); + } +} + +/* static */ bool AIEngine::CanRefitCargo(EngineID engine_id, CargoID cargo_id) +{ + if (!IsValidEngine(engine_id)) return false; + if (!AICargo::IsValidCargo(cargo_id)) return false; + + if (GetCargoType(engine_id) == cargo_id) return true; + if (cargo_id == CT_MAIL && ::GetEngine(engine_id)->type == VEH_AIRCRAFT) return true; + if (::GetEngine(engine_id)->type == VEH_SHIP && !ShipVehInfo(engine_id)->refittable) return false; + return ::CanRefitTo(engine_id, cargo_id); +} + +/* static */ bool AIEngine::CanPullCargo(EngineID engine_id, CargoID cargo_id) +{ + if (!IsValidEngine(engine_id)) return false; + if (GetVehicleType(engine_id) != AIVehicle::VEHICLE_RAIL) return false; + if (!AICargo::IsValidCargo(cargo_id)) return false; + + return (::RailVehInfo(engine_id)->ai_passenger_only != 1) || AICargo::HasCargoClass(cargo_id, AICargo::CC_PASSENGERS); +} + + +/* static */ int32 AIEngine::GetCapacity(EngineID engine_id) +{ + if (!IsValidEngine(engine_id)) return -1; + + switch (::GetEngine(engine_id)->type) { + case VEH_ROAD: + case VEH_TRAIN: { + uint16 *capacities = GetCapacityOfArticulatedParts(engine_id, ::GetEngine(engine_id)->type); + for (CargoID c = 0; c < NUM_CARGO; c++) { + if (capacities[c] == 0) continue; + return capacities[c]; + } + return -1; + } break; + + case VEH_SHIP: { + const ShipVehicleInfo *vi = ::ShipVehInfo(engine_id); + return vi->capacity; + } break; + + case VEH_AIRCRAFT: { + const AircraftVehicleInfo *vi = ::AircraftVehInfo(engine_id); + return vi->passenger_capacity; + } break; + + default: NOT_REACHED(); + } +} + +/* static */ int32 AIEngine::GetReliability(EngineID engine_id) +{ + if (!IsValidEngine(engine_id)) return -1; + + return (::GetEngine(engine_id)->reliability * 100 >> 16); +} + +/* static */ int32 AIEngine::GetMaxSpeed(EngineID engine_id) +{ + if (!IsValidEngine(engine_id)) return -1; + + switch (::GetEngine(engine_id)->type) { + case VEH_ROAD: { + const RoadVehicleInfo *vi = ::RoadVehInfo(engine_id); + /* Internal speeds are km/h * 2 */ + return vi->max_speed / 2; + } break; + + case VEH_TRAIN: { + const RailVehicleInfo *vi = ::RailVehInfo(engine_id); + return vi->max_speed; + } break; + + case VEH_SHIP: { + const ShipVehicleInfo *vi = ::ShipVehInfo(engine_id); + /* Internal speeds are km/h * 2 */ + return vi->max_speed / 2; + } break; + + case VEH_AIRCRAFT: { + const AircraftVehicleInfo *vi = ::AircraftVehInfo(engine_id); + return vi->max_speed / _settings_game.vehicle.plane_speed; + } break; + + default: NOT_REACHED(); + } +} + +/* static */ Money AIEngine::GetPrice(EngineID engine_id) +{ + if (!IsValidEngine(engine_id)) return -1; + + switch (::GetEngine(engine_id)->type) { + case VEH_ROAD: { + const RoadVehicleInfo *vi = ::RoadVehInfo(engine_id); + return (_price.roadveh_base >> 3) * vi->cost_factor >> 5; + } break; + + case VEH_TRAIN: { + const RailVehicleInfo *vi = ::RailVehInfo(engine_id); + return (_price.build_railvehicle >> 3) * vi->cost_factor >> 5; + } break; + + case VEH_SHIP: { + const ShipVehicleInfo *vi = ::ShipVehInfo(engine_id); + return (_price.ship_base >> 3) * vi->cost_factor >> 5; + } break; + + case VEH_AIRCRAFT: { + const AircraftVehicleInfo *vi = ::AircraftVehInfo(engine_id); + return (_price.aircraft_base >> 3) * vi->cost_factor >> 5; + } break; + + default: NOT_REACHED(); + } +} + +/* static */ int32 AIEngine::GetMaxAge(EngineID engine_id) +{ + if (!IsValidEngine(engine_id)) return -1; + + return ::GetEngine(engine_id)->lifelength * 366; +} + +/* static */ Money AIEngine::GetRunningCost(EngineID engine_id) +{ + if (!IsValidEngine(engine_id)) return -1; + + /* We need to create an instance in order to obtain GetRunningCost. + * This means we temporary allocate a vehicle in the pool, but + * there is no other way.. */ + Vehicle *vehicle; + switch (::GetEngine(engine_id)->type) { + case VEH_ROAD: { + vehicle = new RoadVehicle(); + } break; + + case VEH_TRAIN: { + vehicle = new Train(); + } break; + + case VEH_SHIP: { + vehicle = new Ship(); + } break; + + case VEH_AIRCRAFT: { + vehicle = new Aircraft(); + } break; + + default: NOT_REACHED(); + } + + vehicle->engine_type = engine_id; + Money runningCost = vehicle->GetRunningCost(); + delete vehicle; + return runningCost >> 8; +} + +/* static */ AIVehicle::VehicleType AIEngine::GetVehicleType(EngineID engine_id) +{ + if (!IsValidEngine(engine_id)) return AIVehicle::VEHICLE_INVALID; + + switch (::GetEngine(engine_id)->type) { + case VEH_ROAD: return AIVehicle::VEHICLE_ROAD; + case VEH_TRAIN: return AIVehicle::VEHICLE_RAIL; + case VEH_SHIP: return AIVehicle::VEHICLE_WATER; + case VEH_AIRCRAFT: return AIVehicle::VEHICLE_AIR; + default: NOT_REACHED(); + } +} + +/* static */ bool AIEngine::IsWagon(EngineID engine_id) +{ + if (!IsValidEngine(engine_id)) return false; + if (GetVehicleType(engine_id) != AIVehicle::VEHICLE_RAIL) return false; + + return ::RailVehInfo(engine_id)->power == 0; +} + +/* static */ bool AIEngine::CanRunOnRail(EngineID engine_id, AIRail::RailType track_rail_type) +{ + if (!IsValidEngine(engine_id)) return false; + if (GetVehicleType(engine_id) != AIVehicle::VEHICLE_RAIL) return false; + if (!AIRail::IsRailTypeAvailable(track_rail_type)) return false; + + return ::IsCompatibleRail((::RailType)::RailVehInfo(engine_id)->railtype, (::RailType)track_rail_type); +} + +/* static */ bool AIEngine::HasPowerOnRail(EngineID engine_id, AIRail::RailType track_rail_type) +{ + if (!IsValidEngine(engine_id)) return false; + if (GetVehicleType(engine_id) != AIVehicle::VEHICLE_RAIL) return false; + if (!AIRail::IsRailTypeAvailable(track_rail_type)) return false; + + return ::HasPowerOnRail((::RailType)::RailVehInfo(engine_id)->railtype, (::RailType)track_rail_type); +} + +/* static */ AIRoad::RoadType AIEngine::GetRoadType(EngineID engine_id) +{ + if (!IsValidEngine(engine_id)) return AIRoad::ROADTYPE_INVALID; + if (GetVehicleType(engine_id) != AIVehicle::VEHICLE_ROAD) return AIRoad::ROADTYPE_INVALID; + + return HasBit(::EngInfo(engine_id)->misc_flags, EF_ROAD_TRAM) ? AIRoad::ROADTYPE_TRAM : AIRoad::ROADTYPE_ROAD; +} + +/* static */ AIRail::RailType AIEngine::GetRailType(EngineID engine_id) +{ + if (!IsValidEngine(engine_id)) return AIRail::RAILTYPE_INVALID; + if (GetVehicleType(engine_id) != AIVehicle::VEHICLE_RAIL) return AIRail::RAILTYPE_INVALID; + + return (AIRail::RailType)(uint)::RailVehInfo(engine_id)->railtype; +} + +/* static */ bool AIEngine::IsArticulated(EngineID engine_id) +{ + if (!IsValidEngine(engine_id)) return false; + if (GetVehicleType(engine_id) != AIVehicle::VEHICLE_ROAD && GetVehicleType(engine_id) != AIVehicle::VEHICLE_RAIL) return false; + + return CountArticulatedParts(engine_id, true) != 0; +} + +/* static */ AIAirport::PlaneType AIEngine::GetPlaneType(EngineID engine_id) +{ + if (!IsValidEngine(engine_id)) return AIAirport::PT_INVALID; + if (GetVehicleType(engine_id) != AIVehicle::VEHICLE_AIR) return AIAirport::PT_INVALID; + + return (AIAirport::PlaneType)::AircraftVehInfo(engine_id)->subtype; +} diff --git a/src/ai/api/ai_engine.hpp b/src/ai/api/ai_engine.hpp new file mode 100644 index 000000000..67801107c --- /dev/null +++ b/src/ai/api/ai_engine.hpp @@ -0,0 +1,202 @@ +/* $Id$ */ + +/** @file ai_engine.hpp Everything to query and build engines. */ + +#ifndef AI_ENGINE_HPP +#define AI_ENGINE_HPP + +#include "ai_object.hpp" +#include "ai_vehicle.hpp" +#include "ai_road.hpp" +#include "ai_rail.hpp" +#include "ai_airport.hpp" + +/** + * Class that handles all engine related functions. + */ +class AIEngine : public AIObject { +public: + static const char *GetClassName() { return "AIEngine"; } + + /** + * Checks whether the given engine type is valid and buildable by you. + * @param engine_id The engine to check. + * @return True if and only if the engine type is valid. + */ + static bool IsValidEngine(EngineID engine_id); + + /** + * Get the name of an engine. + * @param engine_id The engine to get the name of. + * @pre IsValidEngine(engine_id). + * @return The name the engine has. + */ + static const char *GetName(EngineID engine_id); + + /** + * Get the cargo-type of an engine. In case it can transport 2 cargos, it + * returns the first. + * @param engine_id The engine to get the cargo-type of. + * @pre IsValidEngine(engine_id). + * @return The cargo-type of the engine. + */ + static CargoID GetCargoType(EngineID engine_id); + + /** + * Check if the cargo of an engine can be refitted to your requested. If + * the engine already allows this cargo, the function also returns true. + * @param engine_id The engine to check for refitting. + * @param cargo_id The cargo to check for refitting. + * @pre IsValidEngine(engine_id). + * @pre AICargo::IsValidCargo(cargo_id). + * @return True if the engine can carry this cargo, either via refit, or + * by default. + */ + static bool CanRefitCargo(EngineID engine_id, CargoID cargo_id); + + /** + * Check if the engine can pull a wagon with the given cargo. + * @param engine_id The engine to check. + * @param cargo_id The cargo to check. + * @pre IsValidEngine(engine_id). + * @pre GetVehicleType(engine_id) == AIVehicle.VEHICLE_RAIL. + * @pre AICargo::IsValidCargo(cargo_id). + * @return True if the engine can pull wagons carrying this cargo. + * @note This function is not exhaustive; a true here does not mean + * that the vehicle can pull the wagons, a false does mean it can't. + */ + static bool CanPullCargo(EngineID engine_id, CargoID cargo_id); + + /** + * Get the capacity of an engine. In case it can transport 2 cargos, it + * returns the first. + * @param engine_id The engine to get the capacity of. + * @pre IsValidEngine(engine_id). + * @return The capacity of the engine. + */ + static int32 GetCapacity(EngineID engine_id); + + /** + * Get the reliability of an engine. The value is between 0 and 100, where + * 100 means 100% reliability (never breaks down) and 0 means 0% + * reliability (you most likely don't want to buy it). + * @param engine_id The engine to get the reliability of. + * @pre IsValidEngine(engine_id). + * @return The reliability the engine has. + */ + static int32 GetReliability(EngineID engine_id); + + /** + * Get the maximum speed of an engine. + * @param engine_id The engine to get the maximum speed of. + * @pre IsValidEngine(engine_id). + * @return The maximum speed the engine has. + * @note The speed is in km/h. + */ + static int32 GetMaxSpeed(EngineID engine_id); + + /** + * Get the new cost of an engine. + * @param engine_id The engine to get the new cost of. + * @pre IsValidEngine(engine_id). + * @return The new cost the engine has. + */ + static Money GetPrice(EngineID engine_id); + + /** + * Get the maximum age of a brand new engine. + * @param engine_id The engine to get the maximum age of. + * @pre IsValidEngine(engine_id). + * @returns The maximum age of a new engine in days. + * @note Age is in days; divide by 366 to get per year. + */ + static int32 GetMaxAge(EngineID engine_id); + + /** + * Get the running cost of an engine. + * @param engine_id The engine to get the running cost of. + * @pre IsValidEngine(engine_id). + * @return The running cost of a vehicle per year. + * @note Cost is per year; divide by 364 to get per day. + */ + static Money GetRunningCost(EngineID engine_id); + + /** + * Get the type of an engine. + * @param engine_id The engine to get the type of. + * @pre IsValidEngine(engine_id). + * @return The type the engine has. + */ + static AIVehicle::VehicleType GetVehicleType(EngineID engine_id); + + /** + * Check if an engine is a wagon. + * @param engine_id The engine to check. + * @pre IsValidEngine(engine_id). + * @pre GetVehicleType(engine_id) == AIVehicle.VEHICLE_RAIL. + * @return Whether or not the engine is a wagon. + */ + static bool IsWagon(EngineID engine_id); + + /** + * Check if a train vehicle can run on a RailType. + * @param engine_id The engine to check. + * @param track_rail_type The type you want to check. + * @pre IsValidEngine(engine_id). + * @pre GetVehicleType(engine_id) == AIVehicle::VEHICLE_RAIL. + * @pre AIRail::IsRailTypeAvailable(track_rail_type). + * @return Whether an engine of type 'engine_id' can run on 'track_rail_type'. + * @note Even if a train can run on a RailType that doesn't mean that it'll be + * able to power the train. Use HasPowerOnRail for that. + */ + static bool CanRunOnRail(EngineID engine_id, AIRail::RailType track_rail_type); + + /** + * Check if a train engine has power on a RailType. + * @param engine_id The engine to check. + * @param track_rail_type Another RailType. + * @pre IsValidEngine(engine_id). + * @pre GetVehicleType(engine_id) == AIVehicle::VEHICLE_RAIL. + * @pre AIRail::IsRailTypeAvailable(track_rail_type). + * @return Whether an engine of type 'engine_id' has power on 'track_rail_type'. + */ + static bool HasPowerOnRail(EngineID engine_id, AIRail::RailType track_rail_type); + + /** + * Get the RoadType of the engine. + * @param engine_id The engine to get the RoadType of. + * @pre IsValidEngine(engine_id). + * @pre GetVehicleType(engine_id) == AIVehicle.VEHICLE_ROAD. + * @return The RoadType the engine has. + */ + static AIRoad::RoadType GetRoadType(EngineID engine_id); + + /** + * Get the RailType of the engine. + * @param engine_id The engine to get the RailType of. + * @pre IsValidEngine(engine_id). + * @pre GetVehicleType(engine_id) == AIVehicle.VEHICLE_RAIL. + * @return The RailType the engine has. + */ + static AIRail::RailType GetRailType(EngineID engine_id); + + /** + * Check if the engine is articulated. + * @param engine_id The engine to check. + * @pre IsValidEngine(engine_id). + * @pre GetVehicleType(engine_id) == AIVehicle.VEHICLE_ROAD || GetVehicleType(engine_id) == AIVehicle.VEHICLE_RAIL. + * @return True if the engine is articulated. + */ + static bool IsArticulated(EngineID engine_id); + + /** + * Get the PlaneType of the engine. + * @param engine_id The engine to get the PlaneType of. + * @pre IsValidEngine(engine_id). + * @pre GetVehicleType(engine_id) == AIVehicle.VEHICLE_AIR. + * @return The PlaneType the engine has. + */ + static AIAirport::PlaneType GetPlaneType(EngineID engine_id); +}; + +#endif /* AI_ENGINE_HPP */ diff --git a/src/ai/api/ai_engine.hpp.sq b/src/ai/api/ai_engine.hpp.sq new file mode 100644 index 000000000..067d3c65e --- /dev/null +++ b/src/ai/api/ai_engine.hpp.sq @@ -0,0 +1,42 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_engine.hpp" + +namespace SQConvert { + /* Allow AIEngine to be used as Squirrel parameter */ + template <> AIEngine *GetParam(ForceType<AIEngine *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEngine *)instance; } + template <> AIEngine &GetParam(ForceType<AIEngine &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEngine *)instance; } + template <> const AIEngine *GetParam(ForceType<const AIEngine *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEngine *)instance; } + template <> const AIEngine &GetParam(ForceType<const AIEngine &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEngine *)instance; } + template <> int Return<AIEngine *>(HSQUIRRELVM vm, AIEngine *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIEngine", res, NULL, DefSQDestructorCallback<AIEngine>); return 1; } +}; // namespace SQConvert + +void SQAIEngine_Register(Squirrel *engine) { + DefSQClass <AIEngine> SQAIEngine("AIEngine"); + SQAIEngine.PreRegister(engine); + SQAIEngine.AddConstructor<void (AIEngine::*)(), 1>(engine, "x"); + + SQAIEngine.DefSQStaticMethod(engine, &AIEngine::GetClassName, "GetClassName", 1, "x"); + SQAIEngine.DefSQStaticMethod(engine, &AIEngine::IsValidEngine, "IsValidEngine", 2, "xi"); + SQAIEngine.DefSQStaticMethod(engine, &AIEngine::GetName, "GetName", 2, "xi"); + SQAIEngine.DefSQStaticMethod(engine, &AIEngine::GetCargoType, "GetCargoType", 2, "xi"); + SQAIEngine.DefSQStaticMethod(engine, &AIEngine::CanRefitCargo, "CanRefitCargo", 3, "xii"); + SQAIEngine.DefSQStaticMethod(engine, &AIEngine::CanPullCargo, "CanPullCargo", 3, "xii"); + SQAIEngine.DefSQStaticMethod(engine, &AIEngine::GetCapacity, "GetCapacity", 2, "xi"); + SQAIEngine.DefSQStaticMethod(engine, &AIEngine::GetReliability, "GetReliability", 2, "xi"); + SQAIEngine.DefSQStaticMethod(engine, &AIEngine::GetMaxSpeed, "GetMaxSpeed", 2, "xi"); + SQAIEngine.DefSQStaticMethod(engine, &AIEngine::GetPrice, "GetPrice", 2, "xi"); + SQAIEngine.DefSQStaticMethod(engine, &AIEngine::GetMaxAge, "GetMaxAge", 2, "xi"); + SQAIEngine.DefSQStaticMethod(engine, &AIEngine::GetRunningCost, "GetRunningCost", 2, "xi"); + SQAIEngine.DefSQStaticMethod(engine, &AIEngine::GetVehicleType, "GetVehicleType", 2, "xi"); + SQAIEngine.DefSQStaticMethod(engine, &AIEngine::IsWagon, "IsWagon", 2, "xi"); + SQAIEngine.DefSQStaticMethod(engine, &AIEngine::CanRunOnRail, "CanRunOnRail", 3, "xii"); + SQAIEngine.DefSQStaticMethod(engine, &AIEngine::HasPowerOnRail, "HasPowerOnRail", 3, "xii"); + SQAIEngine.DefSQStaticMethod(engine, &AIEngine::GetRoadType, "GetRoadType", 2, "xi"); + SQAIEngine.DefSQStaticMethod(engine, &AIEngine::GetRailType, "GetRailType", 2, "xi"); + SQAIEngine.DefSQStaticMethod(engine, &AIEngine::IsArticulated, "IsArticulated", 2, "xi"); + SQAIEngine.DefSQStaticMethod(engine, &AIEngine::GetPlaneType, "GetPlaneType", 2, "xi"); + + SQAIEngine.PostRegister(engine); +} diff --git a/src/ai/api/ai_enginelist.cpp b/src/ai/api/ai_enginelist.cpp new file mode 100644 index 000000000..bd57025f6 --- /dev/null +++ b/src/ai/api/ai_enginelist.cpp @@ -0,0 +1,16 @@ +/* $Id$ */ + +/** @file ai_enginelist.cpp Implementation of AIEngineList and friends. */ + +#include "ai_enginelist.hpp" +#include "../../company_func.h" +#include "../../engine_base.h" +#include "../../core/bitmath_func.hpp" + +AIEngineList::AIEngineList(AIVehicle::VehicleType vehicle_type) +{ + Engine *e; + FOR_ALL_ENGINES_OF_TYPE(e, (::VehicleType)vehicle_type) { + if (HasBit(e->company_avail, _current_company)) this->AddItem(e->index); + } +} diff --git a/src/ai/api/ai_enginelist.hpp b/src/ai/api/ai_enginelist.hpp new file mode 100644 index 000000000..8c299caf3 --- /dev/null +++ b/src/ai/api/ai_enginelist.hpp @@ -0,0 +1,25 @@ +/* $Id$ */ + +/** @file ai_enginelist.hpp List all the engines. */ + +#ifndef AI_ENGINELIST_HPP +#define AI_ENGINELIST_HPP + +#include "ai_abstractlist.hpp" +#include "ai_vehicle.hpp" + +/** + * Create a list of engines based on a vehicle type. + * @ingroup AIList + */ +class AIEngineList : public AIAbstractList { +public: + static const char *GetClassName() { return "AIEngineList"; } + + /** + * @param vehicle_type The type of vehicle to make a list of engines for. + */ + AIEngineList(AIVehicle::VehicleType vehicle_type); +}; + +#endif /* AI_ENGINELIST_HPP */ diff --git a/src/ai/api/ai_enginelist.hpp.sq b/src/ai/api/ai_enginelist.hpp.sq new file mode 100644 index 000000000..98054d22d --- /dev/null +++ b/src/ai/api/ai_enginelist.hpp.sq @@ -0,0 +1,23 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_enginelist.hpp" + +namespace SQConvert { + /* Allow AIEngineList to be used as Squirrel parameter */ + template <> AIEngineList *GetParam(ForceType<AIEngineList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEngineList *)instance; } + template <> AIEngineList &GetParam(ForceType<AIEngineList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEngineList *)instance; } + template <> const AIEngineList *GetParam(ForceType<const AIEngineList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEngineList *)instance; } + template <> const AIEngineList &GetParam(ForceType<const AIEngineList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEngineList *)instance; } + template <> int Return<AIEngineList *>(HSQUIRRELVM vm, AIEngineList *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIEngineList", res, NULL, DefSQDestructorCallback<AIEngineList>); return 1; } +}; // namespace SQConvert + +void SQAIEngineList_Register(Squirrel *engine) { + DefSQClass <AIEngineList> SQAIEngineList("AIEngineList"); + SQAIEngineList.PreRegister(engine, "AIAbstractList"); + SQAIEngineList.AddConstructor<void (AIEngineList::*)(AIVehicle::VehicleType vehicle_type), 2>(engine, "xi"); + + SQAIEngineList.DefSQStaticMethod(engine, &AIEngineList::GetClassName, "GetClassName", 1, "x"); + + SQAIEngineList.PostRegister(engine); +} diff --git a/src/ai/api/ai_error.cpp b/src/ai/api/ai_error.cpp new file mode 100644 index 000000000..4c21db726 --- /dev/null +++ b/src/ai/api/ai_error.cpp @@ -0,0 +1,60 @@ +/* $Id$ */ + +/** @file ai_error.cpp Implementation of AIError. */ + +#include "ai_error.hpp" +#include "table/strings.h" +#include "../../core/bitmath_func.hpp" + +AIError::AIErrorMap AIError::error_map = AIError::AIErrorMap(); +AIError::AIErrorMapString AIError::error_map_string = AIError::AIErrorMapString(); + +/* static */ AIErrorType AIError::GetLastError() +{ + return AIObject::GetLastError(); +} + +/* static */ const char *AIError::GetLastErrorString() +{ + return (*error_map_string.find(AIError::GetLastError())).second; +} + +/* static */ AIErrorType AIError::StringToError(StringID internal_string_id) +{ + uint index = GB(internal_string_id, 11, 5); + switch (GB(internal_string_id, 11, 5)) { + case 26: case 28: case 29: case 30: // NewGRF strings. + return ERR_NEWGRF_SUPPLIED_ERROR; + + /* DO NOT SWAP case 14 and 4 because that will break StringToError due + * to the index dependency that relies on FALL THROUGHs. */ + case 14: if (index < 0xE4) break; // Player name + case 4: if (index < 0xC0) break; // Town name + case 15: // Custom name + case 31: // Dynamic strings + /* These strings are 'random' and have no meaning. + * They actually shouldn't even be returned as error messages. */ + return ERR_UNKNOWN; + + default: + break; + } + + AIErrorMap::iterator it = error_map.find(internal_string_id); + if (it == error_map.end()) return ERR_UNKNOWN; + return (*it).second; +} + +/* static */ void AIError::RegisterErrorMap(StringID internal_string_id, AIErrorType ai_error_msg) +{ + error_map[internal_string_id] = ai_error_msg; +} + +/* static */ void AIError::RegisterErrorMapString(AIErrorType ai_error_msg, const char *message) +{ + error_map_string[ai_error_msg] = message; +} + +/* static */ AIError::ErrorCategories AIError::GetErrorCategory() { + return (AIError::ErrorCategories)(GetLastError() >> (uint)ERR_CAT_BIT_SIZE); +} diff --git a/src/ai/api/ai_error.hpp b/src/ai/api/ai_error.hpp new file mode 100644 index 000000000..6a448ff3a --- /dev/null +++ b/src/ai/api/ai_error.hpp @@ -0,0 +1,171 @@ +/* $Id$ */ + +/** @file ai_error.hpp Everything to query errors. */ + +#ifndef AI_ERROR_HPP +#define AI_ERROR_HPP + +#include "ai_object.hpp" +#include <map> + +/** + * Helper to write precondition enforcers for the AI API in an abbreviated manner. + * @param returnval The value to return on failure. + * @param condition The condition that must be obeyed. + */ +#define EnforcePrecondition(returnval, condition) \ + if (!(condition)) { \ + AIObject::SetLastError(AIError::ERR_PRECONDITION_FAILED); \ + return returnval; \ + } + +/** + * Helper to write precondition enforcers for the AI API in an abbreviated manner. + * @param returnval The value to return on failure. + * @param condition The condition that must be obeyed. + * @param error_code The error code passed to AIObject::SetLastError. + */ +#define EnforcePreconditionCustomError(returnval, condition, error_code) \ + if (!(condition)) { \ + AIObject::SetLastError(error_code); \ + return returnval; \ + } + +/** + * Class that handles all error related functions. + */ +class AIError : public AIObject { +public: + static const char *GetClassName() { return "AIError"; } + + /** + * All categories errors can be divided in. + */ + enum ErrorCategories { + ERR_CAT_NONE = 0, //!< Error messages not related to any category. + ERR_CAT_GENERAL, //!< Error messages related to general things. + ERR_CAT_VEHICLE, //!< Error messages related to building / maintaining vehicles. + ERR_CAT_STATION, //!< Error messages related to building / maintaining stations. + ERR_CAT_BRIDGE, //!< Error messages related to building / removing bridges. + ERR_CAT_TUNNEL, //!< Error messages related to building / removing tunnels. + ERR_CAT_TILE, //!< Error messages related to raising / lowering and demolishing tiles. + ERR_CAT_SIGN, //!< Error messages related to building / removing signs. + ERR_CAT_RAIL, //!< Error messages related to building / maintaining rails. + ERR_CAT_ROAD, //!< Error messages related to building / maintaining roads. + ERR_CAT_ORDER, //!< Error messages related to managing orders. + ERR_CAT_MARINE, //!< Error messages related to building / removing ships, docks and channels. + + /** + * DO NOT USE! The error bitsize determines how many errors can be stored in + * a category and what the offsets are of all categories. + */ + ERR_CAT_BIT_SIZE = 8, + }; + + /** + * All general related error messages. + */ + enum ErrorMessages { + /** Initial error value */ + ERR_NONE = ERR_CAT_NONE << ERR_CAT_BIT_SIZE, // [] + /** If an error occured and the error wasn't mapped */ + ERR_UNKNOWN, // [] + /** If a precondition is not met */ + ERR_PRECONDITION_FAILED, // [] + /** A string supplied was too long */ + ERR_PRECONDITION_STRING_TOO_LONG, // [] + /** An error returned by a NewGRF. No possibility to get the exact error in an AI readable format */ + ERR_NEWGRF_SUPPLIED_ERROR, // [] + + /** Base for general errors */ + ERR_GENERAL_BASE = ERR_CAT_GENERAL << ERR_CAT_BIT_SIZE, + + /** Not enough cash to perform the previous action */ + ERR_NOT_ENOUGH_CASH, // [STR_0003_NOT_ENOUGH_CASH_REQUIRES] + + /** Local authority won't allow the previous action */ + ERR_LOCAL_AUTHORITY_REFUSES, // [STR_2009_LOCAL_AUTHORITY_REFUSES] + + /** The piece of infrastructure you tried to build is already in place */ + ERR_ALREADY_BUILT, // [STR_1007_ALREADY_BUILT, STR_5007_MUST_DEMOLISH_BRIDGE_FIRST] + + /** Area isn't clear, try to demolish the building on it */ + ERR_AREA_NOT_CLEAR, // [STR_2004_BUILDING_MUST_BE_DEMOLISHED, STR_5007_MUST_DEMOLISH_BRIDGE_FIRST, STR_300B_MUST_DEMOLISH_RAILROAD, STR_300E_MUST_DEMOLISH_AIRPORT_FIRST, STR_MUST_DEMOLISH_CARGO_TRAM_STATION, STR_3047_MUST_DEMOLISH_TRUCK_STATION, STR_MUST_DEMOLISH_PASSENGER_TRAM_STATION, STR_3046_MUST_DEMOLISH_BUS_STATION, STR_306A_BUOY_IN_THE_WAY, STR_304D_MUST_DEMOLISH_DOCK_FIRST, STR_4800_IN_THE_WAY, STR_5804_COMPANY_HEADQUARTERS_IN, STR_5800_OBJECT_IN_THE_WAY, STR_1801_MUST_REMOVE_ROAD_FIRST, STR_1008_MUST_REMOVE_RAILROAD_TRACK, STR_5007_MUST_DEMOLISH_BRIDGE_FIRST, STR_5006_MUST_DEMOLISH_TUNNEL_FIRST, STR_1002_EXCAVATION_WOULD_DAMAGE] + + /** Area / property is owned by another company */ + ERR_OWNED_BY_ANOTHER_COMPANY, // [STR_1024_AREA_IS_OWNED_BY_ANOTHER, STR_013B_OWNED_BY] + + /** The name given is not unique for the object type */ + ERR_NAME_IS_NOT_UNIQUE, // [STR_NAME_MUST_BE_UNIQUE] + + /** The building you want to build requires flat land */ + ERR_FLAT_LAND_REQUIRED, // [STR_0007_FLAT_LAND_REQUIRED] + + /** Land is sloped in the wrong direction for this build action */ + ERR_LAND_SLOPED_WRONG, // [STR_1000_LAND_SLOPED_IN_WRONG_DIRECTION] + + /** A vehicle is in the way */ + ERR_VEHICLE_IN_THE_WAY, // [STR_8803_TRAIN_IN_THE_WAY, STR_9000_ROAD_VEHICLE_IN_THE_WAY, STR_980E_SHIP_IN_THE_WAY, STR_A015_AIRCRAFT_IN_THE_WAY] + + /** Site is unsuitable */ + ERR_SITE_UNSUITABLE, // [STR_0239_SITE_UNSUITABLE, STR_304B_SITE_UNSUITABLE] + + /** Too close to the edge of the map */ + ERR_TOO_CLOSE_TO_EDGE, // [STR_0002_TOO_CLOSE_TO_EDGE_OF_MAP] + + /** Station is too spread out */ + ERR_STATION_TOO_SPREAD_OUT, // [STR_306C_STATION_TOO_SPREAD_OUT] + }; + + /** + * Check the membership of the last thrown error. + * @return The category the error belongs to. + * @note The last throw error can be aquired by calling GetLastError(). + */ + static ErrorCategories GetErrorCategory(); + + /** + * Get the last error. + * @return An ErrorMessages enum value. + */ + static AIErrorType GetLastError(); + + /** + * Get the last error in string format (for human readability). + * @return An ErrorMessage enum item, as string. + */ + static const char *GetLastErrorString(); + + /** + * Get the error based on the OpenTTD StringID. + * @note DO NOT INVOKE THIS METHOD YOURSELF! + * @param internal_string_id The string to convert. + * @return The NoAI equivalent error message. + */ + static AIErrorType StringToError(StringID internal_string_id); + + /** + * Map an internal OpenTTD error message to it's NoAI equivalent. + * @note DO NOT INVOKE THIS METHOD YOURSELF! The calls are autogenerated. + * @param internal_string_id The OpenTTD StringID used for an error. + * @param ai_error_msg The NoAI equivalent error message. + */ + static void RegisterErrorMap(StringID internal_string_id, AIErrorType ai_error_msg); + + /** + * Map an internal OpenTTD error message to it's NoAI equivalent. + * @note DO NOT INVOKE THIS METHOD YOURSELF! The calls are autogenerated. + * @param ai_error_msg The NoAI error message representation. + * @param message The string representation of this error message, used for debug purposes. + */ + static void RegisterErrorMapString(AIErrorType ai_error_msg, const char *message); + +private: + typedef std::map<StringID, AIErrorType> AIErrorMap; + typedef std::map<AIErrorType, const char *> AIErrorMapString; + + static AIErrorMap error_map; + static AIErrorMapString error_map_string; +}; + +#endif /* AI_ERROR_HPP */ diff --git a/src/ai/api/ai_error.hpp.sq b/src/ai/api/ai_error.hpp.sq new file mode 100644 index 000000000..1955de76b --- /dev/null +++ b/src/ai/api/ai_error.hpp.sq @@ -0,0 +1,121 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_error.hpp" + +namespace SQConvert { + /* Allow enums to be used as Squirrel parameters */ + template <> AIError::ErrorCategories GetParam(ForceType<AIError::ErrorCategories>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AIError::ErrorCategories)tmp; } + template <> int Return<AIError::ErrorCategories>(HSQUIRRELVM vm, AIError::ErrorCategories res) { sq_pushinteger(vm, (int32)res); return 1; } + template <> AIError::ErrorMessages GetParam(ForceType<AIError::ErrorMessages>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AIError::ErrorMessages)tmp; } + template <> int Return<AIError::ErrorMessages>(HSQUIRRELVM vm, AIError::ErrorMessages res) { sq_pushinteger(vm, (int32)res); return 1; } + + /* Allow AIError to be used as Squirrel parameter */ + template <> AIError *GetParam(ForceType<AIError *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIError *)instance; } + template <> AIError &GetParam(ForceType<AIError &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIError *)instance; } + template <> const AIError *GetParam(ForceType<const AIError *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIError *)instance; } + template <> const AIError &GetParam(ForceType<const AIError &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIError *)instance; } + template <> int Return<AIError *>(HSQUIRRELVM vm, AIError *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIError", res, NULL, DefSQDestructorCallback<AIError>); return 1; } +}; // namespace SQConvert + +void SQAIError_Register(Squirrel *engine) { + DefSQClass <AIError> SQAIError("AIError"); + SQAIError.PreRegister(engine); + SQAIError.AddConstructor<void (AIError::*)(), 1>(engine, "x"); + + SQAIError.DefSQConst(engine, AIError::ERR_CAT_NONE, "ERR_CAT_NONE"); + SQAIError.DefSQConst(engine, AIError::ERR_CAT_GENERAL, "ERR_CAT_GENERAL"); + SQAIError.DefSQConst(engine, AIError::ERR_CAT_VEHICLE, "ERR_CAT_VEHICLE"); + SQAIError.DefSQConst(engine, AIError::ERR_CAT_STATION, "ERR_CAT_STATION"); + SQAIError.DefSQConst(engine, AIError::ERR_CAT_BRIDGE, "ERR_CAT_BRIDGE"); + SQAIError.DefSQConst(engine, AIError::ERR_CAT_TUNNEL, "ERR_CAT_TUNNEL"); + SQAIError.DefSQConst(engine, AIError::ERR_CAT_TILE, "ERR_CAT_TILE"); + SQAIError.DefSQConst(engine, AIError::ERR_CAT_SIGN, "ERR_CAT_SIGN"); + SQAIError.DefSQConst(engine, AIError::ERR_CAT_RAIL, "ERR_CAT_RAIL"); + SQAIError.DefSQConst(engine, AIError::ERR_CAT_ROAD, "ERR_CAT_ROAD"); + SQAIError.DefSQConst(engine, AIError::ERR_CAT_ORDER, "ERR_CAT_ORDER"); + SQAIError.DefSQConst(engine, AIError::ERR_CAT_MARINE, "ERR_CAT_MARINE"); + SQAIError.DefSQConst(engine, AIError::ERR_CAT_BIT_SIZE, "ERR_CAT_BIT_SIZE"); + SQAIError.DefSQConst(engine, AIError::ERR_NONE, "ERR_NONE"); + SQAIError.DefSQConst(engine, AIError::ERR_UNKNOWN, "ERR_UNKNOWN"); + SQAIError.DefSQConst(engine, AIError::ERR_PRECONDITION_FAILED, "ERR_PRECONDITION_FAILED"); + SQAIError.DefSQConst(engine, AIError::ERR_PRECONDITION_STRING_TOO_LONG, "ERR_PRECONDITION_STRING_TOO_LONG"); + SQAIError.DefSQConst(engine, AIError::ERR_NEWGRF_SUPPLIED_ERROR, "ERR_NEWGRF_SUPPLIED_ERROR"); + SQAIError.DefSQConst(engine, AIError::ERR_GENERAL_BASE, "ERR_GENERAL_BASE"); + SQAIError.DefSQConst(engine, AIError::ERR_NOT_ENOUGH_CASH, "ERR_NOT_ENOUGH_CASH"); + SQAIError.DefSQConst(engine, AIError::ERR_LOCAL_AUTHORITY_REFUSES, "ERR_LOCAL_AUTHORITY_REFUSES"); + SQAIError.DefSQConst(engine, AIError::ERR_ALREADY_BUILT, "ERR_ALREADY_BUILT"); + SQAIError.DefSQConst(engine, AIError::ERR_AREA_NOT_CLEAR, "ERR_AREA_NOT_CLEAR"); + SQAIError.DefSQConst(engine, AIError::ERR_OWNED_BY_ANOTHER_COMPANY, "ERR_OWNED_BY_ANOTHER_COMPANY"); + SQAIError.DefSQConst(engine, AIError::ERR_NAME_IS_NOT_UNIQUE, "ERR_NAME_IS_NOT_UNIQUE"); + SQAIError.DefSQConst(engine, AIError::ERR_FLAT_LAND_REQUIRED, "ERR_FLAT_LAND_REQUIRED"); + SQAIError.DefSQConst(engine, AIError::ERR_LAND_SLOPED_WRONG, "ERR_LAND_SLOPED_WRONG"); + SQAIError.DefSQConst(engine, AIError::ERR_VEHICLE_IN_THE_WAY, "ERR_VEHICLE_IN_THE_WAY"); + SQAIError.DefSQConst(engine, AIError::ERR_SITE_UNSUITABLE, "ERR_SITE_UNSUITABLE"); + SQAIError.DefSQConst(engine, AIError::ERR_TOO_CLOSE_TO_EDGE, "ERR_TOO_CLOSE_TO_EDGE"); + SQAIError.DefSQConst(engine, AIError::ERR_STATION_TOO_SPREAD_OUT, "ERR_STATION_TOO_SPREAD_OUT"); + + AIError::RegisterErrorMap(STR_0003_NOT_ENOUGH_CASH_REQUIRES, AIError::ERR_NOT_ENOUGH_CASH); + AIError::RegisterErrorMap(STR_2009_LOCAL_AUTHORITY_REFUSES, AIError::ERR_LOCAL_AUTHORITY_REFUSES); + AIError::RegisterErrorMap(STR_1007_ALREADY_BUILT, AIError::ERR_ALREADY_BUILT); + AIError::RegisterErrorMap(STR_5007_MUST_DEMOLISH_BRIDGE_FIRST, AIError::ERR_ALREADY_BUILT); + AIError::RegisterErrorMap(STR_2004_BUILDING_MUST_BE_DEMOLISHED, AIError::ERR_AREA_NOT_CLEAR); + AIError::RegisterErrorMap(STR_5007_MUST_DEMOLISH_BRIDGE_FIRST, AIError::ERR_AREA_NOT_CLEAR); + AIError::RegisterErrorMap(STR_300B_MUST_DEMOLISH_RAILROAD, AIError::ERR_AREA_NOT_CLEAR); + AIError::RegisterErrorMap(STR_300E_MUST_DEMOLISH_AIRPORT_FIRST, AIError::ERR_AREA_NOT_CLEAR); + AIError::RegisterErrorMap(STR_MUST_DEMOLISH_CARGO_TRAM_STATION, AIError::ERR_AREA_NOT_CLEAR); + AIError::RegisterErrorMap(STR_3047_MUST_DEMOLISH_TRUCK_STATION, AIError::ERR_AREA_NOT_CLEAR); + AIError::RegisterErrorMap(STR_MUST_DEMOLISH_PASSENGER_TRAM_STATION, AIError::ERR_AREA_NOT_CLEAR); + AIError::RegisterErrorMap(STR_3046_MUST_DEMOLISH_BUS_STATION, AIError::ERR_AREA_NOT_CLEAR); + AIError::RegisterErrorMap(STR_306A_BUOY_IN_THE_WAY, AIError::ERR_AREA_NOT_CLEAR); + AIError::RegisterErrorMap(STR_304D_MUST_DEMOLISH_DOCK_FIRST, AIError::ERR_AREA_NOT_CLEAR); + AIError::RegisterErrorMap(STR_4800_IN_THE_WAY, AIError::ERR_AREA_NOT_CLEAR); + AIError::RegisterErrorMap(STR_5804_COMPANY_HEADQUARTERS_IN, AIError::ERR_AREA_NOT_CLEAR); + AIError::RegisterErrorMap(STR_5800_OBJECT_IN_THE_WAY, AIError::ERR_AREA_NOT_CLEAR); + AIError::RegisterErrorMap(STR_1801_MUST_REMOVE_ROAD_FIRST, AIError::ERR_AREA_NOT_CLEAR); + AIError::RegisterErrorMap(STR_1008_MUST_REMOVE_RAILROAD_TRACK, AIError::ERR_AREA_NOT_CLEAR); + AIError::RegisterErrorMap(STR_5007_MUST_DEMOLISH_BRIDGE_FIRST, AIError::ERR_AREA_NOT_CLEAR); + AIError::RegisterErrorMap(STR_5006_MUST_DEMOLISH_TUNNEL_FIRST, AIError::ERR_AREA_NOT_CLEAR); + AIError::RegisterErrorMap(STR_1002_EXCAVATION_WOULD_DAMAGE, AIError::ERR_AREA_NOT_CLEAR); + AIError::RegisterErrorMap(STR_1024_AREA_IS_OWNED_BY_ANOTHER, AIError::ERR_OWNED_BY_ANOTHER_COMPANY); + AIError::RegisterErrorMap(STR_013B_OWNED_BY, AIError::ERR_OWNED_BY_ANOTHER_COMPANY); + AIError::RegisterErrorMap(STR_NAME_MUST_BE_UNIQUE, AIError::ERR_NAME_IS_NOT_UNIQUE); + AIError::RegisterErrorMap(STR_0007_FLAT_LAND_REQUIRED, AIError::ERR_FLAT_LAND_REQUIRED); + AIError::RegisterErrorMap(STR_1000_LAND_SLOPED_IN_WRONG_DIRECTION, AIError::ERR_LAND_SLOPED_WRONG); + AIError::RegisterErrorMap(STR_8803_TRAIN_IN_THE_WAY, AIError::ERR_VEHICLE_IN_THE_WAY); + AIError::RegisterErrorMap(STR_9000_ROAD_VEHICLE_IN_THE_WAY, AIError::ERR_VEHICLE_IN_THE_WAY); + AIError::RegisterErrorMap(STR_980E_SHIP_IN_THE_WAY, AIError::ERR_VEHICLE_IN_THE_WAY); + AIError::RegisterErrorMap(STR_A015_AIRCRAFT_IN_THE_WAY, AIError::ERR_VEHICLE_IN_THE_WAY); + AIError::RegisterErrorMap(STR_0239_SITE_UNSUITABLE, AIError::ERR_SITE_UNSUITABLE); + AIError::RegisterErrorMap(STR_304B_SITE_UNSUITABLE, AIError::ERR_SITE_UNSUITABLE); + AIError::RegisterErrorMap(STR_0002_TOO_CLOSE_TO_EDGE_OF_MAP, AIError::ERR_TOO_CLOSE_TO_EDGE); + AIError::RegisterErrorMap(STR_306C_STATION_TOO_SPREAD_OUT, AIError::ERR_STATION_TOO_SPREAD_OUT); + + AIError::RegisterErrorMapString(AIError::ERR_NONE, "ERR_NONE"); + AIError::RegisterErrorMapString(AIError::ERR_UNKNOWN, "ERR_UNKNOWN"); + AIError::RegisterErrorMapString(AIError::ERR_PRECONDITION_FAILED, "ERR_PRECONDITION_FAILED"); + AIError::RegisterErrorMapString(AIError::ERR_PRECONDITION_STRING_TOO_LONG, "ERR_PRECONDITION_STRING_TOO_LONG"); + AIError::RegisterErrorMapString(AIError::ERR_NEWGRF_SUPPLIED_ERROR, "ERR_NEWGRF_SUPPLIED_ERROR"); + AIError::RegisterErrorMapString(AIError::ERR_NOT_ENOUGH_CASH, "ERR_NOT_ENOUGH_CASH"); + AIError::RegisterErrorMapString(AIError::ERR_LOCAL_AUTHORITY_REFUSES, "ERR_LOCAL_AUTHORITY_REFUSES"); + AIError::RegisterErrorMapString(AIError::ERR_ALREADY_BUILT, "ERR_ALREADY_BUILT"); + AIError::RegisterErrorMapString(AIError::ERR_AREA_NOT_CLEAR, "ERR_AREA_NOT_CLEAR"); + AIError::RegisterErrorMapString(AIError::ERR_OWNED_BY_ANOTHER_COMPANY, "ERR_OWNED_BY_ANOTHER_COMPANY"); + AIError::RegisterErrorMapString(AIError::ERR_NAME_IS_NOT_UNIQUE, "ERR_NAME_IS_NOT_UNIQUE"); + AIError::RegisterErrorMapString(AIError::ERR_FLAT_LAND_REQUIRED, "ERR_FLAT_LAND_REQUIRED"); + AIError::RegisterErrorMapString(AIError::ERR_LAND_SLOPED_WRONG, "ERR_LAND_SLOPED_WRONG"); + AIError::RegisterErrorMapString(AIError::ERR_VEHICLE_IN_THE_WAY, "ERR_VEHICLE_IN_THE_WAY"); + AIError::RegisterErrorMapString(AIError::ERR_SITE_UNSUITABLE, "ERR_SITE_UNSUITABLE"); + AIError::RegisterErrorMapString(AIError::ERR_TOO_CLOSE_TO_EDGE, "ERR_TOO_CLOSE_TO_EDGE"); + AIError::RegisterErrorMapString(AIError::ERR_STATION_TOO_SPREAD_OUT, "ERR_STATION_TOO_SPREAD_OUT"); + + SQAIError.DefSQStaticMethod(engine, &AIError::GetClassName, "GetClassName", 1, "x"); + SQAIError.DefSQStaticMethod(engine, &AIError::GetErrorCategory, "GetErrorCategory", 1, "x"); + SQAIError.DefSQStaticMethod(engine, &AIError::GetLastError, "GetLastError", 1, "x"); + SQAIError.DefSQStaticMethod(engine, &AIError::GetLastErrorString, "GetLastErrorString", 1, "x"); + SQAIError.DefSQStaticMethod(engine, &AIError::StringToError, "StringToError", 2, "xi"); + SQAIError.DefSQStaticMethod(engine, &AIError::RegisterErrorMap, "RegisterErrorMap", 3, "xii"); + SQAIError.DefSQStaticMethod(engine, &AIError::RegisterErrorMapString, "RegisterErrorMapString", 3, "xis"); + + SQAIError.PostRegister(engine); +} diff --git a/src/ai/api/ai_event.cpp b/src/ai/api/ai_event.cpp new file mode 100644 index 000000000..b18fa4777 --- /dev/null +++ b/src/ai/api/ai_event.cpp @@ -0,0 +1,65 @@ +/* $Id$ */ + +/** @file ai_event.cpp Implementation of AIEvent. */ + +#include "ai_event.hpp" +#include "ai_event_types.hpp" + +#include <queue> +#include <set> + +struct AIEventData { + std::queue<AIEvent *> stack; +}; + +/* static */ void AIEventController::CreateEventPointer() +{ + assert(AIObject::GetEventPointer() == NULL); + + AIObject::GetEventPointer() = new AIEventData(); +} + +/* static */ void AIEventController::FreeEventPointer() +{ + AIEventData *data = (AIEventData *)AIObject::GetEventPointer(); + + /* Free all waiting events (if any) */ + while (!data->stack.empty()) { + AIEvent *e = data->stack.front(); + data->stack.pop(); + e->Release(); + } + + /* Now kill our data pointer */ + delete data; +} + +/* static */ bool AIEventController::IsEventWaiting() +{ + if (AIObject::GetEventPointer() == NULL) AIEventController::CreateEventPointer(); + AIEventData *data = (AIEventData *)AIObject::GetEventPointer(); + + return !data->stack.empty(); +} + +/* static */ AIEvent *AIEventController::GetNextEvent() +{ + if (AIObject::GetEventPointer() == NULL) AIEventController::CreateEventPointer(); + AIEventData *data = (AIEventData *)AIObject::GetEventPointer(); + + if (data->stack.empty()) return NULL; + + AIEvent *e = data->stack.front(); + data->stack.pop(); + return e; +} + +/* static */ void AIEventController::InsertEvent(AIEvent *event) +{ + if (AIObject::GetEventPointer() == NULL) AIEventController::CreateEventPointer(); + AIEventData *data = (AIEventData *)AIObject::GetEventPointer(); + + event->AddRef(); + data->stack.push(event); +} + diff --git a/src/ai/api/ai_event.hpp b/src/ai/api/ai_event.hpp new file mode 100644 index 000000000..a1304ec33 --- /dev/null +++ b/src/ai/api/ai_event.hpp @@ -0,0 +1,107 @@ +/* $Id$ */ + +/** @file ai_event.hpp Everything to handle events from the game. */ + +#ifndef AI_EVENT_HPP +#define AI_EVENT_HPP + +#include "ai_object.hpp" + +/** + * Class that handles all event related functions. + * You can lookup the type, and than convert it to the real event-class. + * That way you can request more detailed information about the event. + */ +class AIEvent : public AIObject { +public: + static const char *GetClassName() { return "AIEvent"; } + + /** + * The type of event. Needed to lookup the detailed class. + */ + enum AIEventType { + AI_ET_INVALID = 0, + AI_ET_TEST, + AI_ET_SUBSIDY_OFFER, + AI_ET_SUBSIDY_OFFER_EXPIRED, + AI_ET_SUBSIDY_AWARDED, + AI_ET_SUBSIDY_EXPIRED, + AI_ET_ENGINE_PREVIEW, + AI_ET_COMPANY_NEW, + AI_ET_COMPANY_IN_TROUBLE, + AI_ET_COMPANY_MERGER, + AI_ET_COMPANY_BANKRUPT, + AI_ET_VEHICLE_CRASHED, + AI_ET_VEHICLE_LOST, + AI_ET_VEHICLE_WAITING_IN_DEPOT, + AI_ET_VEHICLE_UNPROFITABLE, + AI_ET_INDUSTRY_OPEN, + AI_ET_INDUSTRY_CLOSE, + AI_ET_ENGINE_AVAILABLE, + AI_ET_STATION_FIRST_VEHICLE, + }; + + /** + * Constructor of AIEvent, to get the type of event. + */ + AIEvent(AIEvent::AIEventType type) : + type(type) + {} + + /** + * Get the event-type. + * @return The @c AIEventType. + */ + AIEventType GetEventType() { return this->type; } + +protected: + /** + * The type of this event. + */ + AIEventType type; +}; + +/** + * Class that handles all event related functions. + * @note it is not needed to create an instance of AIEvent to access it, as + * all members are static, and all data is stored AI-wide. + */ +class AIEventController : public AIObject { +public: + /** + * The name of the class, needed by several sub-processes. + */ + static const char *GetClassName() { return "AIEventController"; } + + /** + * Check if there is an event waiting. + * @return true if there is an event on the stack. + */ + static bool IsEventWaiting(); + + /** + * Get the next event. + * @return a class of the event-child issues. + */ + static AIEvent *GetNextEvent(); + + /** + * Insert an event to the queue for the company. + * @param event The event to insert. + */ + static void InsertEvent(AIEvent *event); + + /** + * Free the event pointer. + * @note DO NOT CALL YOURSELF; leave it to the internal AI programming. + */ + static void FreeEventPointer(); + +private: + /** + * Create the event pointer. + */ + static void CreateEventPointer(); +}; + +#endif /* AI_EVENT_HPP */ diff --git a/src/ai/api/ai_event.hpp.sq b/src/ai/api/ai_event.hpp.sq new file mode 100644 index 000000000..f5ac02203 --- /dev/null +++ b/src/ai/api/ai_event.hpp.sq @@ -0,0 +1,72 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_event.hpp" + +namespace SQConvert { + /* Allow enums to be used as Squirrel parameters */ + template <> AIEvent::AIEventType GetParam(ForceType<AIEvent::AIEventType>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AIEvent::AIEventType)tmp; } + template <> int Return<AIEvent::AIEventType>(HSQUIRRELVM vm, AIEvent::AIEventType res) { sq_pushinteger(vm, (int32)res); return 1; } + + /* Allow AIEvent to be used as Squirrel parameter */ + template <> AIEvent *GetParam(ForceType<AIEvent *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEvent *)instance; } + template <> AIEvent &GetParam(ForceType<AIEvent &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEvent *)instance; } + template <> const AIEvent *GetParam(ForceType<const AIEvent *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEvent *)instance; } + template <> const AIEvent &GetParam(ForceType<const AIEvent &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEvent *)instance; } + template <> int Return<AIEvent *>(HSQUIRRELVM vm, AIEvent *res) { if (res == NULL) { sq_pushnull(vm); return 1; } Squirrel::CreateClassInstanceVM(vm, "AIEvent", res, NULL, DefSQDestructorCallback<AIEvent>); return 1; } +}; // namespace SQConvert + +void SQAIEvent_Register(Squirrel *engine) { + DefSQClass <AIEvent> SQAIEvent("AIEvent"); + SQAIEvent.PreRegister(engine); + SQAIEvent.AddConstructor<void (AIEvent::*)(AIEvent::AIEventType type), 2>(engine, "xi"); + + SQAIEvent.DefSQConst(engine, AIEvent::AI_ET_INVALID, "AI_ET_INVALID"); + SQAIEvent.DefSQConst(engine, AIEvent::AI_ET_TEST, "AI_ET_TEST"); + SQAIEvent.DefSQConst(engine, AIEvent::AI_ET_SUBSIDY_OFFER, "AI_ET_SUBSIDY_OFFER"); + SQAIEvent.DefSQConst(engine, AIEvent::AI_ET_SUBSIDY_OFFER_EXPIRED, "AI_ET_SUBSIDY_OFFER_EXPIRED"); + SQAIEvent.DefSQConst(engine, AIEvent::AI_ET_SUBSIDY_AWARDED, "AI_ET_SUBSIDY_AWARDED"); + SQAIEvent.DefSQConst(engine, AIEvent::AI_ET_SUBSIDY_EXPIRED, "AI_ET_SUBSIDY_EXPIRED"); + SQAIEvent.DefSQConst(engine, AIEvent::AI_ET_ENGINE_PREVIEW, "AI_ET_ENGINE_PREVIEW"); + SQAIEvent.DefSQConst(engine, AIEvent::AI_ET_COMPANY_NEW, "AI_ET_COMPANY_NEW"); + SQAIEvent.DefSQConst(engine, AIEvent::AI_ET_COMPANY_IN_TROUBLE, "AI_ET_COMPANY_IN_TROUBLE"); + SQAIEvent.DefSQConst(engine, AIEvent::AI_ET_COMPANY_MERGER, "AI_ET_COMPANY_MERGER"); + SQAIEvent.DefSQConst(engine, AIEvent::AI_ET_COMPANY_BANKRUPT, "AI_ET_COMPANY_BANKRUPT"); + SQAIEvent.DefSQConst(engine, AIEvent::AI_ET_VEHICLE_CRASHED, "AI_ET_VEHICLE_CRASHED"); + SQAIEvent.DefSQConst(engine, AIEvent::AI_ET_VEHICLE_LOST, "AI_ET_VEHICLE_LOST"); + SQAIEvent.DefSQConst(engine, AIEvent::AI_ET_VEHICLE_WAITING_IN_DEPOT, "AI_ET_VEHICLE_WAITING_IN_DEPOT"); + SQAIEvent.DefSQConst(engine, AIEvent::AI_ET_VEHICLE_UNPROFITABLE, "AI_ET_VEHICLE_UNPROFITABLE"); + SQAIEvent.DefSQConst(engine, AIEvent::AI_ET_INDUSTRY_OPEN, "AI_ET_INDUSTRY_OPEN"); + SQAIEvent.DefSQConst(engine, AIEvent::AI_ET_INDUSTRY_CLOSE, "AI_ET_INDUSTRY_CLOSE"); + SQAIEvent.DefSQConst(engine, AIEvent::AI_ET_ENGINE_AVAILABLE, "AI_ET_ENGINE_AVAILABLE"); + SQAIEvent.DefSQConst(engine, AIEvent::AI_ET_STATION_FIRST_VEHICLE, "AI_ET_STATION_FIRST_VEHICLE"); + + SQAIEvent.DefSQStaticMethod(engine, &AIEvent::GetClassName, "GetClassName", 1, "x"); + + SQAIEvent.DefSQMethod(engine, &AIEvent::GetEventType, "GetEventType", 1, "x"); + + SQAIEvent.PostRegister(engine); +} + +namespace SQConvert { + /* Allow AIEventController to be used as Squirrel parameter */ + template <> AIEventController *GetParam(ForceType<AIEventController *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventController *)instance; } + template <> AIEventController &GetParam(ForceType<AIEventController &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventController *)instance; } + template <> const AIEventController *GetParam(ForceType<const AIEventController *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventController *)instance; } + template <> const AIEventController &GetParam(ForceType<const AIEventController &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventController *)instance; } + template <> int Return<AIEventController *>(HSQUIRRELVM vm, AIEventController *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIEventController", res, NULL, DefSQDestructorCallback<AIEventController>); return 1; } +}; // namespace SQConvert + +void SQAIEventController_Register(Squirrel *engine) { + DefSQClass <AIEventController> SQAIEventController("AIEventController"); + SQAIEventController.PreRegister(engine); + SQAIEventController.AddConstructor<void (AIEventController::*)(), 1>(engine, "x"); + + SQAIEventController.DefSQStaticMethod(engine, &AIEventController::GetClassName, "GetClassName", 1, "x"); + SQAIEventController.DefSQStaticMethod(engine, &AIEventController::IsEventWaiting, "IsEventWaiting", 1, "x"); + SQAIEventController.DefSQStaticMethod(engine, &AIEventController::GetNextEvent, "GetNextEvent", 1, "x"); + SQAIEventController.DefSQStaticMethod(engine, &AIEventController::InsertEvent, "InsertEvent", 2, "xx"); + SQAIEventController.DefSQStaticMethod(engine, &AIEventController::FreeEventPointer, "FreeEventPointer", 1, "x"); + + SQAIEventController.PostRegister(engine); +} diff --git a/src/ai/api/ai_event_types.cpp b/src/ai/api/ai_event_types.cpp new file mode 100644 index 000000000..07b324cfa --- /dev/null +++ b/src/ai/api/ai_event_types.cpp @@ -0,0 +1,187 @@ +/* $Id$ */ + +/** @file ai_event_types.cpp Implementation of all EventTypes. */ + +#include "ai_event_types.hpp" +#include "../../openttd.h" +#include "../../core/alloc_func.hpp" +#include "../../strings_func.h" +#include "../../roadveh.h" +#include "../../train.h" +#include "../../ship.h" +#include "../../aircraft.h" +#include "../../settings_type.h" +#include "../../articulated_vehicles.h" +#include "table/strings.h" + +bool AIEventVehicleCrashed::CloneCrashedVehicle(TileIndex depot) +{ + return false; +} + +const char *AIEventEnginePreview::GetName() +{ + static const int len = 64; + char *engine_name = MallocT<char>(len); + + ::SetDParam(0, engine); + ::GetString(engine_name, STR_ENGINE_NAME, &engine_name[len - 1]); + return engine_name; +} + +CargoID AIEventEnginePreview::GetCargoType() +{ + switch (::GetEngine(engine)->type) { + case VEH_ROAD: { + const RoadVehicleInfo *vi = ::RoadVehInfo(engine); + return vi->cargo_type; + } break; + + case VEH_TRAIN: { + const RailVehicleInfo *vi = ::RailVehInfo(engine); + return vi->cargo_type; + } break; + + case VEH_SHIP: { + const ShipVehicleInfo *vi = ::ShipVehInfo(engine); + return vi->cargo_type; + } break; + + case VEH_AIRCRAFT: { + return CT_PASSENGERS; + } break; + + default: NOT_REACHED(); + } +} + +int32 AIEventEnginePreview::GetCapacity() +{ + switch (::GetEngine(engine)->type) { + case VEH_ROAD: + case VEH_TRAIN: { + uint16 *capacities = GetCapacityOfArticulatedParts(engine, ::GetEngine(engine)->type); + for (CargoID c = 0; c < NUM_CARGO; c++) { + if (capacities[c] == 0) continue; + return capacities[c]; + } + return -1; + } break; + + case VEH_SHIP: { + const ShipVehicleInfo *vi = ::ShipVehInfo(engine); + return vi->capacity; + } break; + + case VEH_AIRCRAFT: { + const AircraftVehicleInfo *vi = ::AircraftVehInfo(engine); + return vi->passenger_capacity; + } break; + + default: NOT_REACHED(); + } +} + +int32 AIEventEnginePreview::GetMaxSpeed() +{ + switch (::GetEngine(engine)->type) { + case VEH_ROAD: { + const RoadVehicleInfo *vi = ::RoadVehInfo(engine); + /* Internal speeds are km/h * 2 */ + return vi->max_speed / 2; + } break; + + case VEH_TRAIN: { + const RailVehicleInfo *vi = ::RailVehInfo(engine); + return vi->max_speed; + } break; + + case VEH_SHIP: { + const ShipVehicleInfo *vi = ::ShipVehInfo(engine); + /* Internal speeds are km/h * 2 */ + return vi->max_speed / 2; + } break; + + case VEH_AIRCRAFT: { + const AircraftVehicleInfo *vi = ::AircraftVehInfo(engine); + return vi->max_speed / _settings_game.vehicle.plane_speed; + } break; + + default: NOT_REACHED(); + } +} + +Money AIEventEnginePreview::GetPrice() +{ + switch (::GetEngine(engine)->type) { + case VEH_ROAD: { + const RoadVehicleInfo *vi = ::RoadVehInfo(engine); + return (_price.roadveh_base >> 3) * vi->cost_factor >> 5; + } break; + + case VEH_TRAIN: { + const RailVehicleInfo *vi = ::RailVehInfo(engine); + return (_price.build_railvehicle >> 3) * vi->cost_factor >> 5; + } break; + + case VEH_SHIP: { + const ShipVehicleInfo *vi = ::ShipVehInfo(engine); + return (_price.ship_base >> 3) * vi->cost_factor >> 5; + } break; + + case VEH_AIRCRAFT: { + const AircraftVehicleInfo *vi = ::AircraftVehInfo(engine); + return (_price.aircraft_base >> 3) * vi->cost_factor >> 5; + } break; + + default: NOT_REACHED(); + } +} + +Money AIEventEnginePreview::GetRunningCost() +{ + /* We need to create an instance in order to obtain GetRunningCost. + * This means we temporary allocate a vehicle in the pool, but + * there is no other way.. */ + Vehicle *vehicle; + switch (::GetEngine(engine)->type) { + case VEH_ROAD: { + vehicle = new RoadVehicle(); + } break; + + case VEH_TRAIN: { + vehicle = new Train(); + } break; + + case VEH_SHIP: { + vehicle = new Ship(); + } break; + + case VEH_AIRCRAFT: { + vehicle = new Aircraft(); + } break; + + default: NOT_REACHED(); + } + + vehicle->engine_type = engine; + Money runningCost = vehicle->GetRunningCost(); + delete vehicle; + return runningCost >> 8; +} + +AIVehicle::VehicleType AIEventEnginePreview::GetVehicleType() +{ + switch (::GetEngine(engine)->type) { + case VEH_ROAD: return AIVehicle::VEHICLE_ROAD; + case VEH_TRAIN: return AIVehicle::VEHICLE_RAIL; + case VEH_SHIP: return AIVehicle::VEHICLE_WATER; + case VEH_AIRCRAFT: return AIVehicle::VEHICLE_AIR; + default: NOT_REACHED(); + } +} + +bool AIEventEnginePreview::AcceptPreview() +{ + return AIObject::DoCommand(0, engine, 0, CMD_WANT_ENGINE_PREVIEW); +} diff --git a/src/ai/api/ai_event_types.hpp b/src/ai/api/ai_event_types.hpp new file mode 100644 index 000000000..c64b2a367 --- /dev/null +++ b/src/ai/api/ai_event_types.hpp @@ -0,0 +1,684 @@ +/* $Id$ */ + +/** @file ai_event_types.hpp The detailed types of all events. */ + +#ifndef AI_EVENT_TYPES_HPP +#define AI_EVENT_TYPES_HPP + +#include "ai_object.hpp" +#include "ai_event.hpp" +#include "ai_town.hpp" +#include "ai_industry.hpp" +#include "ai_engine.hpp" +#include "ai_subsidy.hpp" + +/** + * Event Test: a simple test event, to see if the event system is working. + * Triggered via AIEventController::Test(); + */ +class AIEventTest : public AIEvent { +public: + static const char *GetClassName() { return "AIEventTest"; } + + /** + * @param test A test value. + */ + AIEventTest(uint test) : + AIEvent(AI_ET_TEST), + test(test) + {} + + /** + * Convert an AIEvent to the real instance. + * @param instance The instance to convert. + * @return The converted instance. + */ + static AIEventTest *Convert(AIEvent *instance) { return (AIEventTest *)instance; } + + /** + * Return the test value. + * @return The test value. + */ + uint GetTest() { return this->test; } + +private: + uint test; +}; + +/** + * Event Vehicle Crash, indicating a vehicle of yours is crashed. + * It contains both the crash site as the vehicle crashed. It has a nice + * helper that creates a new vehicle in a depot with the same type + * and orders as the crashed one. In case the vehicle type isn't available + * anymore, it will find the next best. + */ +class AIEventVehicleCrashed : public AIEvent { +public: + static const char *GetClassName() { return "AIEventVehicleCrashed"; } + + /** + * @param vehicle The vehicle that crashed. + * @param crash_site Where the vehicle crashed. + */ + AIEventVehicleCrashed(VehicleID vehicle, TileIndex crash_site) : + AIEvent(AI_ET_VEHICLE_CRASHED), + crash_site(crash_site), + vehicle(vehicle) + {} + + /** + * Convert an AIEvent to the real instance. + * @param instance The instance to convert. + * @return The converted instance. + */ + static AIEventVehicleCrashed *Convert(AIEvent *instance) { return (AIEventVehicleCrashed *)instance; } + + /** + * Get the VehicleID of the crashed vehicle. + * @return The crashed vehicle. + */ + VehicleID GetVehicleID() { return vehicle; } + + /** + * Find the tile the vehicle crashed. + * @return The crash site. + */ + TileIndex GetCrashSite() { return crash_site; } + + /** + * Clone the crashed vehicle and send it on its way again. + * @param depot the depot to build the vehicle in. + * @return True when the cloning succeeded. + * @note This function isn't implemented yet. + */ + bool CloneCrashedVehicle(TileIndex depot); + +private: + TileIndex crash_site; + VehicleID vehicle; +}; + +/** + * Event Subsidy Offered, indicating someone offered a subsidy. + */ +class AIEventSubsidyOffer : public AIEvent { +public: + static const char *GetClassName() { return "AIEventSubsidyOffer"; } + + /** + * @param subsidy_id The index of this subsidy in the _subsidies array. + */ + AIEventSubsidyOffer(SubsidyID subsidy_id) : + AIEvent(AI_ET_SUBSIDY_OFFER), + subsidy_id(subsidy_id) + {} + + /** + * Convert an AIEvent to the real instance. + * @param instance The instance to convert. + * @return The converted instance. + */ + static AIEventSubsidyOffer *Convert(AIEvent *instance) { return (AIEventSubsidyOffer *)instance; } + + /** + * Get the SubsidyID of the subsidy. + * @return The subsidy id. + */ + SubsidyID GetSubsidyID() { return subsidy_id; } + +private: + SubsidyID subsidy_id; +}; + +/** + * Event Subsidy Offer Expired, indicating a subsidy will no longer be awarded. + */ +class AIEventSubsidyOfferExpired : public AIEvent { +public: + static const char *GetClassName() { return "AIEventSubsidyOfferExpired"; } + + /** + * @param subsidy_id The index of this subsidy in the _subsidies array. + */ + AIEventSubsidyOfferExpired(SubsidyID subsidy_id) : + AIEvent(AI_ET_SUBSIDY_OFFER_EXPIRED), + subsidy_id(subsidy_id) + {} + + /** + * Convert an AIEvent to the real instance. + * @param instance The instance to convert. + * @return The converted instance. + */ + static AIEventSubsidyOfferExpired *Convert(AIEvent *instance) { return (AIEventSubsidyOfferExpired *)instance; } + + /** + * Get the SubsidyID of the subsidy. + * @return The subsidy id. + */ + SubsidyID GetSubsidyID() { return subsidy_id; } + +private: + SubsidyID subsidy_id; +}; + +/** + * Event Subidy Awarded, indicating a subsidy is awarded to some company. + */ +class AIEventSubsidyAwarded : public AIEvent { +public: + static const char *GetClassName() { return "AIEventSubsidyAwarded"; } + + /** + * @param subsidy_id The index of this subsidy in the _subsidies array. + */ + AIEventSubsidyAwarded(SubsidyID subsidy_id) : + AIEvent(AI_ET_SUBSIDY_AWARDED), + subsidy_id(subsidy_id) + {} + + /** + * Convert an AIEvent to the real instance. + * @param instance The instance to convert. + * @return The converted instance. + */ + static AIEventSubsidyAwarded *Convert(AIEvent *instance) { return (AIEventSubsidyAwarded *)instance; } + + /** + * Get the SubsidyID of the subsidy. + * @return The subsidy id. + */ + SubsidyID GetSubsidyID() { return subsidy_id; } + +private: + SubsidyID subsidy_id; +}; + +/** + * Event Subsidy Expired, indicating a route that was once subsidized no longer is. + */ +class AIEventSubsidyExpired : public AIEvent { +public: + static const char *GetClassName() { return "AIEventSubsidyExpired"; } + + /** + * @param subsidy_id The index of this subsidy in the _subsidies array. + */ + AIEventSubsidyExpired(SubsidyID subsidy_id) : + AIEvent(AI_ET_SUBSIDY_EXPIRED), + subsidy_id(subsidy_id) + {} + + /** + * Convert an AIEvent to the real instance. + * @param instance The instance to convert. + * @return The converted instance. + */ + static AIEventSubsidyExpired *Convert(AIEvent *instance) { return (AIEventSubsidyExpired *)instance; } + + /** + * Get the SubsidyID of the subsidy. + * @return The subsidy id. + */ + SubsidyID GetSubsidyID() { return subsidy_id; } + +private: + SubsidyID subsidy_id; +}; + +/** + * Event Engine Preview, indicating a manufacturer offer you to test a new engine. + * You can get the same information about the offered engine as a real user + * would see in the offer window. And you can also accept the offer. + */ +class AIEventEnginePreview : public AIEvent { +public: + static const char *GetClassName() { return "AIEventEnginePreview"; } + + /** + * @param engine The engine offered to test. + */ + AIEventEnginePreview(EngineID engine) : + AIEvent(AI_ET_ENGINE_PREVIEW), + engine(engine) + {} + + /** + * Convert an AIEvent to the real instance. + * @param instance The instance to convert. + * @return The converted instance. + */ + static AIEventEnginePreview *Convert(AIEvent *instance) { return (AIEventEnginePreview *)instance; } + + /** + * Get the name of the offered engine. + * @return The name the engine has. + */ + const char *GetName(); + + /** + * Get the cargo-type of the offered engine. In case it can transport 2 cargos, it + * returns the first. + * @return The cargo-type of the engine. + */ + CargoID GetCargoType(); + + /** + * Get the capacity of the offered engine. In case it can transport 2 cargos, it + * returns the first. + * @return The capacity of the engine. + */ + int32 GetCapacity(); + + /** + * Get the maximum speed of the offered engine. + * @return The maximum speed the engine has. + * @note The speed is in km/h. + */ + int32 GetMaxSpeed(); + + /** + * Get the new cost of the offered engine. + * @return The new cost the engine has. + */ + Money GetPrice(); + + /** + * Get the running cost of the offered engine. + * @return The running cost of the vehicle per year. + * @note Cost is per year; divide by 364 to get per day. + */ + Money GetRunningCost(); + + /** + * Get the type of the offered engine. + * @return The type the engine has. + */ + AIVehicle::VehicleType GetVehicleType(); + + /** + * Accept the engine preview. + * @return True when the accepting succeeded. + */ + bool AcceptPreview(); + +private: + EngineID engine; +}; + +/** + * Event Company New, indicating a new company has been created. + */ +class AIEventCompanyNew : public AIEvent { +public: + static const char *GetClassName() { return "AIEventCompanyNew"; } + + /** + * @param owner The new company. + */ + AIEventCompanyNew(Owner owner) : + AIEvent(AI_ET_COMPANY_NEW), + owner((AICompany::CompanyID)(byte)owner) + {} + + /** + * Convert an AIEvent to the real instance. + * @param instance The instance to convert. + * @return The converted instance. + */ + static AIEventCompanyNew *Convert(AIEvent *instance) { return (AIEventCompanyNew *)instance; } + + /** + * Get the CompanyID of the company that has been created. + * @return The CompanyID of the company. + */ + AICompany::CompanyID GetCompanyID() { return owner; } + +private: + AICompany::CompanyID owner; +}; + +/** + * Event Company In Trouble, indicating a company is in trouble and might go + * bankrupt soon. + */ +class AIEventCompanyInTrouble : public AIEvent { +public: + static const char *GetClassName() { return "AIEventCompanyInTrouble"; } + + /** + * @param owner The company that is in trouble. + */ + AIEventCompanyInTrouble(Owner owner) : + AIEvent(AI_ET_COMPANY_IN_TROUBLE), + owner((AICompany::CompanyID)(byte)owner) + {} + + /** + * Convert an AIEvent to the real instance. + * @param instance The instance to convert. + * @return The converted instance. + */ + static AIEventCompanyInTrouble *Convert(AIEvent *instance) { return (AIEventCompanyInTrouble *)instance; } + + /** + * Get the CompanyID of the company that is in trouble. + * @return The CompanyID of the company in trouble. + */ + AICompany::CompanyID GetCompanyID() { return owner; } + +private: + AICompany::CompanyID owner; +}; + +/** + * Event Company Merger, indicating a company has been bought by another + * company. + */ +class AIEventCompanyMerger : public AIEvent { +public: + static const char *GetClassName() { return "AIEventCompanyMerger"; } + + /** + * @param old_owner The company bought off. + * @param new_owner The company that bougth owner. + */ + AIEventCompanyMerger(Owner old_owner, Owner new_owner) : + AIEvent(AI_ET_COMPANY_MERGER), + old_owner((AICompany::CompanyID)(byte)old_owner), + new_owner((AICompany::CompanyID)(byte)new_owner) + {} + + /** + * Convert an AIEvent to the real instance. + * @param instance The instance to convert. + * @return The converted instance. + */ + static AIEventCompanyMerger *Convert(AIEvent *instance) { return (AIEventCompanyMerger *)instance; } + + /** + * Get the CompanyID of the company that has been bought. + * @return The CompanyID of the company that has been bought. + * @note: The value below is not valid anymore as CompanyID, and + * AICompany::ResolveCompanyID will return INVALID_COMPANY. It's + * only usefull if you're keeping track of company's yourself. + */ + AICompany::CompanyID GetOldCompanyID() { return old_owner; } + + /** + * Get the CompanyID of the new owner. + * @return The CompanyID of the new owner. + */ + AICompany::CompanyID GetNewCompanyID() { return new_owner; } + +private: + AICompany::CompanyID old_owner; + AICompany::CompanyID new_owner; +}; + +/** + * Event Company Bankrupt, indicating a company has gone bankrupt. + */ +class AIEventCompanyBankrupt : public AIEvent { +public: + static const char *GetClassName() { return "AIEventCompanyBankrupt"; } + + /** + * @param owner The company that has gone bankrupt. + */ + AIEventCompanyBankrupt(Owner owner) : + AIEvent(AI_ET_COMPANY_BANKRUPT), + owner((AICompany::CompanyID)(byte)owner) + {} + + /** + * Convert an AIEvent to the real instance. + * @param instance The instance to convert. + * @return The converted instance. + */ + static AIEventCompanyBankrupt *Convert(AIEvent *instance) { return (AIEventCompanyBankrupt *)instance; } + + /** + * Get the CompanyID of the company that has gone bankrupt. + * @return The CompanyID of the company that has gone bankrupt. + */ + AICompany::CompanyID GetCompanyID() { return owner; } + +private: + AICompany::CompanyID owner; +}; + +/** + * Event Vehicle Lost, indicating a vehicle can't find its way to its destination. + */ +class AIEventVehicleLost : public AIEvent { +public: + static const char *GetClassName() { return "AIEventVehicleLost"; } + + /** + * @param vehicle_id The vehicle that is lost. + */ + AIEventVehicleLost(VehicleID vehicle_id) : + AIEvent(AI_ET_VEHICLE_LOST), + vehicle_id(vehicle_id) + {} + + /** + * Convert an AIEvent to the real instance. + * @param instance The instance to convert. + * @return The converted instance. + */ + static AIEventVehicleLost *Convert(AIEvent *instance) { return (AIEventVehicleLost *)instance; } + + /** + * Get the VehicleID of the vehicle that is lost. + * @return The VehicleID of the vehicle that is lost. + */ + VehicleID GetVehicleID() { return vehicle_id; } + +private: + VehicleID vehicle_id; +}; + +/** + * Event VehicleWaitingInDepot, indicating a vehicle has arrived a depot and is now waiting there. + */ +class AIEventVehicleWaitingInDepot : public AIEvent { +public: + static const char *GetClassName() { return "AIEventVehicleWaitingInDepot"; } + + /** + * @param vehicle_id The vehicle that is waiting in a depot. + */ + AIEventVehicleWaitingInDepot(VehicleID vehicle_id) : + AIEvent(AI_ET_VEHICLE_WAITING_IN_DEPOT), + vehicle_id(vehicle_id) + {} + + /** + * Convert an AIEvent to the real instance. + * @param instance The instance to convert. + * @return The converted instance. + */ + static AIEventVehicleWaitingInDepot *Convert(AIEvent *instance) { return (AIEventVehicleWaitingInDepot *)instance; } + + /** + * Get the VehicleID of the vehicle that is waiting in a depot. + * @return The VehicleID of the vehicle that is waiting in a depot. + */ + VehicleID GetVehicleID() { return vehicle_id; } + +private: + VehicleID vehicle_id; +}; + +/** + * Event Vehicle Unprofitable, indicating a vehicle lost money last year. + */ +class AIEventVehicleUnprofitable : public AIEvent { +public: + static const char *GetClassName() { return "AIEventVehicleUnprofitable"; } + + /** + * @param vehicle_id The vehicle that was unprofitable. + */ + AIEventVehicleUnprofitable(VehicleID vehicle_id) : + AIEvent(AI_ET_VEHICLE_UNPROFITABLE), + vehicle_id(vehicle_id) + {} + + /** + * Convert an AIEvent to the real instance. + * @param instance The instance to convert. + * @return The converted instance. + */ + static AIEventVehicleUnprofitable *Convert(AIEvent *instance) { return (AIEventVehicleUnprofitable *)instance; } + + /** + * Get the VehicleID of the vehicle that lost money. + * @return The VehicleID of the vehicle that lost money. + */ + VehicleID GetVehicleID() { return vehicle_id; } + +private: + VehicleID vehicle_id; +}; + +/** + * Event Industry Open, indicating a new industry has been created. + */ +class AIEventIndustryOpen : public AIEvent { +public: + static const char *GetClassName() { return "AIEventIndustryOpen"; } + + /** + * @param industry_id The new industry. + */ + AIEventIndustryOpen(IndustryID industry_id) : + AIEvent(AI_ET_INDUSTRY_OPEN), + industry_id(industry_id) + {} + + /** + * Convert an AIEvent to the real instance. + * @param instance The instance to convert. + * @return The converted instance. + */ + static AIEventIndustryOpen *Convert(AIEvent *instance) { return (AIEventIndustryOpen *)instance; } + + /** + * Get the IndustryID of the new industry. + * @return The IndustryID of the industry. + */ + IndustryID GetIndustryID() { return industry_id; } + +private: + IndustryID industry_id; +}; + +/** + * Event Industry Close, indicating an industry is going to be closed. + */ +class AIEventIndustryClose : public AIEvent { +public: + static const char *GetClassName() { return "AIEventIndustryClose"; } + + /** + * @param industry_id The new industry. + */ + AIEventIndustryClose(IndustryID industry_id) : + AIEvent(AI_ET_INDUSTRY_CLOSE), + industry_id(industry_id) + {} + + /** + * Convert an AIEvent to the real instance. + * @param instance The instance to convert. + * @return The converted instance. + */ + static AIEventIndustryClose *Convert(AIEvent *instance) { return (AIEventIndustryClose *)instance; } + + /** + * Get the IndustryID of the closing industry. + * @return The IndustryID of the industry. + */ + IndustryID GetIndustryID() { return industry_id; } + +private: + IndustryID industry_id; +}; + +/** + * Event Engine Available, indicating a new engine is available. + */ +class AIEventEngineAvailable : public AIEvent { +public: + static const char *GetClassName() { return "AIEventEngineAvailable"; } + + /** + * @param engine The engine that is available. + */ + AIEventEngineAvailable(EngineID engine) : + AIEvent(AI_ET_ENGINE_AVAILABLE), + engine(engine) + {} + + /** + * Convert an AIEvent to the real instance. + * @param instance The instance to convert. + * @return The converted instance. + */ + static AIEventEngineAvailable *Convert(AIEvent *instance) { return (AIEventEngineAvailable *)instance; } + + /** + * Get the EngineID of the new engine. + * @return The EngineID of the new engine. + */ + EngineID GetEngineID() { return engine; } + +private: + EngineID engine; +}; + +/** + * Event Station First Vehicle, indicating a station has been visited by a vehicle for the first time. + */ +class AIEventStationFirstVehicle : public AIEvent { +public: + static const char *GetClassName() { return "AIEventStationFirstVehicle"; } + + /** + * @param station The station visited for the first time. + * @param vehicle The vehicle visiting the station. + */ + AIEventStationFirstVehicle(StationID station, VehicleID vehicle) : + AIEvent(AI_ET_STATION_FIRST_VEHICLE), + station(station), + vehicle(vehicle) + {} + + /** + * Convert an AIEvent to the real instance. + * @param instance The instance to convert. + * @return The converted instance. + */ + static AIEventStationFirstVehicle *Convert(AIEvent *instance) { return (AIEventStationFirstVehicle *)instance; } + + /** + * Get the StationID of the visited station. + * @return The StationID of the visited station. + */ + StationID GetStationID() { return station; } + + /** + * Get the VehicleID of the first vehicle. + * @return The VehicleID of the first vehicle. + */ + VehicleID GetVehicleID() { return vehicle; } + +private: + StationID station; + VehicleID vehicle; +}; + +#endif /* AI_EVENT_TYPES_HPP */ diff --git a/src/ai/api/ai_event_types.hpp.sq b/src/ai/api/ai_event_types.hpp.sq new file mode 100644 index 000000000..84d8b722b --- /dev/null +++ b/src/ai/api/ai_event_types.hpp.sq @@ -0,0 +1,393 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_event_types.hpp" + +namespace SQConvert { + /* Allow AIEventTest to be used as Squirrel parameter */ + template <> AIEventTest *GetParam(ForceType<AIEventTest *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventTest *)instance; } + template <> AIEventTest &GetParam(ForceType<AIEventTest &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventTest *)instance; } + template <> const AIEventTest *GetParam(ForceType<const AIEventTest *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventTest *)instance; } + template <> const AIEventTest &GetParam(ForceType<const AIEventTest &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventTest *)instance; } + template <> int Return<AIEventTest *>(HSQUIRRELVM vm, AIEventTest *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIEventTest", res, NULL, DefSQDestructorCallback<AIEventTest>); return 1; } +}; // namespace SQConvert + +void SQAIEventTest_Register(Squirrel *engine) { + DefSQClass <AIEventTest> SQAIEventTest("AIEventTest"); + SQAIEventTest.PreRegister(engine, "AIEvent"); + + SQAIEventTest.DefSQStaticMethod(engine, &AIEventTest::GetClassName, "GetClassName", 1, "x"); + SQAIEventTest.DefSQStaticMethod(engine, &AIEventTest::Convert, "Convert", 2, "xx"); + + SQAIEventTest.DefSQMethod(engine, &AIEventTest::GetTest, "GetTest", 1, "x"); + + SQAIEventTest.PostRegister(engine); +} + +namespace SQConvert { + /* Allow AIEventVehicleCrashed to be used as Squirrel parameter */ + template <> AIEventVehicleCrashed *GetParam(ForceType<AIEventVehicleCrashed *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventVehicleCrashed *)instance; } + template <> AIEventVehicleCrashed &GetParam(ForceType<AIEventVehicleCrashed &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventVehicleCrashed *)instance; } + template <> const AIEventVehicleCrashed *GetParam(ForceType<const AIEventVehicleCrashed *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventVehicleCrashed *)instance; } + template <> const AIEventVehicleCrashed &GetParam(ForceType<const AIEventVehicleCrashed &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventVehicleCrashed *)instance; } + template <> int Return<AIEventVehicleCrashed *>(HSQUIRRELVM vm, AIEventVehicleCrashed *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIEventVehicleCrashed", res, NULL, DefSQDestructorCallback<AIEventVehicleCrashed>); return 1; } +}; // namespace SQConvert + +void SQAIEventVehicleCrashed_Register(Squirrel *engine) { + DefSQClass <AIEventVehicleCrashed> SQAIEventVehicleCrashed("AIEventVehicleCrashed"); + SQAIEventVehicleCrashed.PreRegister(engine, "AIEvent"); + + SQAIEventVehicleCrashed.DefSQStaticMethod(engine, &AIEventVehicleCrashed::GetClassName, "GetClassName", 1, "x"); + SQAIEventVehicleCrashed.DefSQStaticMethod(engine, &AIEventVehicleCrashed::Convert, "Convert", 2, "xx"); + + SQAIEventVehicleCrashed.DefSQMethod(engine, &AIEventVehicleCrashed::GetVehicleID, "GetVehicleID", 1, "x"); + SQAIEventVehicleCrashed.DefSQMethod(engine, &AIEventVehicleCrashed::GetCrashSite, "GetCrashSite", 1, "x"); + SQAIEventVehicleCrashed.DefSQMethod(engine, &AIEventVehicleCrashed::CloneCrashedVehicle, "CloneCrashedVehicle", 2, "xi"); + + SQAIEventVehicleCrashed.PostRegister(engine); +} + +namespace SQConvert { + /* Allow AIEventSubsidyOffer to be used as Squirrel parameter */ + template <> AIEventSubsidyOffer *GetParam(ForceType<AIEventSubsidyOffer *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventSubsidyOffer *)instance; } + template <> AIEventSubsidyOffer &GetParam(ForceType<AIEventSubsidyOffer &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventSubsidyOffer *)instance; } + template <> const AIEventSubsidyOffer *GetParam(ForceType<const AIEventSubsidyOffer *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventSubsidyOffer *)instance; } + template <> const AIEventSubsidyOffer &GetParam(ForceType<const AIEventSubsidyOffer &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventSubsidyOffer *)instance; } + template <> int Return<AIEventSubsidyOffer *>(HSQUIRRELVM vm, AIEventSubsidyOffer *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIEventSubsidyOffer", res, NULL, DefSQDestructorCallback<AIEventSubsidyOffer>); return 1; } +}; // namespace SQConvert + +void SQAIEventSubsidyOffer_Register(Squirrel *engine) { + DefSQClass <AIEventSubsidyOffer> SQAIEventSubsidyOffer("AIEventSubsidyOffer"); + SQAIEventSubsidyOffer.PreRegister(engine, "AIEvent"); + + SQAIEventSubsidyOffer.DefSQStaticMethod(engine, &AIEventSubsidyOffer::GetClassName, "GetClassName", 1, "x"); + SQAIEventSubsidyOffer.DefSQStaticMethod(engine, &AIEventSubsidyOffer::Convert, "Convert", 2, "xx"); + + SQAIEventSubsidyOffer.DefSQMethod(engine, &AIEventSubsidyOffer::GetSubsidyID, "GetSubsidyID", 1, "x"); + + SQAIEventSubsidyOffer.PostRegister(engine); +} + +namespace SQConvert { + /* Allow AIEventSubsidyOfferExpired to be used as Squirrel parameter */ + template <> AIEventSubsidyOfferExpired *GetParam(ForceType<AIEventSubsidyOfferExpired *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventSubsidyOfferExpired *)instance; } + template <> AIEventSubsidyOfferExpired &GetParam(ForceType<AIEventSubsidyOfferExpired &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventSubsidyOfferExpired *)instance; } + template <> const AIEventSubsidyOfferExpired *GetParam(ForceType<const AIEventSubsidyOfferExpired *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventSubsidyOfferExpired *)instance; } + template <> const AIEventSubsidyOfferExpired &GetParam(ForceType<const AIEventSubsidyOfferExpired &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventSubsidyOfferExpired *)instance; } + template <> int Return<AIEventSubsidyOfferExpired *>(HSQUIRRELVM vm, AIEventSubsidyOfferExpired *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIEventSubsidyOfferExpired", res, NULL, DefSQDestructorCallback<AIEventSubsidyOfferExpired>); return 1; } +}; // namespace SQConvert + +void SQAIEventSubsidyOfferExpired_Register(Squirrel *engine) { + DefSQClass <AIEventSubsidyOfferExpired> SQAIEventSubsidyOfferExpired("AIEventSubsidyOfferExpired"); + SQAIEventSubsidyOfferExpired.PreRegister(engine, "AIEvent"); + + SQAIEventSubsidyOfferExpired.DefSQStaticMethod(engine, &AIEventSubsidyOfferExpired::GetClassName, "GetClassName", 1, "x"); + SQAIEventSubsidyOfferExpired.DefSQStaticMethod(engine, &AIEventSubsidyOfferExpired::Convert, "Convert", 2, "xx"); + + SQAIEventSubsidyOfferExpired.DefSQMethod(engine, &AIEventSubsidyOfferExpired::GetSubsidyID, "GetSubsidyID", 1, "x"); + + SQAIEventSubsidyOfferExpired.PostRegister(engine); +} + +namespace SQConvert { + /* Allow AIEventSubsidyAwarded to be used as Squirrel parameter */ + template <> AIEventSubsidyAwarded *GetParam(ForceType<AIEventSubsidyAwarded *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventSubsidyAwarded *)instance; } + template <> AIEventSubsidyAwarded &GetParam(ForceType<AIEventSubsidyAwarded &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventSubsidyAwarded *)instance; } + template <> const AIEventSubsidyAwarded *GetParam(ForceType<const AIEventSubsidyAwarded *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventSubsidyAwarded *)instance; } + template <> const AIEventSubsidyAwarded &GetParam(ForceType<const AIEventSubsidyAwarded &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventSubsidyAwarded *)instance; } + template <> int Return<AIEventSubsidyAwarded *>(HSQUIRRELVM vm, AIEventSubsidyAwarded *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIEventSubsidyAwarded", res, NULL, DefSQDestructorCallback<AIEventSubsidyAwarded>); return 1; } +}; // namespace SQConvert + +void SQAIEventSubsidyAwarded_Register(Squirrel *engine) { + DefSQClass <AIEventSubsidyAwarded> SQAIEventSubsidyAwarded("AIEventSubsidyAwarded"); + SQAIEventSubsidyAwarded.PreRegister(engine, "AIEvent"); + + SQAIEventSubsidyAwarded.DefSQStaticMethod(engine, &AIEventSubsidyAwarded::GetClassName, "GetClassName", 1, "x"); + SQAIEventSubsidyAwarded.DefSQStaticMethod(engine, &AIEventSubsidyAwarded::Convert, "Convert", 2, "xx"); + + SQAIEventSubsidyAwarded.DefSQMethod(engine, &AIEventSubsidyAwarded::GetSubsidyID, "GetSubsidyID", 1, "x"); + + SQAIEventSubsidyAwarded.PostRegister(engine); +} + +namespace SQConvert { + /* Allow AIEventSubsidyExpired to be used as Squirrel parameter */ + template <> AIEventSubsidyExpired *GetParam(ForceType<AIEventSubsidyExpired *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventSubsidyExpired *)instance; } + template <> AIEventSubsidyExpired &GetParam(ForceType<AIEventSubsidyExpired &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventSubsidyExpired *)instance; } + template <> const AIEventSubsidyExpired *GetParam(ForceType<const AIEventSubsidyExpired *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventSubsidyExpired *)instance; } + template <> const AIEventSubsidyExpired &GetParam(ForceType<const AIEventSubsidyExpired &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventSubsidyExpired *)instance; } + template <> int Return<AIEventSubsidyExpired *>(HSQUIRRELVM vm, AIEventSubsidyExpired *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIEventSubsidyExpired", res, NULL, DefSQDestructorCallback<AIEventSubsidyExpired>); return 1; } +}; // namespace SQConvert + +void SQAIEventSubsidyExpired_Register(Squirrel *engine) { + DefSQClass <AIEventSubsidyExpired> SQAIEventSubsidyExpired("AIEventSubsidyExpired"); + SQAIEventSubsidyExpired.PreRegister(engine, "AIEvent"); + + SQAIEventSubsidyExpired.DefSQStaticMethod(engine, &AIEventSubsidyExpired::GetClassName, "GetClassName", 1, "x"); + SQAIEventSubsidyExpired.DefSQStaticMethod(engine, &AIEventSubsidyExpired::Convert, "Convert", 2, "xx"); + + SQAIEventSubsidyExpired.DefSQMethod(engine, &AIEventSubsidyExpired::GetSubsidyID, "GetSubsidyID", 1, "x"); + + SQAIEventSubsidyExpired.PostRegister(engine); +} + +namespace SQConvert { + /* Allow AIEventEnginePreview to be used as Squirrel parameter */ + template <> AIEventEnginePreview *GetParam(ForceType<AIEventEnginePreview *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventEnginePreview *)instance; } + template <> AIEventEnginePreview &GetParam(ForceType<AIEventEnginePreview &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventEnginePreview *)instance; } + template <> const AIEventEnginePreview *GetParam(ForceType<const AIEventEnginePreview *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventEnginePreview *)instance; } + template <> const AIEventEnginePreview &GetParam(ForceType<const AIEventEnginePreview &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventEnginePreview *)instance; } + template <> int Return<AIEventEnginePreview *>(HSQUIRRELVM vm, AIEventEnginePreview *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIEventEnginePreview", res, NULL, DefSQDestructorCallback<AIEventEnginePreview>); return 1; } +}; // namespace SQConvert + +void SQAIEventEnginePreview_Register(Squirrel *engine) { + DefSQClass <AIEventEnginePreview> SQAIEventEnginePreview("AIEventEnginePreview"); + SQAIEventEnginePreview.PreRegister(engine, "AIEvent"); + + SQAIEventEnginePreview.DefSQStaticMethod(engine, &AIEventEnginePreview::GetClassName, "GetClassName", 1, "x"); + SQAIEventEnginePreview.DefSQStaticMethod(engine, &AIEventEnginePreview::Convert, "Convert", 2, "xx"); + + SQAIEventEnginePreview.DefSQMethod(engine, &AIEventEnginePreview::GetName, "GetName", 1, "x"); + SQAIEventEnginePreview.DefSQMethod(engine, &AIEventEnginePreview::GetCargoType, "GetCargoType", 1, "x"); + SQAIEventEnginePreview.DefSQMethod(engine, &AIEventEnginePreview::GetCapacity, "GetCapacity", 1, "x"); + SQAIEventEnginePreview.DefSQMethod(engine, &AIEventEnginePreview::GetMaxSpeed, "GetMaxSpeed", 1, "x"); + SQAIEventEnginePreview.DefSQMethod(engine, &AIEventEnginePreview::GetPrice, "GetPrice", 1, "x"); + SQAIEventEnginePreview.DefSQMethod(engine, &AIEventEnginePreview::GetRunningCost, "GetRunningCost", 1, "x"); + SQAIEventEnginePreview.DefSQMethod(engine, &AIEventEnginePreview::GetVehicleType, "GetVehicleType", 1, "x"); + SQAIEventEnginePreview.DefSQMethod(engine, &AIEventEnginePreview::AcceptPreview, "AcceptPreview", 1, "x"); + + SQAIEventEnginePreview.PostRegister(engine); +} + +namespace SQConvert { + /* Allow AIEventCompanyNew to be used as Squirrel parameter */ + template <> AIEventCompanyNew *GetParam(ForceType<AIEventCompanyNew *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventCompanyNew *)instance; } + template <> AIEventCompanyNew &GetParam(ForceType<AIEventCompanyNew &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventCompanyNew *)instance; } + template <> const AIEventCompanyNew *GetParam(ForceType<const AIEventCompanyNew *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventCompanyNew *)instance; } + template <> const AIEventCompanyNew &GetParam(ForceType<const AIEventCompanyNew &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventCompanyNew *)instance; } + template <> int Return<AIEventCompanyNew *>(HSQUIRRELVM vm, AIEventCompanyNew *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIEventCompanyNew", res, NULL, DefSQDestructorCallback<AIEventCompanyNew>); return 1; } +}; // namespace SQConvert + +void SQAIEventCompanyNew_Register(Squirrel *engine) { + DefSQClass <AIEventCompanyNew> SQAIEventCompanyNew("AIEventCompanyNew"); + SQAIEventCompanyNew.PreRegister(engine, "AIEvent"); + + SQAIEventCompanyNew.DefSQStaticMethod(engine, &AIEventCompanyNew::GetClassName, "GetClassName", 1, "x"); + SQAIEventCompanyNew.DefSQStaticMethod(engine, &AIEventCompanyNew::Convert, "Convert", 2, "xx"); + + SQAIEventCompanyNew.DefSQMethod(engine, &AIEventCompanyNew::GetCompanyID, "GetCompanyID", 1, "x"); + + SQAIEventCompanyNew.PostRegister(engine); +} + +namespace SQConvert { + /* Allow AIEventCompanyInTrouble to be used as Squirrel parameter */ + template <> AIEventCompanyInTrouble *GetParam(ForceType<AIEventCompanyInTrouble *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventCompanyInTrouble *)instance; } + template <> AIEventCompanyInTrouble &GetParam(ForceType<AIEventCompanyInTrouble &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventCompanyInTrouble *)instance; } + template <> const AIEventCompanyInTrouble *GetParam(ForceType<const AIEventCompanyInTrouble *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventCompanyInTrouble *)instance; } + template <> const AIEventCompanyInTrouble &GetParam(ForceType<const AIEventCompanyInTrouble &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventCompanyInTrouble *)instance; } + template <> int Return<AIEventCompanyInTrouble *>(HSQUIRRELVM vm, AIEventCompanyInTrouble *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIEventCompanyInTrouble", res, NULL, DefSQDestructorCallback<AIEventCompanyInTrouble>); return 1; } +}; // namespace SQConvert + +void SQAIEventCompanyInTrouble_Register(Squirrel *engine) { + DefSQClass <AIEventCompanyInTrouble> SQAIEventCompanyInTrouble("AIEventCompanyInTrouble"); + SQAIEventCompanyInTrouble.PreRegister(engine, "AIEvent"); + + SQAIEventCompanyInTrouble.DefSQStaticMethod(engine, &AIEventCompanyInTrouble::GetClassName, "GetClassName", 1, "x"); + SQAIEventCompanyInTrouble.DefSQStaticMethod(engine, &AIEventCompanyInTrouble::Convert, "Convert", 2, "xx"); + + SQAIEventCompanyInTrouble.DefSQMethod(engine, &AIEventCompanyInTrouble::GetCompanyID, "GetCompanyID", 1, "x"); + + SQAIEventCompanyInTrouble.PostRegister(engine); +} + +namespace SQConvert { + /* Allow AIEventCompanyMerger to be used as Squirrel parameter */ + template <> AIEventCompanyMerger *GetParam(ForceType<AIEventCompanyMerger *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventCompanyMerger *)instance; } + template <> AIEventCompanyMerger &GetParam(ForceType<AIEventCompanyMerger &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventCompanyMerger *)instance; } + template <> const AIEventCompanyMerger *GetParam(ForceType<const AIEventCompanyMerger *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventCompanyMerger *)instance; } + template <> const AIEventCompanyMerger &GetParam(ForceType<const AIEventCompanyMerger &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventCompanyMerger *)instance; } + template <> int Return<AIEventCompanyMerger *>(HSQUIRRELVM vm, AIEventCompanyMerger *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIEventCompanyMerger", res, NULL, DefSQDestructorCallback<AIEventCompanyMerger>); return 1; } +}; // namespace SQConvert + +void SQAIEventCompanyMerger_Register(Squirrel *engine) { + DefSQClass <AIEventCompanyMerger> SQAIEventCompanyMerger("AIEventCompanyMerger"); + SQAIEventCompanyMerger.PreRegister(engine, "AIEvent"); + + SQAIEventCompanyMerger.DefSQStaticMethod(engine, &AIEventCompanyMerger::GetClassName, "GetClassName", 1, "x"); + SQAIEventCompanyMerger.DefSQStaticMethod(engine, &AIEventCompanyMerger::Convert, "Convert", 2, "xx"); + + SQAIEventCompanyMerger.DefSQMethod(engine, &AIEventCompanyMerger::GetOldCompanyID, "GetOldCompanyID", 1, "x"); + SQAIEventCompanyMerger.DefSQMethod(engine, &AIEventCompanyMerger::GetNewCompanyID, "GetNewCompanyID", 1, "x"); + + SQAIEventCompanyMerger.PostRegister(engine); +} + +namespace SQConvert { + /* Allow AIEventCompanyBankrupt to be used as Squirrel parameter */ + template <> AIEventCompanyBankrupt *GetParam(ForceType<AIEventCompanyBankrupt *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventCompanyBankrupt *)instance; } + template <> AIEventCompanyBankrupt &GetParam(ForceType<AIEventCompanyBankrupt &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventCompanyBankrupt *)instance; } + template <> const AIEventCompanyBankrupt *GetParam(ForceType<const AIEventCompanyBankrupt *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventCompanyBankrupt *)instance; } + template <> const AIEventCompanyBankrupt &GetParam(ForceType<const AIEventCompanyBankrupt &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventCompanyBankrupt *)instance; } + template <> int Return<AIEventCompanyBankrupt *>(HSQUIRRELVM vm, AIEventCompanyBankrupt *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIEventCompanyBankrupt", res, NULL, DefSQDestructorCallback<AIEventCompanyBankrupt>); return 1; } +}; // namespace SQConvert + +void SQAIEventCompanyBankrupt_Register(Squirrel *engine) { + DefSQClass <AIEventCompanyBankrupt> SQAIEventCompanyBankrupt("AIEventCompanyBankrupt"); + SQAIEventCompanyBankrupt.PreRegister(engine, "AIEvent"); + + SQAIEventCompanyBankrupt.DefSQStaticMethod(engine, &AIEventCompanyBankrupt::GetClassName, "GetClassName", 1, "x"); + SQAIEventCompanyBankrupt.DefSQStaticMethod(engine, &AIEventCompanyBankrupt::Convert, "Convert", 2, "xx"); + + SQAIEventCompanyBankrupt.DefSQMethod(engine, &AIEventCompanyBankrupt::GetCompanyID, "GetCompanyID", 1, "x"); + + SQAIEventCompanyBankrupt.PostRegister(engine); +} + +namespace SQConvert { + /* Allow AIEventVehicleLost to be used as Squirrel parameter */ + template <> AIEventVehicleLost *GetParam(ForceType<AIEventVehicleLost *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventVehicleLost *)instance; } + template <> AIEventVehicleLost &GetParam(ForceType<AIEventVehicleLost &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventVehicleLost *)instance; } + template <> const AIEventVehicleLost *GetParam(ForceType<const AIEventVehicleLost *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventVehicleLost *)instance; } + template <> const AIEventVehicleLost &GetParam(ForceType<const AIEventVehicleLost &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventVehicleLost *)instance; } + template <> int Return<AIEventVehicleLost *>(HSQUIRRELVM vm, AIEventVehicleLost *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIEventVehicleLost", res, NULL, DefSQDestructorCallback<AIEventVehicleLost>); return 1; } +}; // namespace SQConvert + +void SQAIEventVehicleLost_Register(Squirrel *engine) { + DefSQClass <AIEventVehicleLost> SQAIEventVehicleLost("AIEventVehicleLost"); + SQAIEventVehicleLost.PreRegister(engine, "AIEvent"); + + SQAIEventVehicleLost.DefSQStaticMethod(engine, &AIEventVehicleLost::GetClassName, "GetClassName", 1, "x"); + SQAIEventVehicleLost.DefSQStaticMethod(engine, &AIEventVehicleLost::Convert, "Convert", 2, "xx"); + + SQAIEventVehicleLost.DefSQMethod(engine, &AIEventVehicleLost::GetVehicleID, "GetVehicleID", 1, "x"); + + SQAIEventVehicleLost.PostRegister(engine); +} + +namespace SQConvert { + /* Allow AIEventVehicleWaitingInDepot to be used as Squirrel parameter */ + template <> AIEventVehicleWaitingInDepot *GetParam(ForceType<AIEventVehicleWaitingInDepot *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventVehicleWaitingInDepot *)instance; } + template <> AIEventVehicleWaitingInDepot &GetParam(ForceType<AIEventVehicleWaitingInDepot &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventVehicleWaitingInDepot *)instance; } + template <> const AIEventVehicleWaitingInDepot *GetParam(ForceType<const AIEventVehicleWaitingInDepot *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventVehicleWaitingInDepot *)instance; } + template <> const AIEventVehicleWaitingInDepot &GetParam(ForceType<const AIEventVehicleWaitingInDepot &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventVehicleWaitingInDepot *)instance; } + template <> int Return<AIEventVehicleWaitingInDepot *>(HSQUIRRELVM vm, AIEventVehicleWaitingInDepot *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIEventVehicleWaitingInDepot", res, NULL, DefSQDestructorCallback<AIEventVehicleWaitingInDepot>); return 1; } +}; // namespace SQConvert + +void SQAIEventVehicleWaitingInDepot_Register(Squirrel *engine) { + DefSQClass <AIEventVehicleWaitingInDepot> SQAIEventVehicleWaitingInDepot("AIEventVehicleWaitingInDepot"); + SQAIEventVehicleWaitingInDepot.PreRegister(engine, "AIEvent"); + + SQAIEventVehicleWaitingInDepot.DefSQStaticMethod(engine, &AIEventVehicleWaitingInDepot::GetClassName, "GetClassName", 1, "x"); + SQAIEventVehicleWaitingInDepot.DefSQStaticMethod(engine, &AIEventVehicleWaitingInDepot::Convert, "Convert", 2, "xx"); + + SQAIEventVehicleWaitingInDepot.DefSQMethod(engine, &AIEventVehicleWaitingInDepot::GetVehicleID, "GetVehicleID", 1, "x"); + + SQAIEventVehicleWaitingInDepot.PostRegister(engine); +} + +namespace SQConvert { + /* Allow AIEventVehicleUnprofitable to be used as Squirrel parameter */ + template <> AIEventVehicleUnprofitable *GetParam(ForceType<AIEventVehicleUnprofitable *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventVehicleUnprofitable *)instance; } + template <> AIEventVehicleUnprofitable &GetParam(ForceType<AIEventVehicleUnprofitable &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventVehicleUnprofitable *)instance; } + template <> const AIEventVehicleUnprofitable *GetParam(ForceType<const AIEventVehicleUnprofitable *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventVehicleUnprofitable *)instance; } + template <> const AIEventVehicleUnprofitable &GetParam(ForceType<const AIEventVehicleUnprofitable &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventVehicleUnprofitable *)instance; } + template <> int Return<AIEventVehicleUnprofitable *>(HSQUIRRELVM vm, AIEventVehicleUnprofitable *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIEventVehicleUnprofitable", res, NULL, DefSQDestructorCallback<AIEventVehicleUnprofitable>); return 1; } +}; // namespace SQConvert + +void SQAIEventVehicleUnprofitable_Register(Squirrel *engine) { + DefSQClass <AIEventVehicleUnprofitable> SQAIEventVehicleUnprofitable("AIEventVehicleUnprofitable"); + SQAIEventVehicleUnprofitable.PreRegister(engine, "AIEvent"); + + SQAIEventVehicleUnprofitable.DefSQStaticMethod(engine, &AIEventVehicleUnprofitable::GetClassName, "GetClassName", 1, "x"); + SQAIEventVehicleUnprofitable.DefSQStaticMethod(engine, &AIEventVehicleUnprofitable::Convert, "Convert", 2, "xx"); + + SQAIEventVehicleUnprofitable.DefSQMethod(engine, &AIEventVehicleUnprofitable::GetVehicleID, "GetVehicleID", 1, "x"); + + SQAIEventVehicleUnprofitable.PostRegister(engine); +} + +namespace SQConvert { + /* Allow AIEventIndustryOpen to be used as Squirrel parameter */ + template <> AIEventIndustryOpen *GetParam(ForceType<AIEventIndustryOpen *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventIndustryOpen *)instance; } + template <> AIEventIndustryOpen &GetParam(ForceType<AIEventIndustryOpen &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventIndustryOpen *)instance; } + template <> const AIEventIndustryOpen *GetParam(ForceType<const AIEventIndustryOpen *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventIndustryOpen *)instance; } + template <> const AIEventIndustryOpen &GetParam(ForceType<const AIEventIndustryOpen &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventIndustryOpen *)instance; } + template <> int Return<AIEventIndustryOpen *>(HSQUIRRELVM vm, AIEventIndustryOpen *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIEventIndustryOpen", res, NULL, DefSQDestructorCallback<AIEventIndustryOpen>); return 1; } +}; // namespace SQConvert + +void SQAIEventIndustryOpen_Register(Squirrel *engine) { + DefSQClass <AIEventIndustryOpen> SQAIEventIndustryOpen("AIEventIndustryOpen"); + SQAIEventIndustryOpen.PreRegister(engine, "AIEvent"); + + SQAIEventIndustryOpen.DefSQStaticMethod(engine, &AIEventIndustryOpen::GetClassName, "GetClassName", 1, "x"); + SQAIEventIndustryOpen.DefSQStaticMethod(engine, &AIEventIndustryOpen::Convert, "Convert", 2, "xx"); + + SQAIEventIndustryOpen.DefSQMethod(engine, &AIEventIndustryOpen::GetIndustryID, "GetIndustryID", 1, "x"); + + SQAIEventIndustryOpen.PostRegister(engine); +} + +namespace SQConvert { + /* Allow AIEventIndustryClose to be used as Squirrel parameter */ + template <> AIEventIndustryClose *GetParam(ForceType<AIEventIndustryClose *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventIndustryClose *)instance; } + template <> AIEventIndustryClose &GetParam(ForceType<AIEventIndustryClose &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventIndustryClose *)instance; } + template <> const AIEventIndustryClose *GetParam(ForceType<const AIEventIndustryClose *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventIndustryClose *)instance; } + template <> const AIEventIndustryClose &GetParam(ForceType<const AIEventIndustryClose &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventIndustryClose *)instance; } + template <> int Return<AIEventIndustryClose *>(HSQUIRRELVM vm, AIEventIndustryClose *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIEventIndustryClose", res, NULL, DefSQDestructorCallback<AIEventIndustryClose>); return 1; } +}; // namespace SQConvert + +void SQAIEventIndustryClose_Register(Squirrel *engine) { + DefSQClass <AIEventIndustryClose> SQAIEventIndustryClose("AIEventIndustryClose"); + SQAIEventIndustryClose.PreRegister(engine, "AIEvent"); + + SQAIEventIndustryClose.DefSQStaticMethod(engine, &AIEventIndustryClose::GetClassName, "GetClassName", 1, "x"); + SQAIEventIndustryClose.DefSQStaticMethod(engine, &AIEventIndustryClose::Convert, "Convert", 2, "xx"); + + SQAIEventIndustryClose.DefSQMethod(engine, &AIEventIndustryClose::GetIndustryID, "GetIndustryID", 1, "x"); + + SQAIEventIndustryClose.PostRegister(engine); +} + +namespace SQConvert { + /* Allow AIEventEngineAvailable to be used as Squirrel parameter */ + template <> AIEventEngineAvailable *GetParam(ForceType<AIEventEngineAvailable *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventEngineAvailable *)instance; } + template <> AIEventEngineAvailable &GetParam(ForceType<AIEventEngineAvailable &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventEngineAvailable *)instance; } + template <> const AIEventEngineAvailable *GetParam(ForceType<const AIEventEngineAvailable *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventEngineAvailable *)instance; } + template <> const AIEventEngineAvailable &GetParam(ForceType<const AIEventEngineAvailable &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventEngineAvailable *)instance; } + template <> int Return<AIEventEngineAvailable *>(HSQUIRRELVM vm, AIEventEngineAvailable *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIEventEngineAvailable", res, NULL, DefSQDestructorCallback<AIEventEngineAvailable>); return 1; } +}; // namespace SQConvert + +void SQAIEventEngineAvailable_Register(Squirrel *engine) { + DefSQClass <AIEventEngineAvailable> SQAIEventEngineAvailable("AIEventEngineAvailable"); + SQAIEventEngineAvailable.PreRegister(engine, "AIEvent"); + + SQAIEventEngineAvailable.DefSQStaticMethod(engine, &AIEventEngineAvailable::GetClassName, "GetClassName", 1, "x"); + SQAIEventEngineAvailable.DefSQStaticMethod(engine, &AIEventEngineAvailable::Convert, "Convert", 2, "xx"); + + SQAIEventEngineAvailable.DefSQMethod(engine, &AIEventEngineAvailable::GetEngineID, "GetEngineID", 1, "x"); + + SQAIEventEngineAvailable.PostRegister(engine); +} + +namespace SQConvert { + /* Allow AIEventStationFirstVehicle to be used as Squirrel parameter */ + template <> AIEventStationFirstVehicle *GetParam(ForceType<AIEventStationFirstVehicle *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventStationFirstVehicle *)instance; } + template <> AIEventStationFirstVehicle &GetParam(ForceType<AIEventStationFirstVehicle &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventStationFirstVehicle *)instance; } + template <> const AIEventStationFirstVehicle *GetParam(ForceType<const AIEventStationFirstVehicle *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIEventStationFirstVehicle *)instance; } + template <> const AIEventStationFirstVehicle &GetParam(ForceType<const AIEventStationFirstVehicle &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIEventStationFirstVehicle *)instance; } + template <> int Return<AIEventStationFirstVehicle *>(HSQUIRRELVM vm, AIEventStationFirstVehicle *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIEventStationFirstVehicle", res, NULL, DefSQDestructorCallback<AIEventStationFirstVehicle>); return 1; } +}; // namespace SQConvert + +void SQAIEventStationFirstVehicle_Register(Squirrel *engine) { + DefSQClass <AIEventStationFirstVehicle> SQAIEventStationFirstVehicle("AIEventStationFirstVehicle"); + SQAIEventStationFirstVehicle.PreRegister(engine, "AIEvent"); + + SQAIEventStationFirstVehicle.DefSQStaticMethod(engine, &AIEventStationFirstVehicle::GetClassName, "GetClassName", 1, "x"); + SQAIEventStationFirstVehicle.DefSQStaticMethod(engine, &AIEventStationFirstVehicle::Convert, "Convert", 2, "xx"); + + SQAIEventStationFirstVehicle.DefSQMethod(engine, &AIEventStationFirstVehicle::GetStationID, "GetStationID", 1, "x"); + SQAIEventStationFirstVehicle.DefSQMethod(engine, &AIEventStationFirstVehicle::GetVehicleID, "GetVehicleID", 1, "x"); + + SQAIEventStationFirstVehicle.PostRegister(engine); +} diff --git a/src/ai/api/ai_execmode.cpp b/src/ai/api/ai_execmode.cpp new file mode 100644 index 000000000..941718bee --- /dev/null +++ b/src/ai/api/ai_execmode.cpp @@ -0,0 +1,26 @@ +/* $Id$ */ + +/** @file ai_execmode.cpp Implementation of AIExecMode. */ + +#include "ai_execmode.hpp" +#include "../../command_type.h" + +bool AIExecMode::ModeProc(TileIndex tile, uint32 p1, uint32 p2, uint procc, CommandCost costs) +{ + /* In execution mode we only return 'true', telling the DoCommand it + * should continue with the real execution of the command. */ + return true; +} + +AIExecMode::AIExecMode() +{ + this->last_mode = this->GetDoCommandMode(); + this->last_instance = this->GetDoCommandModeInstance(); + this->SetDoCommandMode(&AIExecMode::ModeProc, this); +} + +AIExecMode::~AIExecMode() +{ + assert(this->GetDoCommandModeInstance() == this); + this->SetDoCommandMode(this->last_mode, this->last_instance); +} diff --git a/src/ai/api/ai_execmode.hpp b/src/ai/api/ai_execmode.hpp new file mode 100644 index 000000000..4f6a47bc4 --- /dev/null +++ b/src/ai/api/ai_execmode.hpp @@ -0,0 +1,46 @@ +/* $Id$ */ + +/** @file ai_execmode.hpp Switch the AI to Execute Mode. */ + +#ifndef AI_EXECMODE_HPP +#define AI_EXECMODE_HPP + +#include "ai_object.hpp" + +/** + * Class to switch current mode to Execute Mode. + * If you create an instance of this class, the mode will be switched to + * Execute. The original mode is stored and recovered from when ever the + * instance is destroyed. + * In Execute mode all commands you do are executed for real. + */ +class AIExecMode : public AIObject { +public: + static const char *GetClassName() { return "AIExecMode"; } + +private: + AIModeProc *last_mode; + AIObject *last_instance; + +protected: + /** + * The callback proc for Execute mode. + */ + static bool ModeProc(TileIndex tile, uint32 p1, uint32 p2, uint procc, CommandCost costs); + +public: + /** + * Creating instance of this class switches the build mode to Execute. + * @note When the instance is destroyed, he restores the mode that was + * current when the instance was created! + */ + AIExecMode(); + + /** + * Destroying this instance reset the building mode to the mode it was + * in when the instance was created. + */ + ~AIExecMode(); +}; + +#endif /* AI_EXECMODE_HPP */ diff --git a/src/ai/api/ai_execmode.hpp.sq b/src/ai/api/ai_execmode.hpp.sq new file mode 100644 index 000000000..de78e9b6b --- /dev/null +++ b/src/ai/api/ai_execmode.hpp.sq @@ -0,0 +1,23 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_execmode.hpp" + +namespace SQConvert { + /* Allow AIExecMode to be used as Squirrel parameter */ + template <> AIExecMode *GetParam(ForceType<AIExecMode *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIExecMode *)instance; } + template <> AIExecMode &GetParam(ForceType<AIExecMode &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIExecMode *)instance; } + template <> const AIExecMode *GetParam(ForceType<const AIExecMode *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIExecMode *)instance; } + template <> const AIExecMode &GetParam(ForceType<const AIExecMode &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIExecMode *)instance; } + template <> int Return<AIExecMode *>(HSQUIRRELVM vm, AIExecMode *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIExecMode", res, NULL, DefSQDestructorCallback<AIExecMode>); return 1; } +}; // namespace SQConvert + +void SQAIExecMode_Register(Squirrel *engine) { + DefSQClass <AIExecMode> SQAIExecMode("AIExecMode"); + SQAIExecMode.PreRegister(engine); + SQAIExecMode.AddConstructor<void (AIExecMode::*)(), 1>(engine, "x"); + + SQAIExecMode.DefSQStaticMethod(engine, &AIExecMode::GetClassName, "GetClassName", 1, "x"); + + SQAIExecMode.PostRegister(engine); +} diff --git a/src/ai/api/ai_gamesettings.cpp b/src/ai/api/ai_gamesettings.cpp new file mode 100644 index 000000000..81b187d73 --- /dev/null +++ b/src/ai/api/ai_gamesettings.cpp @@ -0,0 +1,38 @@ +/* $Id$ */ + +/** @file ai_gamesettings.cpp Implementation of AIGameSettings. */ + +#include "ai_gamesettings.hpp" +#include "../../settings_internal.h" +#include "../../saveload/saveload.h" + +/* static */ bool AIGameSettings::IsValid(const char *setting) +{ + uint i; + const SettingDesc *sd = GetPatchFromName(setting, &i); + return sd != NULL && sd->desc.cmd != SDT_STRING; +} + +/* static */ int32 AIGameSettings::GetValue(const char *setting) +{ + if (!IsValid(setting)) return -1; + + uint i; + const SettingDesc *sd = GetPatchFromName(setting, &i); + + void *ptr = GetVariableAddress(&_settings_game, &sd->save); + if (sd->desc.cmd == SDT_BOOLX) return *(bool*)ptr; + + return (int32)ReadValue(ptr, sd->save.conv); +} + +/* static */ bool AIGameSettings::IsDisabledVehicleType(AIVehicle::VehicleType vehicle_type) +{ + switch (vehicle_type) { + case AIVehicle::VEHICLE_RAIL: return _settings_game.ai.ai_disable_veh_train; + case AIVehicle::VEHICLE_ROAD: return _settings_game.ai.ai_disable_veh_roadveh; + case AIVehicle::VEHICLE_WATER: return _settings_game.ai.ai_disable_veh_ship; + case AIVehicle::VEHICLE_AIR: return _settings_game.ai.ai_disable_veh_aircraft; + default: return true; + } +} diff --git a/src/ai/api/ai_gamesettings.hpp b/src/ai/api/ai_gamesettings.hpp new file mode 100644 index 000000000..67c8d964e --- /dev/null +++ b/src/ai/api/ai_gamesettings.hpp @@ -0,0 +1,67 @@ +/* $Id$ */ + +/** @file ai_gamesettings.hpp Everything to read game settings. */ + +#ifndef AI_GAMESETTINGS_HPP +#define AI_GAMESETTINGS_HPP + +#include "ai_object.hpp" +#include "ai_vehicle.hpp" + +/** + * Class that handles all game settings related functions. + * + * @note AIGameSettings::IsValid and AIGameSettings::GetValue are functions + * that rely on the settings as OpenTTD stores them in savegame and + * openttd.cfg. No guarantees can be given on the long term validity, + * consistency and stability of the names, values and value ranges. + * Using these settings can be dangerous and could cause issues in + * future versions. To make sure that a setting still exists in the + * current version you have to run AIGameSettings::IsValid before + * accessing it. + * + * @note The names of the setting for AIGameSettings::IsValid and + * AIGameSettings::GetValue are the same ones as those that are shown by + * the list_patches command in the in-game console. Settings that are + * string based are NOT supported and AIGAmeSettings::IsValid will return + * false for them. These settings will not be supported either because + * they have no relevance for the AI (default client names, server IPs, + * etc.). + */ +class AIGameSettings : public AIObject { +public: + static const char *GetClassName() { return "AIGameSettings"; } + + /** + * Is the given game setting a valid setting for this instance of OpenTTD? + * @param setting The setting to check for existence. + * @warning Results of this function are not governed by the API. This means + * that a setting that previously existed can be gone or has + * changed it's name. + * @note Results achieved in the past offer no gurantee for the future. + * @return True if and only if the setting is valid. + */ + static bool IsValid(const char *setting); + + /** + * Gets the value of the game setting. + * @param setting The setting to get the value of. + * @pre IsValid(setting). + * @warning Results of this function are not governed by the API. This means + * that the value of settings may be out of the expected range. It + * also means that a setting that previously existed can be gone or + * has changed it's name/characteristics. + * @note Results achieved in the past offer no gurantee for the future. + * @return The value for the setting. + */ + static int32 GetValue(const char *setting); + + /** + * Checks whether the given vehicle-type is disabled for AIs. + * @param vehicle_type The vehicle-type to check. + * @return True if the vehicle-type is disabled. + */ + static bool IsDisabledVehicleType(AIVehicle::VehicleType vehicle_type); +}; + +#endif /* AI_GAMESETTINGS_HPP */ diff --git a/src/ai/api/ai_gamesettings.hpp.sq b/src/ai/api/ai_gamesettings.hpp.sq new file mode 100644 index 000000000..e21812c08 --- /dev/null +++ b/src/ai/api/ai_gamesettings.hpp.sq @@ -0,0 +1,26 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_gamesettings.hpp" + +namespace SQConvert { + /* Allow AIGameSettings to be used as Squirrel parameter */ + template <> AIGameSettings *GetParam(ForceType<AIGameSettings *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIGameSettings *)instance; } + template <> AIGameSettings &GetParam(ForceType<AIGameSettings &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIGameSettings *)instance; } + template <> const AIGameSettings *GetParam(ForceType<const AIGameSettings *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIGameSettings *)instance; } + template <> const AIGameSettings &GetParam(ForceType<const AIGameSettings &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIGameSettings *)instance; } + template <> int Return<AIGameSettings *>(HSQUIRRELVM vm, AIGameSettings *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIGameSettings", res, NULL, DefSQDestructorCallback<AIGameSettings>); return 1; } +}; // namespace SQConvert + +void SQAIGameSettings_Register(Squirrel *engine) { + DefSQClass <AIGameSettings> SQAIGameSettings("AIGameSettings"); + SQAIGameSettings.PreRegister(engine); + SQAIGameSettings.AddConstructor<void (AIGameSettings::*)(), 1>(engine, "x"); + + SQAIGameSettings.DefSQStaticMethod(engine, &AIGameSettings::GetClassName, "GetClassName", 1, "x"); + SQAIGameSettings.DefSQStaticMethod(engine, &AIGameSettings::IsValid, "IsValid", 2, "xs"); + SQAIGameSettings.DefSQStaticMethod(engine, &AIGameSettings::GetValue, "GetValue", 2, "xs"); + SQAIGameSettings.DefSQStaticMethod(engine, &AIGameSettings::IsDisabledVehicleType, "IsDisabledVehicleType", 2, "xi"); + + SQAIGameSettings.PostRegister(engine); +} diff --git a/src/ai/api/ai_group.cpp b/src/ai/api/ai_group.cpp new file mode 100644 index 000000000..a6365450b --- /dev/null +++ b/src/ai/api/ai_group.cpp @@ -0,0 +1,128 @@ +/* $Id$ */ + +/** @file ai_group.cpp Implementation of AIGroup. */ + +#include "ai_group.hpp" +#include "ai_vehicle.hpp" +#include "ai_engine.hpp" +#include "../ai_instance.hpp" +#include "../../openttd.h" +#include "../../company_func.h" +#include "../../group.h" +#include "../../string_func.h" +#include "../../strings_func.h" +#include "../../core/alloc_func.hpp" +#include "../../command_func.h" +#include "../../autoreplace_func.h" +#include "table/strings.h" + +/* static */ bool AIGroup::IsValidGroup(GroupID group_id) +{ + return ::IsValidGroupID(group_id) && ::GetGroup(group_id)->owner == _current_company; +} + +/* static */ AIGroup::GroupID AIGroup::CreateGroup(AIVehicle::VehicleType vehicle_type) +{ + if (!AIObject::DoCommand(0, (::VehicleType)vehicle_type, 0, CMD_CREATE_GROUP, NULL, &AIInstance::DoCommandReturnGroupID)) return INVALID_GROUP; + + /* In case of test-mode, we return GroupID 0 */ + return (AIGroup::GroupID)0; +} + +/* static */ bool AIGroup::DeleteGroup(GroupID group_id) +{ + EnforcePrecondition(false, IsValidGroup(group_id)); + + return AIObject::DoCommand(0, group_id, 0, CMD_DELETE_GROUP); +} + +/* static */ AIVehicle::VehicleType AIGroup::GetVehicleType(GroupID group_id) +{ + if (!IsValidGroup(group_id)) return AIVehicle::VEHICLE_INVALID; + + return (AIVehicle::VehicleType)((::VehicleType)::GetGroup(group_id)->vehicle_type); +} + +/* static */ bool AIGroup::SetName(GroupID group_id, const char *name) +{ + EnforcePrecondition(false, IsValidGroup(group_id)); + EnforcePrecondition(false, !::StrEmpty(name)); + EnforcePreconditionCustomError(false, ::strlen(name) < MAX_LENGTH_GROUP_NAME_BYTES, AIError::ERR_PRECONDITION_STRING_TOO_LONG); + + return AIObject::DoCommand(0, group_id, 0, CMD_RENAME_GROUP, name); +} + +/* static */ const char *AIGroup::GetName(GroupID group_id) +{ + if (!IsValidGroup(group_id)) return NULL; + + static const int len = 64; + char *group_name = MallocT<char>(len); + + ::SetDParam(0, group_id); + ::GetString(group_name, STR_GROUP_NAME, &group_name[len - 1]); + return group_name; +} + +/* static */ bool AIGroup::EnableAutoReplaceProtection(GroupID group_id, bool enable) +{ + EnforcePrecondition(false, IsValidGroup(group_id)); + + return AIObject::DoCommand(0, group_id, enable ? 1 : 0, CMD_SET_GROUP_REPLACE_PROTECTION); +} + +/* static */ bool AIGroup::GetAutoReplaceProtection(GroupID group_id) +{ + if (!IsValidGroup(group_id)) return false; + + return ::GetGroup(group_id)->replace_protection; +} + +/* static */ int32 AIGroup::GetNumEngines(GroupID group_id, EngineID engine_id) +{ + if (!IsValidGroup(group_id) && group_id != DEFAULT_GROUP && group_id != ALL_GROUP) return -1; + + return GetGroupNumEngines(_current_company, group_id, engine_id); +} + +/* static */ bool AIGroup::MoveVehicle(GroupID group_id, VehicleID vehicle_id) +{ + EnforcePrecondition(false, IsValidGroup(group_id) || group_id == DEFAULT_GROUP); + EnforcePrecondition(false, AIVehicle::IsValidVehicle(vehicle_id)); + + return AIObject::DoCommand(0, group_id, vehicle_id, CMD_ADD_VEHICLE_GROUP); +} + +/* static */ bool AIGroup::EnableWagonRemoval(bool enable_removal) +{ + if (HasWagonRemoval() == enable_removal) return true; + + return AIObject::DoCommand(0, 5, enable_removal ? 1 : 0, CMD_SET_AUTOREPLACE); +} + +/* static */ bool AIGroup::HasWagonRemoval() +{ + return ::GetCompany(_current_company)->renew_keep_length; +} + +/* static */ bool AIGroup::SetAutoReplace(GroupID group_id, EngineID engine_id_old, EngineID engine_id_new) +{ + EnforcePrecondition(false, IsValidGroup(group_id) || group_id == ALL_GROUP); + EnforcePrecondition(false, AIEngine::IsValidEngine(engine_id_new)); + + return AIObject::DoCommand(0, 3 | (group_id << 16), (engine_id_new << 16) | engine_id_old, CMD_SET_AUTOREPLACE); +} + +/* static */ EngineID AIGroup::GetEngineReplacement(GroupID group_id, EngineID engine_id) +{ + if (!IsValidGroup(group_id) && group_id != ALL_GROUP) return ::INVALID_ENGINE; + + return ::EngineReplacementForCompany(GetCompany(_current_company), engine_id, group_id); +} + +/* static */ bool AIGroup::StopAutoReplace(GroupID group_id, EngineID engine_id) +{ + EnforcePrecondition(false, IsValidGroup(group_id) || group_id == ALL_GROUP); + + return AIObject::DoCommand(0, 3 | (group_id << 16), (::INVALID_ENGINE << 16) | engine_id, CMD_SET_AUTOREPLACE); +} diff --git a/src/ai/api/ai_group.hpp b/src/ai/api/ai_group.hpp new file mode 100644 index 000000000..84569c562 --- /dev/null +++ b/src/ai/api/ai_group.hpp @@ -0,0 +1,170 @@ +/* $Id$ */ + +/** @file ai_group.hpp Everything to put vehicles into groups. */ + +#ifndef AI_GROUP_HPP +#define AI_GROUP_HPP + +#include "ai_object.hpp" +#include "ai_vehicle.hpp" + +/** + * Class that handles all group related functions. + */ +class AIGroup : public AIObject { +public: + static const char *GetClassName() { return "AIGroup"; } + + /** + * The group IDs of some special groups. + */ + enum GroupID { + /* Values are important, as they represent the internal state of the game (see group_type.h). */ + ALL_GROUP = 0xFFFD, //!< All vehicles are in this group. + DEFAULT_GROUP = 0xFFFE, //!< Vehicles not put in any other group are in this one. + INVALID_GROUP = 0xFFFF, //!< An invalid group id. + }; + + /** + * Checks whether the given group is valid. + * @param group_id The group to check. + * @pre group_id != DEFAULT_GROUP && group_id != ALL_GROUP. + * @return True if and only if the group is valid. + */ + static bool IsValidGroup(GroupID group_id); + + /** + * Create a new group. + * @param vehicle_type The type of vehicle to create a group for. + * @return The GroupID of the new group, or an invalid GroupID when + * it failed. Check the return value using IsValidGroup(). In test-mode + * 0 is returned if it was successful; any other value indicates failure. + */ + static GroupID CreateGroup(AIVehicle::VehicleType vehicle_type); + + /** + * Delete the given group. When the deletion succeeds all vehicles in the + * given group will move to the DEFAULT_GROUP. + * @param group_id The group to delete. + * @pre IsValidGroup(group_id). + * @return True if and only if the group was succesfully deleted. + */ + static bool DeleteGroup(GroupID group_id); + + /** + * Get the vehicle type of a group. + * @param group_id The group to get the type from. + * @pre IsValidGroup(group_id). + * @return The vehicletype of the given group. + */ + static AIVehicle::VehicleType GetVehicleType(GroupID group_id); + + /** + * Set the name of a group. + * @param group_id The group to set the name for. + * @param name The name for the group. + * @pre IsValidGroup(group_id). + * @pre 'name' must have at least one character. + * @pre 'name' must have at most 30 characters. + * @exception AIError::ERR_NAME_IS_NOT_UNIQUE + * @return True if and only if the name was changed. + */ + static bool SetName(GroupID group_id, const char *name); + + /** + * Get the name of a group. + * @param group_id The group to get the name of. + * @pre IsValidGroup(group_id). + * @return The name the group has. + */ + static const char *GetName(GroupID group_id); + + /** + * Enable or disable autoreplace protected. If the protection is + * enabled, global autoreplace won't affect vehicles in this group. + * @param group_id The group to change the protection for. + * @param enable True if protection should be enabled. + * @pre IsValidGroup(group_id). + * @return True if and only if the protection was succesfully changed. + */ + static bool EnableAutoReplaceProtection(GroupID group_id, bool enable); + + /** + * Get the autoreplace protection status. + * @param group_id The group to get the protection status for. + * @pre IsValidGroup(group_id). + * @return The autoreplace protection status for the given group. + */ + static bool GetAutoReplaceProtection(GroupID group_id); + + /** + * Get the number of engines in a given group. + * @param group_id The group to get the number of engines in. + * @param engine_id The engine id to count. + * @pre IsValidGroup(group_id) || group_id == ALL_GROUP || group_id == DEFAULT_GROUP. + * @return The number of engines with id engine_id in the group with id group_id. + */ + static int32 GetNumEngines(GroupID group_id, EngineID engine_id); + + /** + * Move a vehicle to a group. + * @param group_id The group to move the vehicel to. + * @param vehicle_id The vehicle to move to the group. + * @pre IsValidGroup(group_id) || group_id == DEFAULT_GROUP. + * @pre AIVehicle::IsValidVehicle(vehicle_id). + * @return True if and only if the vehicle was succesfully moved to the group. + * @note A vehicle can be in only one group at the same time. To remove it from + * a group, move it to another or to DEFAULT_GROUP. Moving the vehicle to the + * given group means removing it from another group. + */ + static bool MoveVehicle(GroupID group_id, VehicleID vehicle_id); + + /** + * Enable or disable the removal of wagons when a (part of a) vehicle is + * (auto)replaced with a longer variant (longer wagons or longer engines) + * If enabled, wagons are removed from the end of the vehicle until it + * fits in the same number of tiles as it did before. + * @param keep_length If true, wagons will be removed if the a new engine is longer. + * @return True if and only if the value was succesfully changed. + */ + static bool EnableWagonRemoval(bool keep_length); + + /** + * Get the current status of wagon removal. + * @return Whether or not wagon removal is enabled. + */ + static bool HasWagonRemoval(); + + /** + * Start replacing all vehicles with a specified engine with another engine. + * @param group_id The group to replace vehicles from. Use ALL_GROUP to replace + * vehicles from all groups that haven't set autoreplace protection. + * @param engine_id_old The engine id to start replacing. + * @param engine_id_new The engine id to replace with. + * @pre IsValidGroup(group_id) || group_id == ALL_GROUP. + * @pre AIEngine.IsValidEngine(engine_id_new). + * @note To stop autoreplacing engine_id_old, call StopAutoReplace(group_id, engine_id_old). + */ + static bool SetAutoReplace(GroupID group_id, EngineID engine_id_old, EngineID engine_id_new); + + /** + * Get the EngineID the given EngineID is replaced with. + * @param group_id The group to get the replacement from. + * @param engine_id The engine that is being replaced. + * @pre IsValidGroup(group_id) || group_id == ALL_GROUP. + * @return The EngineID that is replacing engine_id or an invalid EngineID + * in case engine_id is not begin replaced. + */ + static EngineID GetEngineReplacement(GroupID group_id, EngineID engine_id); + + /** + * Stop replacing a certain engine in the specified group. + * @param group_id The group to stop replacing the engine in. + * @param engine_id The engine id to stop replacing with another engine. + * @pre IsValidGroup(group_id) || group_id == ALL_GROUP. + * @return True if and if the replacing was succesfully stopped. + */ + static bool StopAutoReplace(GroupID group_id, EngineID engine_id); +}; + +#endif /* AI_GROUP_HPP */ diff --git a/src/ai/api/ai_group.hpp.sq b/src/ai/api/ai_group.hpp.sq new file mode 100644 index 000000000..3556f5cc9 --- /dev/null +++ b/src/ai/api/ai_group.hpp.sq @@ -0,0 +1,46 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_group.hpp" + +namespace SQConvert { + /* Allow enums to be used as Squirrel parameters */ + template <> AIGroup::GroupID GetParam(ForceType<AIGroup::GroupID>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AIGroup::GroupID)tmp; } + template <> int Return<AIGroup::GroupID>(HSQUIRRELVM vm, AIGroup::GroupID res) { sq_pushinteger(vm, (int32)res); return 1; } + + /* Allow AIGroup to be used as Squirrel parameter */ + template <> AIGroup *GetParam(ForceType<AIGroup *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIGroup *)instance; } + template <> AIGroup &GetParam(ForceType<AIGroup &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIGroup *)instance; } + template <> const AIGroup *GetParam(ForceType<const AIGroup *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIGroup *)instance; } + template <> const AIGroup &GetParam(ForceType<const AIGroup &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIGroup *)instance; } + template <> int Return<AIGroup *>(HSQUIRRELVM vm, AIGroup *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIGroup", res, NULL, DefSQDestructorCallback<AIGroup>); return 1; } +}; // namespace SQConvert + +void SQAIGroup_Register(Squirrel *engine) { + DefSQClass <AIGroup> SQAIGroup("AIGroup"); + SQAIGroup.PreRegister(engine); + SQAIGroup.AddConstructor<void (AIGroup::*)(), 1>(engine, "x"); + + SQAIGroup.DefSQConst(engine, AIGroup::ALL_GROUP, "ALL_GROUP"); + SQAIGroup.DefSQConst(engine, AIGroup::DEFAULT_GROUP, "DEFAULT_GROUP"); + SQAIGroup.DefSQConst(engine, AIGroup::INVALID_GROUP, "INVALID_GROUP"); + + SQAIGroup.DefSQStaticMethod(engine, &AIGroup::GetClassName, "GetClassName", 1, "x"); + SQAIGroup.DefSQStaticMethod(engine, &AIGroup::IsValidGroup, "IsValidGroup", 2, "xi"); + SQAIGroup.DefSQStaticMethod(engine, &AIGroup::CreateGroup, "CreateGroup", 2, "xi"); + SQAIGroup.DefSQStaticMethod(engine, &AIGroup::DeleteGroup, "DeleteGroup", 2, "xi"); + SQAIGroup.DefSQStaticMethod(engine, &AIGroup::GetVehicleType, "GetVehicleType", 2, "xi"); + SQAIGroup.DefSQStaticMethod(engine, &AIGroup::SetName, "SetName", 3, "xis"); + SQAIGroup.DefSQStaticMethod(engine, &AIGroup::GetName, "GetName", 2, "xi"); + SQAIGroup.DefSQStaticMethod(engine, &AIGroup::EnableAutoReplaceProtection, "EnableAutoReplaceProtection", 3, "xib"); + SQAIGroup.DefSQStaticMethod(engine, &AIGroup::GetAutoReplaceProtection, "GetAutoReplaceProtection", 2, "xi"); + SQAIGroup.DefSQStaticMethod(engine, &AIGroup::GetNumEngines, "GetNumEngines", 3, "xii"); + SQAIGroup.DefSQStaticMethod(engine, &AIGroup::MoveVehicle, "MoveVehicle", 3, "xii"); + SQAIGroup.DefSQStaticMethod(engine, &AIGroup::EnableWagonRemoval, "EnableWagonRemoval", 2, "xb"); + SQAIGroup.DefSQStaticMethod(engine, &AIGroup::HasWagonRemoval, "HasWagonRemoval", 1, "x"); + SQAIGroup.DefSQStaticMethod(engine, &AIGroup::SetAutoReplace, "SetAutoReplace", 4, "xiii"); + SQAIGroup.DefSQStaticMethod(engine, &AIGroup::GetEngineReplacement, "GetEngineReplacement", 3, "xii"); + SQAIGroup.DefSQStaticMethod(engine, &AIGroup::StopAutoReplace, "StopAutoReplace", 3, "xii"); + + SQAIGroup.PostRegister(engine); +} diff --git a/src/ai/api/ai_grouplist.cpp b/src/ai/api/ai_grouplist.cpp new file mode 100644 index 000000000..be57b3750 --- /dev/null +++ b/src/ai/api/ai_grouplist.cpp @@ -0,0 +1,16 @@ +/* $Id$ */ + +/** @file ai_grouplist.cpp Implementation of AIGroupList and friends. */ + +#include "ai_grouplist.hpp" +#include "../../openttd.h" +#include "../../company_func.h" +#include "../../group.h" + +AIGroupList::AIGroupList() +{ + Group *g; + FOR_ALL_GROUPS(g) { + if (g->owner == _current_company) this->AddItem(g->index); + } +} diff --git a/src/ai/api/ai_grouplist.hpp b/src/ai/api/ai_grouplist.hpp new file mode 100644 index 000000000..d97ef85c7 --- /dev/null +++ b/src/ai/api/ai_grouplist.hpp @@ -0,0 +1,21 @@ +/* $Id$ */ + +/** @file ai_grouplist.hpp List all the groups (you own). */ + +#ifndef AI_GROUPLIST_HPP +#define AI_GROUPLIST_HPP + +#include "ai_abstractlist.hpp" + +/** + * Creates a list of groups of which you are the owner. + * @note Neither AIGroup.ALL_GROUP nor AIGroup.DEFAULT_GROUP is in this list. + * @ingroup AIList + */ +class AIGroupList : public AIAbstractList { +public: + static const char *GetClassName() { return "AIGroupList"; } + AIGroupList(); +}; + +#endif /* AI_GROUPLIST_HPP */ diff --git a/src/ai/api/ai_grouplist.hpp.sq b/src/ai/api/ai_grouplist.hpp.sq new file mode 100644 index 000000000..b5c4d945d --- /dev/null +++ b/src/ai/api/ai_grouplist.hpp.sq @@ -0,0 +1,23 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_grouplist.hpp" + +namespace SQConvert { + /* Allow AIGroupList to be used as Squirrel parameter */ + template <> AIGroupList *GetParam(ForceType<AIGroupList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIGroupList *)instance; } + template <> AIGroupList &GetParam(ForceType<AIGroupList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIGroupList *)instance; } + template <> const AIGroupList *GetParam(ForceType<const AIGroupList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIGroupList *)instance; } + template <> const AIGroupList &GetParam(ForceType<const AIGroupList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIGroupList *)instance; } + template <> int Return<AIGroupList *>(HSQUIRRELVM vm, AIGroupList *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIGroupList", res, NULL, DefSQDestructorCallback<AIGroupList>); return 1; } +}; // namespace SQConvert + +void SQAIGroupList_Register(Squirrel *engine) { + DefSQClass <AIGroupList> SQAIGroupList("AIGroupList"); + SQAIGroupList.PreRegister(engine, "AIAbstractList"); + SQAIGroupList.AddConstructor<void (AIGroupList::*)(), 1>(engine, "x"); + + SQAIGroupList.DefSQStaticMethod(engine, &AIGroupList::GetClassName, "GetClassName", 1, "x"); + + SQAIGroupList.PostRegister(engine); +} diff --git a/src/ai/api/ai_industry.cpp b/src/ai/api/ai_industry.cpp new file mode 100644 index 000000000..64687af51 --- /dev/null +++ b/src/ai/api/ai_industry.cpp @@ -0,0 +1,170 @@ +/* $Id$ */ + +/** @file ai_industry.cpp Implementation of AIIndustry. */ + +#include "ai_industry.hpp" +#include "ai_cargo.hpp" +#include "ai_map.hpp" +#include "../../openttd.h" +#include "../../tile_type.h" +#include "../../industry.h" +#include "../../strings_func.h" +#include "../../station_func.h" +#include "table/strings.h" + +/* static */ IndustryID AIIndustry::GetMaxIndustryID() +{ + return ::GetMaxIndustryIndex(); +} + +/* static */ int32 AIIndustry::GetIndustryCount() +{ + return ::GetNumIndustries(); +} + +/* static */ bool AIIndustry::IsValidIndustry(IndustryID industry_id) +{ + return ::IsValidIndustryID(industry_id); +} + +/* static */ const char *AIIndustry::GetName(IndustryID industry_id) +{ + if (!IsValidIndustry(industry_id)) return NULL; + static const int len = 64; + char *industry_name = MallocT<char>(len); + + ::SetDParam(0, industry_id); + ::GetString(industry_name, STR_INDUSTRY, &industry_name[len - 1]); + + return industry_name; +} + +/* static */ int32 AIIndustry::GetProduction(IndustryID industry_id, CargoID cargo_id) +{ + if (!IsValidIndustry(industry_id)) return -1; + if (!AICargo::IsValidCargo(cargo_id)) return -1; + + const Industry *i = ::GetIndustry(industry_id); + const IndustrySpec *indsp = ::GetIndustrySpec(i->type); + + for (byte j = 0; j < lengthof(indsp->produced_cargo); j++) + if (indsp->produced_cargo[j] == cargo_id) return i->production_rate[j] * 8; + + return -1; +} + +/* static */ bool AIIndustry::IsCargoAccepted(IndustryID industry_id, CargoID cargo_id) +{ + if (!IsValidIndustry(industry_id)) return false; + if (!AICargo::IsValidCargo(cargo_id)) return false; + + const Industry *i = ::GetIndustry(industry_id); + const IndustrySpec *indsp = ::GetIndustrySpec(i->type); + + for (byte j = 0; j < lengthof(indsp->accepts_cargo); j++) + if (indsp->accepts_cargo[j] == cargo_id) return true; + + return false; +} + +/* static */ int32 AIIndustry::GetStockpiledCargo(IndustryID industry_id, CargoID cargo_id) +{ + if (!IsValidIndustry(industry_id)) return -1; + if (!AICargo::IsValidCargo(cargo_id)) return -1; + + Industry *ind = ::GetIndustry(industry_id); + for (uint i = 0; i < lengthof(ind->accepts_cargo); i++) { + CargoID cid = ind->accepts_cargo[i]; + if (cid == cargo_id) { + return ind->incoming_cargo_waiting[i]; + } + } + + return -1; +} + +/* static */ int32 AIIndustry::GetLastMonthProduction(IndustryID industry_id, CargoID cargo_id) +{ + if (!IsValidIndustry(industry_id)) return -1; + if (!AICargo::IsValidCargo(cargo_id)) return -1; + + const Industry *i = ::GetIndustry(industry_id); + const IndustrySpec *indsp = ::GetIndustrySpec(i->type); + + for (byte j = 0; j < lengthof(indsp->produced_cargo); j++) + if (indsp->produced_cargo[j] == cargo_id) return i->last_month_production[j]; + + return -1; +} + +/* static */ int32 AIIndustry::GetLastMonthTransported(IndustryID industry_id, CargoID cargo_id) +{ + if (!IsValidIndustry(industry_id)) return -1; + if (!AICargo::IsValidCargo(cargo_id)) return -1; + + const Industry *i = ::GetIndustry(industry_id); + const IndustrySpec *indsp = ::GetIndustrySpec(i->type); + + for (byte j = 0; j < lengthof(indsp->produced_cargo); j++) + if (indsp->produced_cargo[j] == cargo_id) return i->last_month_transported[j]; + + return -1; +} + +/* static */ TileIndex AIIndustry::GetLocation(IndustryID industry_id) +{ + if (!IsValidIndustry(industry_id)) return INVALID_TILE; + + return ::GetIndustry(industry_id)->xy; +} + +/* static */ int32 AIIndustry::GetAmountOfStationsAround(IndustryID industry_id) +{ + if (!IsValidIndustry(industry_id)) return -1; + + Industry *ind = ::GetIndustry(industry_id); + return (int32)::FindStationsAroundTiles(ind->xy, ind->width, ind->height).size(); +} + +/* static */ int32 AIIndustry::GetDistanceManhattanToTile(IndustryID industry_id, TileIndex tile) +{ + if (!IsValidIndustry(industry_id)) return -1; + + return AIMap::DistanceManhattan(tile, GetLocation(industry_id)); +} + +/* static */ int32 AIIndustry::GetDistanceSquareToTile(IndustryID industry_id, TileIndex tile) +{ + if (!IsValidIndustry(industry_id)) return -1; + + return AIMap::DistanceSquare(tile, GetLocation(industry_id)); +} + +/* static */ bool AIIndustry::IsBuiltOnWater(IndustryID industry_id) +{ + if (!IsValidIndustry(industry_id)) return false; + + return (::GetIndustrySpec(::GetIndustry(industry_id)->type)->behaviour & INDUSTRYBEH_BUILT_ONWATER) != 0; +} + +/* static */ bool AIIndustry::HasHeliportAndDock(IndustryID industry_id) +{ + if (!IsValidIndustry(industry_id)) return false; + + return (::GetIndustrySpec(::GetIndustry(industry_id)->type)->behaviour & INDUSTRYBEH_AI_AIRSHIP_ROUTES) != 0; +} + +/* static */ TileIndex AIIndustry::GetHeliportAndDockLocation(IndustryID industry_id) +{ + if (!IsValidIndustry(industry_id)) return INVALID_TILE; + if (!HasHeliportAndDock(industry_id)) return INVALID_TILE; + + return ::GetIndustry(industry_id)->xy; +} + +/* static */ IndustryType AIIndustry::GetIndustryType(IndustryID industry_id) +{ + if (!IsValidIndustry(industry_id)) return INVALID_INDUSTRYTYPE; + + return ::GetIndustry(industry_id)->type; +} diff --git a/src/ai/api/ai_industry.hpp b/src/ai/api/ai_industry.hpp new file mode 100644 index 000000000..1b3365a97 --- /dev/null +++ b/src/ai/api/ai_industry.hpp @@ -0,0 +1,171 @@ +/* $Id$ */ + +/** @file ai_industry.hpp Everything to query and build industries. */ + +#ifndef AI_INDUSTRY_HPP +#define AI_INDUSTRY_HPP + +#include "ai_object.hpp" + +/** + * Class that handles all industry related functions. + */ +class AIIndustry : public AIObject { +public: + static const char *GetClassName() { return "AIIndustry"; } + + /** + * Gets the maximum industry index; there are no valid industries with a + * higher index. + * @return The maximum industry index. + * @post Return value is always non-negative. + */ + static IndustryID GetMaxIndustryID(); + + /** + * Gets the number of industries. This is different than GetMaxIndustryID() + * because of the way OpenTTD works internally. + * @return The number of industries. + * @post Return value is always non-negative. + */ + static int32 GetIndustryCount(); + + /** + * Checks whether the given industry index is valid. + * @param industry_id The index to check. + * @return True if and only if the industry is valid. + */ + static bool IsValidIndustry(IndustryID industry_id); + + /** + * Get the name of the industry. + * @param industry_id The industry to get the name of. + * @pre IsValidIndustry(industry_id). + * @return The name of the industry. + */ + static const char *GetName(IndustryID industry_id); + + /** + * Gets the production of a cargo of the industry. + * @param industry_id The index of the industry. + * @param cargo_id The index of the cargo. + * @pre IsValidIndustry(industry_id). + * @pre AICargo::IsValidCargo(cargo_id). + * @return The production of the cargo for this industry, or -1 if + * this industry doesn't produce this cargo. + */ + static int32 GetProduction(IndustryID industry_id, CargoID cargo_id); + + /** + * See if an industry accepts a certain cargo. + * @param industry_id The index of the industry. + * @param cargo_id The index of the cargo. + * @pre IsValidIndustry(industry_id). + * @pre AICargo::IsValidCargo(cargo_id). + * @return The production of the cargo for this industry. + */ + static bool IsCargoAccepted(IndustryID industry_id, CargoID cargo_id); + + /** + * Get the amount of cargo stockpiled for processing. + * @param industry_id The index of the industry. + * @param cargo_id The index of the cargo. + * @pre IsValidIndustry(industry_id). + * @pre AICargo::IsValidCargo(cargo_id). + * @return The amount of cargo that is waiting for processing. + */ + static int32 GetStockpiledCargo(IndustryID industry_id, CargoID cargo_id); + + /** + * Get the total last month's production of the given cargo at an industry. + * @param industry_id The index of the industry. + * @param cargo_id The index of the cargo. + * @pre IsValidIndustry(industry_id). + * @pre AICargo::IsValidCargo(cargo_id). + * @return The last month's production of the given cargo for this industry. + */ + static int32 GetLastMonthProduction(IndustryID industry_id, CargoID cargo_id); + + /** + * Get the total amount of cargo transported from an industry last month. + * @param industry_id The index of the industry. + * @param cargo_id The index of the cargo. + * @pre IsValidIndustry(industry_id). + * @pre AICargo::IsValidCargo(cargo_id). + * @return The amount of given cargo transported from this industry last month. + */ + static int32 GetLastMonthTransported(IndustryID industry_id, CargoID cargo_id); + + /** + * Gets the location of the industry. + * @param industry_id The index of the industry. + * @pre IsValidIndustry(industry_id). + * @return The location of the industry. + */ + static TileIndex GetLocation(IndustryID industry_id); + + /** + * Get the number of stations around an industry. + * @param industry_id The index of the industry. + * @pre IsValidIndustry(industry_id). + * @return The number of stations around an industry. + */ + static int32 GetAmountOfStationsAround(IndustryID industry_id); + + /** + * Get the manhattan distance from the tile to the AIIndustry::GetLocation() + * of the industry. + * @param industry_id The industry to get the distance to. + * @param tile The tile to get the distance to. + * @pre IsValidIndustry(industry_id). + * @pre AIMap::IsValidTile(tile). + * @return The distance between industry and tile. + */ + static int32 GetDistanceManhattanToTile(IndustryID industry_id, TileIndex tile); + + /** + * Get the square distance from the tile to the AIIndustry::GetLocation() + * of the industry. + * @param industry_id The industry to get the distance to. + * @param tile The tile to get the distance to. + * @pre IsValidIndustry(industry_id). + * @pre AIMap::IsValidTile(tile). + * @return The distance between industry and tile. + */ + static int32 GetDistanceSquareToTile(IndustryID industry_id, TileIndex tile); + + /** + * Is this industry built on water. + * @param industry_id The index of the industry. + * @pre IsValidIndustry(industry_id). + * @return True when the industry is built on water. + */ + static bool IsBuiltOnWater(IndustryID industry_id); + + /** + * Does this industry have a heliport and dock? + * @param industry_id The index of the industry. + * @pre IsValidIndustry(industry_id). + * @return True when the industry has a heliport and dock. + */ + static bool HasHeliportAndDock(IndustryID industry_id); + + /** + * Gets the location of the industry's heliport/dock. + * @param industry_id The index of the industry. + * @pre IsValidIndustry(industry_id). + * @pre HasHeliportAndDock(industry_id). + * @return The location of the industry's heliport/dock. + */ + static TileIndex GetHeliportAndDockLocation(IndustryID industry_id); + + /** + * Get the IndustryType of the industry. + * @param industry_id The index of the industry. + * @pre IsValidIndustry(industry_id). + * @return The IndustryType of the industry. + */ + static IndustryType GetIndustryType(IndustryID industry_id); +}; + +#endif /* AI_INDUSTRY_HPP */ diff --git a/src/ai/api/ai_industry.hpp.sq b/src/ai/api/ai_industry.hpp.sq new file mode 100644 index 000000000..dc0493efb --- /dev/null +++ b/src/ai/api/ai_industry.hpp.sq @@ -0,0 +1,40 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_industry.hpp" + +namespace SQConvert { + /* Allow AIIndustry to be used as Squirrel parameter */ + template <> AIIndustry *GetParam(ForceType<AIIndustry *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIIndustry *)instance; } + template <> AIIndustry &GetParam(ForceType<AIIndustry &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIIndustry *)instance; } + template <> const AIIndustry *GetParam(ForceType<const AIIndustry *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIIndustry *)instance; } + template <> const AIIndustry &GetParam(ForceType<const AIIndustry &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIIndustry *)instance; } + template <> int Return<AIIndustry *>(HSQUIRRELVM vm, AIIndustry *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIIndustry", res, NULL, DefSQDestructorCallback<AIIndustry>); return 1; } +}; // namespace SQConvert + +void SQAIIndustry_Register(Squirrel *engine) { + DefSQClass <AIIndustry> SQAIIndustry("AIIndustry"); + SQAIIndustry.PreRegister(engine); + SQAIIndustry.AddConstructor<void (AIIndustry::*)(), 1>(engine, "x"); + + SQAIIndustry.DefSQStaticMethod(engine, &AIIndustry::GetClassName, "GetClassName", 1, "x"); + SQAIIndustry.DefSQStaticMethod(engine, &AIIndustry::GetMaxIndustryID, "GetMaxIndustryID", 1, "x"); + SQAIIndustry.DefSQStaticMethod(engine, &AIIndustry::GetIndustryCount, "GetIndustryCount", 1, "x"); + SQAIIndustry.DefSQStaticMethod(engine, &AIIndustry::IsValidIndustry, "IsValidIndustry", 2, "xi"); + SQAIIndustry.DefSQStaticMethod(engine, &AIIndustry::GetName, "GetName", 2, "xi"); + SQAIIndustry.DefSQStaticMethod(engine, &AIIndustry::GetProduction, "GetProduction", 3, "xii"); + SQAIIndustry.DefSQStaticMethod(engine, &AIIndustry::IsCargoAccepted, "IsCargoAccepted", 3, "xii"); + SQAIIndustry.DefSQStaticMethod(engine, &AIIndustry::GetStockpiledCargo, "GetStockpiledCargo", 3, "xii"); + SQAIIndustry.DefSQStaticMethod(engine, &AIIndustry::GetLastMonthProduction, "GetLastMonthProduction", 3, "xii"); + SQAIIndustry.DefSQStaticMethod(engine, &AIIndustry::GetLastMonthTransported, "GetLastMonthTransported", 3, "xii"); + SQAIIndustry.DefSQStaticMethod(engine, &AIIndustry::GetLocation, "GetLocation", 2, "xi"); + SQAIIndustry.DefSQStaticMethod(engine, &AIIndustry::GetAmountOfStationsAround, "GetAmountOfStationsAround", 2, "xi"); + SQAIIndustry.DefSQStaticMethod(engine, &AIIndustry::GetDistanceManhattanToTile, "GetDistanceManhattanToTile", 3, "xii"); + SQAIIndustry.DefSQStaticMethod(engine, &AIIndustry::GetDistanceSquareToTile, "GetDistanceSquareToTile", 3, "xii"); + SQAIIndustry.DefSQStaticMethod(engine, &AIIndustry::IsBuiltOnWater, "IsBuiltOnWater", 2, "xi"); + SQAIIndustry.DefSQStaticMethod(engine, &AIIndustry::HasHeliportAndDock, "HasHeliportAndDock", 2, "xi"); + SQAIIndustry.DefSQStaticMethod(engine, &AIIndustry::GetHeliportAndDockLocation, "GetHeliportAndDockLocation", 2, "xi"); + SQAIIndustry.DefSQStaticMethod(engine, &AIIndustry::GetIndustryType, "GetIndustryType", 2, "xi"); + + SQAIIndustry.PostRegister(engine); +} diff --git a/src/ai/api/ai_industrylist.cpp b/src/ai/api/ai_industrylist.cpp new file mode 100644 index 000000000..be8fbcfa4 --- /dev/null +++ b/src/ai/api/ai_industrylist.cpp @@ -0,0 +1,42 @@ +/* $Id$ */ + +/** @file ai_industrylist.cpp Implementation of AIIndustryList and friends. */ + +#include "ai_industrylist.hpp" +#include "../../openttd.h" +#include "../../tile_type.h" +#include "../../industry.h" + +AIIndustryList::AIIndustryList() +{ + Industry *i; + FOR_ALL_INDUSTRIES(i) { + this->AddItem(i->index); + } +} + +AIIndustryList_CargoAccepting::AIIndustryList_CargoAccepting(CargoID cargo_id) +{ + const Industry *i; + const IndustrySpec *indsp; + + FOR_ALL_INDUSTRIES(i) { + indsp = ::GetIndustrySpec(i->type); + + for (byte j = 0; j < lengthof(indsp->accepts_cargo); j++) + if (indsp->accepts_cargo[j] == cargo_id) this->AddItem(i->index); + } +} + +AIIndustryList_CargoProducing::AIIndustryList_CargoProducing(CargoID cargo_id) +{ + const Industry *i; + const IndustrySpec *indsp; + + FOR_ALL_INDUSTRIES(i) { + indsp = ::GetIndustrySpec(i->type); + + for (byte j = 0; j < lengthof(indsp->produced_cargo); j++) + if (indsp->produced_cargo[j] == cargo_id) this->AddItem(i->index); + } +} diff --git a/src/ai/api/ai_industrylist.hpp b/src/ai/api/ai_industrylist.hpp new file mode 100644 index 000000000..2afca7b97 --- /dev/null +++ b/src/ai/api/ai_industrylist.hpp @@ -0,0 +1,49 @@ +/* $Id$ */ + +/** @file ai_industrylist.hpp List all the industries. */ + +#ifndef AI_INDUSTRYLIST_HPP +#define AI_INDUSTRYLIST_HPP + +#include "ai_abstractlist.hpp" + +/** + * Creates a list of industries that are currently on the map. + * @ingroup AIList + */ +class AIIndustryList : public AIAbstractList { +public: + static const char *GetClassName() { return "AIIndustryList"; } + AIIndustryList(); +}; + +/** + * Creates a list of industries that accepts a given cargo. + * @ingroup AIList + */ +class AIIndustryList_CargoAccepting : public AIAbstractList { +public: + static const char *GetClassName() { return "AIIndustryList_CargoAccepting"; } + + /** + * @param cargo_id The cargo this industry should accept. + */ + AIIndustryList_CargoAccepting(CargoID cargo_id); +}; + +/** + * Creates a list of industries that can produce a given cargo. + * @note It also contains industries that currently produces 0 units of the cargo. + * @ingroup AIList + */ +class AIIndustryList_CargoProducing : public AIAbstractList { +public: + static const char *GetClassName() { return "AIIndustryList_CargoProducing"; } + + /** + * @param cargo_id The cargo this industry should produce. + */ + AIIndustryList_CargoProducing(CargoID cargo_id); +}; + +#endif /* AI_INDUSTRYLIST_HPP */ diff --git a/src/ai/api/ai_industrylist.hpp.sq b/src/ai/api/ai_industrylist.hpp.sq new file mode 100644 index 000000000..1abc9c549 --- /dev/null +++ b/src/ai/api/ai_industrylist.hpp.sq @@ -0,0 +1,61 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_industrylist.hpp" + +namespace SQConvert { + /* Allow AIIndustryList to be used as Squirrel parameter */ + template <> AIIndustryList *GetParam(ForceType<AIIndustryList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIIndustryList *)instance; } + template <> AIIndustryList &GetParam(ForceType<AIIndustryList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIIndustryList *)instance; } + template <> const AIIndustryList *GetParam(ForceType<const AIIndustryList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIIndustryList *)instance; } + template <> const AIIndustryList &GetParam(ForceType<const AIIndustryList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIIndustryList *)instance; } + template <> int Return<AIIndustryList *>(HSQUIRRELVM vm, AIIndustryList *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIIndustryList", res, NULL, DefSQDestructorCallback<AIIndustryList>); return 1; } +}; // namespace SQConvert + +void SQAIIndustryList_Register(Squirrel *engine) { + DefSQClass <AIIndustryList> SQAIIndustryList("AIIndustryList"); + SQAIIndustryList.PreRegister(engine, "AIAbstractList"); + SQAIIndustryList.AddConstructor<void (AIIndustryList::*)(), 1>(engine, "x"); + + SQAIIndustryList.DefSQStaticMethod(engine, &AIIndustryList::GetClassName, "GetClassName", 1, "x"); + + SQAIIndustryList.PostRegister(engine); +} + +namespace SQConvert { + /* Allow AIIndustryList_CargoAccepting to be used as Squirrel parameter */ + template <> AIIndustryList_CargoAccepting *GetParam(ForceType<AIIndustryList_CargoAccepting *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIIndustryList_CargoAccepting *)instance; } + template <> AIIndustryList_CargoAccepting &GetParam(ForceType<AIIndustryList_CargoAccepting &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIIndustryList_CargoAccepting *)instance; } + template <> const AIIndustryList_CargoAccepting *GetParam(ForceType<const AIIndustryList_CargoAccepting *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIIndustryList_CargoAccepting *)instance; } + template <> const AIIndustryList_CargoAccepting &GetParam(ForceType<const AIIndustryList_CargoAccepting &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIIndustryList_CargoAccepting *)instance; } + template <> int Return<AIIndustryList_CargoAccepting *>(HSQUIRRELVM vm, AIIndustryList_CargoAccepting *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIIndustryList_CargoAccepting", res, NULL, DefSQDestructorCallback<AIIndustryList_CargoAccepting>); return 1; } +}; // namespace SQConvert + +void SQAIIndustryList_CargoAccepting_Register(Squirrel *engine) { + DefSQClass <AIIndustryList_CargoAccepting> SQAIIndustryList_CargoAccepting("AIIndustryList_CargoAccepting"); + SQAIIndustryList_CargoAccepting.PreRegister(engine, "AIAbstractList"); + SQAIIndustryList_CargoAccepting.AddConstructor<void (AIIndustryList_CargoAccepting::*)(CargoID cargo_id), 2>(engine, "xi"); + + SQAIIndustryList_CargoAccepting.DefSQStaticMethod(engine, &AIIndustryList_CargoAccepting::GetClassName, "GetClassName", 1, "x"); + + SQAIIndustryList_CargoAccepting.PostRegister(engine); +} + +namespace SQConvert { + /* Allow AIIndustryList_CargoProducing to be used as Squirrel parameter */ + template <> AIIndustryList_CargoProducing *GetParam(ForceType<AIIndustryList_CargoProducing *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIIndustryList_CargoProducing *)instance; } + template <> AIIndustryList_CargoProducing &GetParam(ForceType<AIIndustryList_CargoProducing &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIIndustryList_CargoProducing *)instance; } + template <> const AIIndustryList_CargoProducing *GetParam(ForceType<const AIIndustryList_CargoProducing *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIIndustryList_CargoProducing *)instance; } + template <> const AIIndustryList_CargoProducing &GetParam(ForceType<const AIIndustryList_CargoProducing &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIIndustryList_CargoProducing *)instance; } + template <> int Return<AIIndustryList_CargoProducing *>(HSQUIRRELVM vm, AIIndustryList_CargoProducing *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIIndustryList_CargoProducing", res, NULL, DefSQDestructorCallback<AIIndustryList_CargoProducing>); return 1; } +}; // namespace SQConvert + +void SQAIIndustryList_CargoProducing_Register(Squirrel *engine) { + DefSQClass <AIIndustryList_CargoProducing> SQAIIndustryList_CargoProducing("AIIndustryList_CargoProducing"); + SQAIIndustryList_CargoProducing.PreRegister(engine, "AIAbstractList"); + SQAIIndustryList_CargoProducing.AddConstructor<void (AIIndustryList_CargoProducing::*)(CargoID cargo_id), 2>(engine, "xi"); + + SQAIIndustryList_CargoProducing.DefSQStaticMethod(engine, &AIIndustryList_CargoProducing::GetClassName, "GetClassName", 1, "x"); + + SQAIIndustryList_CargoProducing.PostRegister(engine); +} diff --git a/src/ai/api/ai_industrytype.cpp b/src/ai/api/ai_industrytype.cpp new file mode 100644 index 000000000..34d69f5f8 --- /dev/null +++ b/src/ai/api/ai_industrytype.cpp @@ -0,0 +1,115 @@ +/* $Id$ */ + +/** @file ai_industrytype.cpp Implementation of AIIndustryType. */ + +#include "ai_industrytype.hpp" +#include "ai_map.hpp" +#include "../../openttd.h" +#include "../../command_type.h" +#include "../../settings_type.h" +#include "../../strings_func.h" +#include "../../tile_type.h" +#include "../../industry.h" + +/* static */ bool AIIndustryType::IsValidIndustryType(IndustryType industry_type) +{ + if (industry_type >= NUM_INDUSTRYTYPES) return false; + + return ::GetIndustrySpec(industry_type)->enabled; +} + +/* static */ bool AIIndustryType::IsRawIndustry(IndustryType industry_type) +{ + if (!IsValidIndustryType(industry_type)) return false; + + return ::GetIndustrySpec(industry_type)->IsRawIndustry(); +} + +/* static */ bool AIIndustryType::ProductionCanIncrease(IndustryType industry_type) +{ + if (!IsValidIndustryType(industry_type)) return false; + + if (_settings_game.game_creation.landscape != LT_TEMPERATE) return true; + return (::GetIndustrySpec(industry_type)->behaviour & INDUSTRYBEH_DONT_INCR_PROD) == 0; +} + +/* static */ Money AIIndustryType::GetConstructionCost(IndustryType industry_type) +{ + if (!IsValidIndustryType(industry_type)) return false; + + return ::GetIndustrySpec(industry_type)->GetConstructionCost(); +} + +/* static */ const char *AIIndustryType::GetName(IndustryType industry_type) +{ + if (!IsValidIndustryType(industry_type)) return NULL; + static const int len = 64; + char *industrytype_name = MallocT<char>(len); + + ::GetString(industrytype_name, ::GetIndustrySpec(industry_type)->name, &industrytype_name[len - 1]); + + return industrytype_name; +} + +/* static */ AIList *AIIndustryType::GetProducedCargo(IndustryType industry_type) +{ + if (!IsValidIndustryType(industry_type)) return NULL; + + const IndustrySpec *ins = ::GetIndustrySpec(industry_type); + + AIList *list = new AIList(); + for (int i = 0; i < 2; i++) { + if (ins->produced_cargo[i] != CT_INVALID) list->AddItem(ins->produced_cargo[i], 0); + } + + return list; +} + +/* static */ AIList *AIIndustryType::GetAcceptedCargo(IndustryType industry_type) +{ + if (!IsValidIndustryType(industry_type)) return NULL; + + const IndustrySpec *ins = ::GetIndustrySpec(industry_type); + + AIList *list = new AIList(); + for (int i = 0; i < 3; i++) { + if (ins->accepts_cargo[i] != CT_INVALID) list->AddItem(ins->accepts_cargo[i], 0); + } + + return list; +} + +/* static */ bool AIIndustryType::CanBuildIndustry(IndustryType industry_type) +{ + if (!IsValidIndustryType(industry_type)) return false; + if (!::GetIndustrySpec(industry_type)->IsRawIndustry()) return true; + + /* raw_industry_construction == 1 means "Build as other industries" */ + return _settings_game.construction.raw_industry_construction == 1; +} + +/* static */ bool AIIndustryType::CanProspectIndustry(IndustryType industry_type) +{ + if (!IsValidIndustryType(industry_type)) return false; + if (!::GetIndustrySpec(industry_type)->IsRawIndustry()) return false; + + /* raw_industry_construction == 2 means "prospect" */ + return _settings_game.construction.raw_industry_construction == 2; +} + +/* static */ bool AIIndustryType::BuildIndustry(IndustryType industry_type, TileIndex tile) +{ + EnforcePrecondition(false, CanBuildIndustry(industry_type)); + EnforcePrecondition(false, AIMap::IsValidTile(tile)); + + uint32 seed = ::InteractiveRandom(); + return AIObject::DoCommand(tile, (::InteractiveRandomRange(::GetIndustrySpec(industry_type)->num_table) << 16) | industry_type, seed, CMD_BUILD_INDUSTRY); +} + +/* static */ bool AIIndustryType::ProspectIndustry(IndustryType industry_type) +{ + EnforcePrecondition(false, CanProspectIndustry(industry_type)); + + uint32 seed = ::InteractiveRandom(); + return AIObject::DoCommand(0, industry_type, seed, CMD_BUILD_INDUSTRY); +} diff --git a/src/ai/api/ai_industrytype.hpp b/src/ai/api/ai_industrytype.hpp new file mode 100644 index 000000000..88ba6d730 --- /dev/null +++ b/src/ai/api/ai_industrytype.hpp @@ -0,0 +1,114 @@ +/* $Id$ */ + +/** @file ai_industrytype.hpp Everything to query and build industries. */ + +#ifndef AI_INDUSTRYTYPE_HPP +#define AI_INDUSTRYTYPE_HPP + +#include "ai_object.hpp" +#include "ai_error.hpp" +#include "ai_list.hpp" + +/** + * Class that handles all industry-type related functions. + */ +class AIIndustryType : public AIObject { +public: + static const char *GetClassName() { return "AIIndustryType"; } + + /** + * Checks whether the given industry-type is valid. + * @param industry_type The type check. + * @return True if and only if the industry-type is valid. + */ + static bool IsValidIndustryType(IndustryType industry_type); + + /** + * Get the name of an industry-type. + * @param industry_type The type to get the name for. + * @pre IsValidIndustryType(industry_type). + * @return The name of an industry. + */ + static const char *GetName(IndustryType industry_type); + + /** + * Get a list of CargoID possible produced by this industry-type. + * @param industry_type The type to get the CargoIDs for. + * @pre IsValidIndustryType(industry_type). + * @return The CargoIDs of all cargotypes this industry could produce. + */ + static AIList *GetProducedCargo(IndustryType industry_type); + + /** + * Get a list of CargoID accepted by this industry-type. + * @param industry_type The type to get the CargoIDs for. + * @pre IsValidIndustryType(industry_type). + * @return The CargoIDs of all cargotypes this industry accepts. + */ + static AIList *GetAcceptedCargo(IndustryType industry_type); + + /** + * Is this industry type a raw industry? + * @param industry_type The type of the industry. + * @pre IsValidIndustryType(industry_type). + * @return True if it should be handled as a raw industry. + */ + static bool IsRawIndustry(IndustryType industry_type); + + /** + * Can the production of this industry increase? + * @param industry_type The type of the industry. + * @pre IsValidIndustryType(industry_type). + * @return True if the production of this industry can increase. + */ + static bool ProductionCanIncrease(IndustryType industry_type); + + /** + * Get the cost for building this industry-type. + * @param industry_type The type of the industry. + * @pre IsValidIndustryType(industry_type). + * @return The cost for building this industry-type. + */ + static Money GetConstructionCost(IndustryType industry_type); + + /** + * Can you build this type of industry? + * @param industry_type The type of the industry. + * @pre IsValidIndustryType(industry_type). + * @return True if you can prospect this type of industry. + * @note Returns false if you can only prospect this type of industry. + */ + static bool CanBuildIndustry(IndustryType industry_type); + + /** + * Can you prospect this type of industry? + * @param industry_type The type of the industry. + * @pre IsValidIndustryType(industry_type). + * @return True if you can prospect this type of industry. + * @note If the patch setting "Manual primary industry construction method" is set + * to either "None" or "as other industries" this function always returns false. + */ + static bool CanProspectIndustry(IndustryType industry_type); + + /** + * Build an industry of the specified type. + * @param industry_type The type of the industry to build. + * @param tile The tile to build the industry on. + * @pre CanBuildIndustry(industry_type). + * @return True if the industry was succesfully build. + */ + static bool BuildIndustry(IndustryType industry_type, TileIndex tile); + + /** + * Prospect an industry of this type. Prospecting an industries let the game try to create + * an industry on a random place on the map. + * @param industry_type The type of the industry. + * @pre CanProspectIndustry(industry_type). + * @return True if no error occured while trying to prospect. + * @note Even if true is returned there is no guarantee a new industry is build. + * @note If true is returned the money is paid, whether a new industry was build or not. + */ + static bool ProspectIndustry(IndustryType industry_type); +}; + +#endif /* AI_INDUSTRYTYPE_HPP */ diff --git a/src/ai/api/ai_industrytype.hpp.sq b/src/ai/api/ai_industrytype.hpp.sq new file mode 100644 index 000000000..ed5353776 --- /dev/null +++ b/src/ai/api/ai_industrytype.hpp.sq @@ -0,0 +1,34 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_industrytype.hpp" + +namespace SQConvert { + /* Allow AIIndustryType to be used as Squirrel parameter */ + template <> AIIndustryType *GetParam(ForceType<AIIndustryType *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIIndustryType *)instance; } + template <> AIIndustryType &GetParam(ForceType<AIIndustryType &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIIndustryType *)instance; } + template <> const AIIndustryType *GetParam(ForceType<const AIIndustryType *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIIndustryType *)instance; } + template <> const AIIndustryType &GetParam(ForceType<const AIIndustryType &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIIndustryType *)instance; } + template <> int Return<AIIndustryType *>(HSQUIRRELVM vm, AIIndustryType *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIIndustryType", res, NULL, DefSQDestructorCallback<AIIndustryType>); return 1; } +}; // namespace SQConvert + +void SQAIIndustryType_Register(Squirrel *engine) { + DefSQClass <AIIndustryType> SQAIIndustryType("AIIndustryType"); + SQAIIndustryType.PreRegister(engine); + SQAIIndustryType.AddConstructor<void (AIIndustryType::*)(), 1>(engine, "x"); + + SQAIIndustryType.DefSQStaticMethod(engine, &AIIndustryType::GetClassName, "GetClassName", 1, "x"); + SQAIIndustryType.DefSQStaticMethod(engine, &AIIndustryType::IsValidIndustryType, "IsValidIndustryType", 2, "xi"); + SQAIIndustryType.DefSQStaticMethod(engine, &AIIndustryType::GetName, "GetName", 2, "xi"); + SQAIIndustryType.DefSQStaticMethod(engine, &AIIndustryType::GetProducedCargo, "GetProducedCargo", 2, "xi"); + SQAIIndustryType.DefSQStaticMethod(engine, &AIIndustryType::GetAcceptedCargo, "GetAcceptedCargo", 2, "xi"); + SQAIIndustryType.DefSQStaticMethod(engine, &AIIndustryType::IsRawIndustry, "IsRawIndustry", 2, "xi"); + SQAIIndustryType.DefSQStaticMethod(engine, &AIIndustryType::ProductionCanIncrease, "ProductionCanIncrease", 2, "xi"); + SQAIIndustryType.DefSQStaticMethod(engine, &AIIndustryType::GetConstructionCost, "GetConstructionCost", 2, "xi"); + SQAIIndustryType.DefSQStaticMethod(engine, &AIIndustryType::CanBuildIndustry, "CanBuildIndustry", 2, "xi"); + SQAIIndustryType.DefSQStaticMethod(engine, &AIIndustryType::CanProspectIndustry, "CanProspectIndustry", 2, "xi"); + SQAIIndustryType.DefSQStaticMethod(engine, &AIIndustryType::BuildIndustry, "BuildIndustry", 3, "xii"); + SQAIIndustryType.DefSQStaticMethod(engine, &AIIndustryType::ProspectIndustry, "ProspectIndustry", 2, "xi"); + + SQAIIndustryType.PostRegister(engine); +} diff --git a/src/ai/api/ai_industrytypelist.cpp b/src/ai/api/ai_industrytypelist.cpp new file mode 100644 index 000000000..b5ef34b50 --- /dev/null +++ b/src/ai/api/ai_industrytypelist.cpp @@ -0,0 +1,15 @@ +/* $Id$ */ + +/** @file ai_industrytypelist.cpp Implementation of AIIndustryTypeList. */ + +#include "ai_industrytypelist.hpp" +#include "../../openttd.h" +#include "../../tile_type.h" +#include "../../industry.h" + +AIIndustryTypeList::AIIndustryTypeList() +{ + for (int i = 0; i < NUM_INDUSTRYTYPES; i++) { + if (AIIndustryType::IsValidIndustryType(i)) this->AddItem(i); + } +} diff --git a/src/ai/api/ai_industrytypelist.hpp b/src/ai/api/ai_industrytypelist.hpp new file mode 100644 index 000000000..55c582c9d --- /dev/null +++ b/src/ai/api/ai_industrytypelist.hpp @@ -0,0 +1,22 @@ +/* $Id$ */ + +/** @file ai_industrytypelist.hpp List all available industry types. */ + +#ifndef AI_INDUSTRYTYPELIST_HPP +#define AI_INDUSTRYTYPELIST_HPP + +#include "ai_abstractlist.hpp" +#include "ai_industrytype.hpp" + +/** + * Creates a list of valid industry types. + * @ingroup AIList + */ +class AIIndustryTypeList : public AIAbstractList { +public: + static const char *GetClassName() { return "AIIndustryTypeList"; } + AIIndustryTypeList(); +}; + + +#endif /* AI_INDUSTRYTYPELIST_HPP */ diff --git a/src/ai/api/ai_industrytypelist.hpp.sq b/src/ai/api/ai_industrytypelist.hpp.sq new file mode 100644 index 000000000..901ee4f68 --- /dev/null +++ b/src/ai/api/ai_industrytypelist.hpp.sq @@ -0,0 +1,23 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_industrytypelist.hpp" + +namespace SQConvert { + /* Allow AIIndustryTypeList to be used as Squirrel parameter */ + template <> AIIndustryTypeList *GetParam(ForceType<AIIndustryTypeList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIIndustryTypeList *)instance; } + template <> AIIndustryTypeList &GetParam(ForceType<AIIndustryTypeList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIIndustryTypeList *)instance; } + template <> const AIIndustryTypeList *GetParam(ForceType<const AIIndustryTypeList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIIndustryTypeList *)instance; } + template <> const AIIndustryTypeList &GetParam(ForceType<const AIIndustryTypeList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIIndustryTypeList *)instance; } + template <> int Return<AIIndustryTypeList *>(HSQUIRRELVM vm, AIIndustryTypeList *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIIndustryTypeList", res, NULL, DefSQDestructorCallback<AIIndustryTypeList>); return 1; } +}; // namespace SQConvert + +void SQAIIndustryTypeList_Register(Squirrel *engine) { + DefSQClass <AIIndustryTypeList> SQAIIndustryTypeList("AIIndustryTypeList"); + SQAIIndustryTypeList.PreRegister(engine, "AIAbstractList"); + SQAIIndustryTypeList.AddConstructor<void (AIIndustryTypeList::*)(), 1>(engine, "x"); + + SQAIIndustryTypeList.DefSQStaticMethod(engine, &AIIndustryTypeList::GetClassName, "GetClassName", 1, "x"); + + SQAIIndustryTypeList.PostRegister(engine); +} diff --git a/src/ai/api/ai_list.cpp b/src/ai/api/ai_list.cpp new file mode 100644 index 000000000..57690d341 --- /dev/null +++ b/src/ai/api/ai_list.cpp @@ -0,0 +1,45 @@ +/* $Id$ */ + +/** @file ai_list.cpp Implementation of AIList. */ + +#include <squirrel.h> +#include "ai_list.hpp" + +void AIList::AddItem(int32 item, int32 value) +{ + AIAbstractList::AddItem(item); + this->SetValue(item, value); +} + +void AIList::ChangeItem(int32 item, int32 value) +{ + this->SetValue(item, value); +} + +void AIList::RemoveItem(int32 item) +{ + AIAbstractList::RemoveItem(item); +} + +SQInteger AIList::_set(HSQUIRRELVM vm) { + if (sq_gettype(vm, 2) != OT_INTEGER) return SQ_ERROR; + if (sq_gettype(vm, 3) != OT_INTEGER || sq_gettype(vm, 3) == OT_NULL) { + return sq_throwerror(vm, _SC("you can only assign integers to this list")); + } + + SQInteger idx, val; + sq_getinteger(vm, 2, &idx); + if (sq_gettype(vm, 3) == OT_NULL) { + this->RemoveItem(idx); + return 0; + } + + sq_getinteger(vm, 3, &val); + if (!this->HasItem(idx)) { + this->AddItem(idx, val); + return 0; + } + + this->ChangeItem(idx, val); + return 0; +} diff --git a/src/ai/api/ai_list.hpp b/src/ai/api/ai_list.hpp new file mode 100644 index 000000000..9ce99a8e0 --- /dev/null +++ b/src/ai/api/ai_list.hpp @@ -0,0 +1,47 @@ +/* $Id$ */ + +/** @file ai_list.hpp List custom entries. */ + +#ifndef AI_LIST_HPP +#define AI_LIST_HPP + +#include "ai_abstractlist.hpp" + +/** + * Creates an empty list, in which you can add integers. + * @ingroup AIList + */ +class AIList : public AIAbstractList { +public: + static const char *GetClassName() { return "AIList"; } + +public: + /** + * Add an item to the list. + * @param item the item to add. + * @param value the value to assign. + */ + void AddItem(int32 item, int32 value); + + /** + * Change the value of an item in the list. + * @param item the item to change + * @param value the value to assign. + */ + void ChangeItem(int32 item, int32 value); + + /** + * Remove the item from the list. + * @param item the item to remove. + */ + void RemoveItem(int32 item); + +#ifndef DOXYGEN_SKIP + /** + * Used for [] set from Squirrel. + */ + SQInteger _set(HSQUIRRELVM vm); +#endif /* DOXYGEN_SKIP */ +}; + +#endif /* AI_LIST_HPP */ diff --git a/src/ai/api/ai_list.hpp.sq b/src/ai/api/ai_list.hpp.sq new file mode 100644 index 000000000..dfa270bbf --- /dev/null +++ b/src/ai/api/ai_list.hpp.sq @@ -0,0 +1,28 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_list.hpp" + +namespace SQConvert { + /* Allow AIList to be used as Squirrel parameter */ + template <> AIList *GetParam(ForceType<AIList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIList *)instance; } + template <> AIList &GetParam(ForceType<AIList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIList *)instance; } + template <> const AIList *GetParam(ForceType<const AIList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIList *)instance; } + template <> const AIList &GetParam(ForceType<const AIList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIList *)instance; } + template <> int Return<AIList *>(HSQUIRRELVM vm, AIList *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIList", res, NULL, DefSQDestructorCallback<AIList>); return 1; } +}; // namespace SQConvert + +void SQAIList_Register(Squirrel *engine) { + DefSQClass <AIList> SQAIList("AIList"); + SQAIList.PreRegister(engine, "AIAbstractList"); + SQAIList.AddConstructor<void (AIList::*)(), 1>(engine, "x"); + + SQAIList.DefSQStaticMethod(engine, &AIList::GetClassName, "GetClassName", 1, "x"); + + SQAIList.DefSQMethod(engine, &AIList::AddItem, "AddItem", 3, "xii"); + SQAIList.DefSQMethod(engine, &AIList::ChangeItem, "ChangeItem", 3, "xii"); + SQAIList.DefSQMethod(engine, &AIList::RemoveItem, "RemoveItem", 2, "xi"); + SQAIList.DefSQAdvancedMethod(engine, &AIList::_set, "_set"); + + SQAIList.PostRegister(engine); +} diff --git a/src/ai/api/ai_log.cpp b/src/ai/api/ai_log.cpp new file mode 100644 index 000000000..6dc9cb365 --- /dev/null +++ b/src/ai/api/ai_log.cpp @@ -0,0 +1,84 @@ +/* $Id$ */ + +/** @file ai_log.cpp Implementation of AILog. */ + +#include "ai_log.hpp" +#include "../../core/alloc_func.hpp" +#include "../../company_func.h" +#include "../../debug.h" +#include "../../window_func.h" + +/* static */ void AILog::Info(const char *message) +{ + AILog::Log(LOG_INFO, message); +} + +/* static */ void AILog::Warning(const char *message) +{ + AILog::Log(LOG_WARNING, message); +} + +/* static */ void AILog::Error(const char *message) +{ + AILog::Log(LOG_ERROR, message); +} + +/* static */ void AILog::Log(AILog::AILogType level, const char *message) +{ + if (AIObject::GetLogPointer() == NULL) { + AIObject::GetLogPointer() = new LogData(); + LogData *log = (LogData *)AIObject::GetLogPointer(); + + log->lines = CallocT<char *>(80); + log->type = CallocT<AILog::AILogType>(80); + log->count = 80; + log->pos = log->count; + log->used = 0; + } + LogData *log = (LogData *)AIObject::GetLogPointer(); + + /* Go to the next log-line */ + log->pos = (log->pos + 1) % log->count; + + if (log->used != log->count) log->used++; + + /* Free last message, and write new message */ + free(log->lines[log->pos]); + log->lines[log->pos] = strdup(message); + log->type[log->pos] = level; + + /* Cut string after first \n */ + char *p; + while ((p = strchr(log->lines[log->pos], '\n')) != NULL) { + *p = '\0'; + break; + } + + char logc; + + switch (level) { + case LOG_SQ_ERROR: logc = 'S'; break; + case LOG_ERROR: logc = 'E'; break; + case LOG_SQ_INFO: logc = 'P'; break; + case LOG_WARNING: logc = 'W'; break; + case LOG_INFO: logc = 'I'; break; + default: logc = '?'; break; + } + + /* Also still print to debug window */ + DEBUG(ai, level, "[%d] [%c] %s", (uint)_current_company, logc, log->lines[log->pos]); + InvalidateWindowData(WC_AI_DEBUG, 0, _current_company); +} + +/* static */ void AILog::FreeLogPointer() +{ + LogData *log = (LogData *)AIObject::GetLogPointer(); + + for (int i = 0; i < log->count; i++) { + free(log->lines[i]); + } + + free(log->lines); + free(log->type); + delete log; +} diff --git a/src/ai/api/ai_log.hpp b/src/ai/api/ai_log.hpp new file mode 100644 index 000000000..7fd1cdb64 --- /dev/null +++ b/src/ai/api/ai_log.hpp @@ -0,0 +1,76 @@ +/* $Id$ */ + +/** @file ai_log.hpp Everything to handle and issue log messages. */ + +#ifndef AI_LOG_HPP +#define AI_LOG_HPP + +#include "ai_object.hpp" + +/** + * Class that handles all log related functions. + */ +class AILog : public AIObject { + /* AIController needs access to Enum and Log, in order to keep the flow from + * OpenTTD core to NoAI API clear and simple. */ + friend class AIController; + +public: + static const char *GetClassName() { return "AILog"; } + + /** + * Log levels; The value is also feed to DEBUG() lvl. + * This has no use for you, as AI writer. + */ + enum AILogType { + LOG_SQ_ERROR = 0, //!< Squirrel printed an error. + LOG_ERROR = 1, //!< User printed an error. + LOG_SQ_INFO = 2, //!< Squirrel printed some info. + LOG_WARNING = 3, //!< User printed some warning. + LOG_INFO = 4, //!< User printed some info. + }; + + /** + * Internal representation of the log-data inside the AI. + * This has no use for you, as AI writer. + */ + struct LogData { + char **lines; //!< The log-lines. + AILog::AILogType *type; //!< Per line, which type of log it was. + int count; //!< Total amount of log-lines possible. + int pos; //!< Current position in lines. + int used; //!< Total amount of used log-lines. + }; + + /** + * Print an Info message to the logs. + * @param message The message to log. + */ + static void Info(const char *message); + + /** + * Print a Warning message to the logs. + * @param message The message to log. + */ + static void Warning(const char *message); + + /** + * Print an Error message to the logs. + * @param message The message to log. + */ + static void Error(const char *message); + + /** + * Free the log pointer. + * @note DO NOT CALL YOURSELF; leave it to the internal AI programming. + */ + static void FreeLogPointer(); + +private: + /** + * Internal command to log the message in a common way. + */ + static void Log(AILog::AILogType level, const char *message); +}; + +#endif /* AI_LOG_HPP */ diff --git a/src/ai/api/ai_log.hpp.sq b/src/ai/api/ai_log.hpp.sq new file mode 100644 index 000000000..7b84dc8f1 --- /dev/null +++ b/src/ai/api/ai_log.hpp.sq @@ -0,0 +1,37 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_log.hpp" + +namespace SQConvert { + /* Allow enums to be used as Squirrel parameters */ + template <> AILog::AILogType GetParam(ForceType<AILog::AILogType>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AILog::AILogType)tmp; } + template <> int Return<AILog::AILogType>(HSQUIRRELVM vm, AILog::AILogType res) { sq_pushinteger(vm, (int32)res); return 1; } + + /* Allow AILog to be used as Squirrel parameter */ + template <> AILog *GetParam(ForceType<AILog *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AILog *)instance; } + template <> AILog &GetParam(ForceType<AILog &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AILog *)instance; } + template <> const AILog *GetParam(ForceType<const AILog *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AILog *)instance; } + template <> const AILog &GetParam(ForceType<const AILog &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AILog *)instance; } + template <> int Return<AILog *>(HSQUIRRELVM vm, AILog *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AILog", res, NULL, DefSQDestructorCallback<AILog>); return 1; } +}; // namespace SQConvert + +void SQAILog_Register(Squirrel *engine) { + DefSQClass <AILog> SQAILog("AILog"); + SQAILog.PreRegister(engine); + SQAILog.AddConstructor<void (AILog::*)(), 1>(engine, "x"); + + SQAILog.DefSQConst(engine, AILog::LOG_SQ_ERROR, "LOG_SQ_ERROR"); + SQAILog.DefSQConst(engine, AILog::LOG_ERROR, "LOG_ERROR"); + SQAILog.DefSQConst(engine, AILog::LOG_SQ_INFO, "LOG_SQ_INFO"); + SQAILog.DefSQConst(engine, AILog::LOG_WARNING, "LOG_WARNING"); + SQAILog.DefSQConst(engine, AILog::LOG_INFO, "LOG_INFO"); + + SQAILog.DefSQStaticMethod(engine, &AILog::GetClassName, "GetClassName", 1, "x"); + SQAILog.DefSQStaticMethod(engine, &AILog::Info, "Info", 2, "xs"); + SQAILog.DefSQStaticMethod(engine, &AILog::Warning, "Warning", 2, "xs"); + SQAILog.DefSQStaticMethod(engine, &AILog::Error, "Error", 2, "xs"); + SQAILog.DefSQStaticMethod(engine, &AILog::FreeLogPointer, "FreeLogPointer", 1, "x"); + + SQAILog.PostRegister(engine); +} diff --git a/src/ai/api/ai_map.cpp b/src/ai/api/ai_map.cpp new file mode 100644 index 000000000..2c8547f95 --- /dev/null +++ b/src/ai/api/ai_map.cpp @@ -0,0 +1,62 @@ +/* $Id$ */ + +/** @file ai_map.cpp Implementation of AIMap. */ + +#include "ai_map.hpp" +#include "../../map_func.h" +#include "../../tile_map.h" + +/* static */ bool AIMap::IsValidTile(TileIndex t) +{ + return ::IsValidTile(t); +} + +/* static */ TileIndex AIMap::GetMapSize() +{ + return ::MapSize(); +} + +/* static */ uint32 AIMap::GetMapSizeX() +{ + return ::MapSizeX(); +} + +/* static */ uint32 AIMap::GetMapSizeY() +{ + return ::MapSizeY(); +} + +/* static */ uint32 AIMap::GetTileX(TileIndex t) +{ + return ::TileX(t); +} + +/* static */ uint32 AIMap::GetTileY(TileIndex t) +{ + return ::TileY(t); +} + +/* static */ TileIndex AIMap::GetTileIndex(uint32 x, uint32 y) +{ + return ::TileXY(x, y); +} + +/* static */ uint32 AIMap::DistanceManhattan(TileIndex t1, TileIndex t2) +{ + return ::DistanceManhattan(t1, t2); +} + +/* static */ uint32 AIMap::DistanceMax(TileIndex t1, TileIndex t2) +{ + return ::DistanceMax(t1, t2); +} + +/* static */ uint32 AIMap::DistanceSquare(TileIndex t1, TileIndex t2) +{ + return ::DistanceSquare(t1, t2); +} + +/* static */ uint32 AIMap::DistanceFromEdge(TileIndex t) +{ + return ::DistanceFromEdge(t); +} diff --git a/src/ai/api/ai_map.hpp b/src/ai/api/ai_map.hpp new file mode 100644 index 000000000..f14c360eb --- /dev/null +++ b/src/ai/api/ai_map.hpp @@ -0,0 +1,117 @@ +/* $Id$ */ + +/** @file ai_map.hpp Everything to query and manipulate map metadata. */ + +#ifndef AI_MAP_HPP +#define AI_MAP_HPP + +#include "ai_object.hpp" + +/** + * Class that handles all map related functions. + */ +class AIMap : public AIObject { +public: + static const char *GetClassName() { return "AIMap"; } + + /** + * Checks whether the given tile is valid. + * @param tile The tile to check. + * @return True is the tile it within the boundaries of the map. + */ + static bool IsValidTile(TileIndex tile); + + /** + * Gets the number of tiles in the map. + * @return The size of the map in tiles. + * @post Return value is always positive. + */ + static TileIndex GetMapSize(); + + /** + * Gets the amount of tiles along the SW and NE border. + * @return The length along the SW and NE borders. + * @post Return value is always positive. + */ + static uint32 GetMapSizeX(); + + /** + * Gets the amount of tiles along the SE and NW border. + * @return The length along the SE and NW borders. + * @post Return value is always positive. + */ + static uint32 GetMapSizeY(); + + /** + * Gets the place along the SW/NE border (X-value). + * @param tile The tile to get the X-value of. + * @pre IsValidTile(tile). + * @return The X-value. + * @post Return value is always lower than GetMapSizeX(). + */ + static uint32 GetTileX(TileIndex tile); + + /** + * Gets the place along the SE/NW border (Y-value). + * @param tile The tile to get the Y-value of. + * @pre IsValidTile(tile). + * @return The Y-value. + * @post Return value is always lower than GetMapSizeY(). + */ + static uint32 GetTileY(TileIndex tile); + + /** + * Gets the TileIndex given a x,y-coordinate. + * @param x The X coordinate. + * @param y The Y coordinate. + * @pre x < GetMapSizeX(). + * @pre y < GetMapSizeY(). + * @return The TileIndex for the given (x,y) coordinate. + */ + static TileIndex GetTileIndex(uint32 x, uint32 y); + + /** + * Calculates the Manhattan distance; the difference of + * the X and Y added together. + * @param tile_from The start tile. + * @param tile_to The destination tile. + * @pre IsValidTile(tile_from). + * @pre IsValidTile(tile_to). + * @return The Manhattan distance between the tiles. + */ + static uint32 DistanceManhattan(TileIndex tile_from, TileIndex tile_to); + + /** + * Calculates the distance between two tiles via 1D calculation. + * This means the distance between X or the distance between Y, depending + * on which one is bigger. + * @param tile_from The start tile. + * @param tile_to The destination tile. + * @pre IsValidTile(tile_from). + * @pre IsValidTile(tile_to). + * @return The maximum distance between the tiles. + */ + static uint32 DistanceMax(TileIndex tile_from, TileIndex tile_to); + + /** + * The squared distance between the two tiles. + * This is the distance is the length of the shortest straight line + * between both points. + * @param tile_from The start tile. + * @param tile_to The destination tile. + * @pre IsValidTile(tile_from). + * @pre IsValidTile(tile_to). + * @return The squared distance between the tiles. + */ + static uint32 DistanceSquare(TileIndex tile_from, TileIndex tile_to); + + /** + * Calculates the shortest distance to the edge. + * @param tile From where the distance has to be calculated. + * @pre IsValidTile(tile). + * @return The distances to the closest edge. + */ + static uint32 DistanceFromEdge(TileIndex tile); +}; + +#endif /* AI_MAP_HPP */ diff --git a/src/ai/api/ai_map.hpp.sq b/src/ai/api/ai_map.hpp.sq new file mode 100644 index 000000000..f65368c8f --- /dev/null +++ b/src/ai/api/ai_map.hpp.sq @@ -0,0 +1,34 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_map.hpp" + +namespace SQConvert { + /* Allow AIMap to be used as Squirrel parameter */ + template <> AIMap *GetParam(ForceType<AIMap *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIMap *)instance; } + template <> AIMap &GetParam(ForceType<AIMap &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIMap *)instance; } + template <> const AIMap *GetParam(ForceType<const AIMap *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIMap *)instance; } + template <> const AIMap &GetParam(ForceType<const AIMap &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIMap *)instance; } + template <> int Return<AIMap *>(HSQUIRRELVM vm, AIMap *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIMap", res, NULL, DefSQDestructorCallback<AIMap>); return 1; } +}; // namespace SQConvert + +void SQAIMap_Register(Squirrel *engine) { + DefSQClass <AIMap> SQAIMap("AIMap"); + SQAIMap.PreRegister(engine); + SQAIMap.AddConstructor<void (AIMap::*)(), 1>(engine, "x"); + + SQAIMap.DefSQStaticMethod(engine, &AIMap::GetClassName, "GetClassName", 1, "x"); + SQAIMap.DefSQStaticMethod(engine, &AIMap::IsValidTile, "IsValidTile", 2, "xi"); + SQAIMap.DefSQStaticMethod(engine, &AIMap::GetMapSize, "GetMapSize", 1, "x"); + SQAIMap.DefSQStaticMethod(engine, &AIMap::GetMapSizeX, "GetMapSizeX", 1, "x"); + SQAIMap.DefSQStaticMethod(engine, &AIMap::GetMapSizeY, "GetMapSizeY", 1, "x"); + SQAIMap.DefSQStaticMethod(engine, &AIMap::GetTileX, "GetTileX", 2, "xi"); + SQAIMap.DefSQStaticMethod(engine, &AIMap::GetTileY, "GetTileY", 2, "xi"); + SQAIMap.DefSQStaticMethod(engine, &AIMap::GetTileIndex, "GetTileIndex", 3, "xii"); + SQAIMap.DefSQStaticMethod(engine, &AIMap::DistanceManhattan, "DistanceManhattan", 3, "xii"); + SQAIMap.DefSQStaticMethod(engine, &AIMap::DistanceMax, "DistanceMax", 3, "xii"); + SQAIMap.DefSQStaticMethod(engine, &AIMap::DistanceSquare, "DistanceSquare", 3, "xii"); + SQAIMap.DefSQStaticMethod(engine, &AIMap::DistanceFromEdge, "DistanceFromEdge", 2, "xi"); + + SQAIMap.PostRegister(engine); +} diff --git a/src/ai/api/ai_marine.cpp b/src/ai/api/ai_marine.cpp new file mode 100644 index 000000000..274ab5f6b --- /dev/null +++ b/src/ai/api/ai_marine.cpp @@ -0,0 +1,143 @@ +/* $Id$ */ + +/** @file ai_marine.cpp Implementation of AIMarine. */ + +#include "ai_marine.hpp" +#include "../../openttd.h" +#include "../../command_type.h" +#include "../../variables.h" +#include "../../station_map.h" +#include "../../water_map.h" +#include "../../tile_cmd.h" + + +/* static */ bool AIMarine::IsWaterDepotTile(TileIndex tile) +{ + if (!::IsValidTile(tile)) return false; + + return ::IsTileType(tile, MP_WATER) && ::GetWaterTileType(tile) == WATER_TILE_DEPOT; +} + +/* static */ bool AIMarine::IsDockTile(TileIndex tile) +{ + if (!::IsValidTile(tile)) return false; + + return ::IsTileType(tile, MP_STATION) && ::IsDock(tile); +} + +/* static */ bool AIMarine::IsBuoyTile(TileIndex tile) +{ + if (!::IsValidTile(tile)) return false; + + return ::IsTileType(tile, MP_STATION) && ::IsBuoy(tile); +} + +/* static */ bool AIMarine::IsLockTile(TileIndex tile) +{ + if (!::IsValidTile(tile)) return false; + + return ::IsTileType(tile, MP_WATER) && ::GetWaterTileType(tile) == WATER_TILE_LOCK; +} + +/* static */ bool AIMarine::IsCanalTile(TileIndex tile) +{ + if (!::IsValidTile(tile)) return false; + + return ::IsTileType(tile, MP_WATER) && ::IsCanal(tile); +} + +/* static */ bool AIMarine::AreWaterTilesConnected(TileIndex t1, TileIndex t2) +{ + if (!::IsValidTile(t1)) return false; + if (!::IsValidTile(t2)) return false; + + /* Tiles not neighbouring */ + if (::DistanceManhattan(t1, t2) != 1) return false; + if (t1 > t2) Swap(t1, t2); + + uint32 gtts1 = ::GetTileTrackStatus(t1, TRANSPORT_WATER, 0); + uint32 gtts2 = ::GetTileTrackStatus(t2, TRANSPORT_WATER, 0); + + /* Ship can't travel on one of the tiles. */ + if (gtts1 == 0 || gtts2 == 0) return false; + + DiagDirection to_other_tile = (TileX(t1) == TileX(t2)) ? DIAGDIR_SE : DIAGDIR_SW; + + /* Check whether we can 'leave' the tile at the border and 'enter' the other tile at the border */ + return (gtts1 & DiagdirReachesTrackdirs(ReverseDiagDir(to_other_tile))) != 0 && (gtts2 & DiagdirReachesTrackdirs(to_other_tile)) != 0; +} + +/* static */ bool AIMarine::BuildWaterDepot(TileIndex tile, bool vertical) +{ + EnforcePrecondition(false, ::IsValidTile(tile)); + + return AIObject::DoCommand(tile, vertical, 0, CMD_BUILD_SHIP_DEPOT); +} + +/* static */ bool AIMarine::BuildDock(TileIndex tile, bool join_adjacent) +{ + EnforcePrecondition(false, ::IsValidTile(tile)); + + return AIObject::DoCommand(tile, join_adjacent ? 0 : 1, INVALID_STATION << 16, CMD_BUILD_DOCK); +} + +/* static */ bool AIMarine::BuildBuoy(TileIndex tile) +{ + EnforcePrecondition(false, ::IsValidTile(tile)); + + return AIObject::DoCommand(tile, 0, 0, CMD_BUILD_BUOY); +} + +/* static */ bool AIMarine::BuildLock(TileIndex tile) +{ + EnforcePrecondition(false, ::IsValidTile(tile)); + + return AIObject::DoCommand(tile, 0, 0, CMD_BUILD_LOCK); +} + +/* static */ bool AIMarine::BuildCanal(TileIndex tile) +{ + EnforcePrecondition(false, ::IsValidTile(tile)); + + return AIObject::DoCommand(tile, tile, 0, CMD_BUILD_CANAL); +} + +/* static */ bool AIMarine::RemoveWaterDepot(TileIndex tile) +{ + EnforcePrecondition(false, ::IsValidTile(tile)); + EnforcePrecondition(false, IsWaterDepotTile(tile)); + + return AIObject::DoCommand(tile, 0, 0, CMD_LANDSCAPE_CLEAR); +} + +/* static */ bool AIMarine::RemoveDock(TileIndex tile) +{ + EnforcePrecondition(false, ::IsValidTile(tile)); + EnforcePrecondition(false, IsDockTile(tile)); + + return AIObject::DoCommand(tile, 0, 0, CMD_LANDSCAPE_CLEAR); +} + +/* static */ bool AIMarine::RemoveBuoy(TileIndex tile) +{ + EnforcePrecondition(false, ::IsValidTile(tile)); + EnforcePrecondition(false, IsBuoyTile(tile)); + + return AIObject::DoCommand(tile, 0, 0, CMD_LANDSCAPE_CLEAR); +} + +/* static */ bool AIMarine::RemoveLock(TileIndex tile) +{ + EnforcePrecondition(false, ::IsValidTile(tile)); + EnforcePrecondition(false, IsLockTile(tile)); + + return AIObject::DoCommand(tile, 0, 0, CMD_LANDSCAPE_CLEAR); +} + +/* static */ bool AIMarine::RemoveCanal(TileIndex tile) +{ + EnforcePrecondition(false, ::IsValidTile(tile)); + EnforcePrecondition(false, IsCanalTile(tile)); + + return AIObject::DoCommand(tile, 0, 0, CMD_LANDSCAPE_CLEAR); +} diff --git a/src/ai/api/ai_marine.hpp b/src/ai/api/ai_marine.hpp new file mode 100644 index 000000000..9f871887d --- /dev/null +++ b/src/ai/api/ai_marine.hpp @@ -0,0 +1,187 @@ +/* $Id$ */ + +/** @file ai_marine.hpp Everything to query and build marine. */ + +#ifndef AI_MARINE_HPP +#define AI_MARINE_HPP + +#include "ai_object.hpp" +#include "ai_error.hpp" + +/** + * Class that handles all marine related functions. + */ +class AIMarine : public AIObject { +public: + static const char *GetClassName() { return "AIMarine"; } + + /** + * All marine related error messages. + */ + enum ErrorMessages { + /** Base for marine related errors */ + ERR_MARINE_BASE = AIError::ERR_CAT_MARINE << AIError::ERR_CAT_BIT_SIZE, + + /** Infrastructure must be built on water */ + ERR_MARINE_MUST_BE_BUILT_ON_WATER, // [STR_3801_MUST_BE_BUILT_ON_WATER] + }; + + /** + * Checks whether the given tile is actually a tile with a water depot. + * @param tile The tile to check. + * @pre AIMap::IsValidTile(tile). + * @return True if and only if the tile has a water depot. + */ + static bool IsWaterDepotTile(TileIndex tile); + + /** + * Checks whether the given tile is actually a tile with a dock. + * @param tile The tile to check. + * @pre AIMap::IsValidTile(tile). + * @return True if and only if the tile has a dock. + */ + static bool IsDockTile(TileIndex tile); + + /** + * Checks whether the given tile is actually a tile with a buoy. + * @param tile The tile to check. + * @pre AIMap::IsValidTile(tile). + * @return True if and only if the tile has a buoy. + */ + static bool IsBuoyTile(TileIndex tile); + + /** + * Checks whether the given tile is actually a tile with a lock. + * @param tile The tile to check. + * @pre AIMap::IsValidTile(tile). + * @return True if and only if the tile has a lock. + */ + static bool IsLockTile(TileIndex tile); + + /** + * Checks whether the given tile is actually a tile with a canal. + * @param tile The tile to check. + * @pre AIMap::IsValidTile(tile). + * @return True if and only if the tile has a canal. + */ + static bool IsCanalTile(TileIndex tile); + + /** + * Checks whether the given tiles are directly connected, i.e. whether + * a ship vehicle can travel from the center of the first tile to the + * center of the second tile. + * @param tile_from The source tile. + * @param tile_to The destination tile. + * @pre AIMap::IsValidTile(tile_from). + * @pre AIMap::IsValidTile(tile_to). + * @pre 'tile_from' and 'tile_to' are directly neighbouring tiles. + * @return True if and only if a ship can go from tile_from to tile_to. + */ + static bool AreWaterTilesConnected(TileIndex tile_from, TileIndex tile_to); + + /** + * Builds a water depot on tile. + * @param tile The tile where the water depot will be build. + * @param vertical If true, depot will be vertical, else horizontal. + * @pre AIMap::IsValidTile(tile). + * @exception AIError::ERR_AREA_NOT_CLEAR + * @exception AIError::ERR_SITE_UNSUITABLE + * @exception AIMarine::ERR_MARINE_MUST_BE_BUILT_ON_WATER + * @return Whether the water depot has been/can be build or not. + * @note A WaterDepot is 1 tile in width, and 2 tiles in length. + */ + static bool BuildWaterDepot(TileIndex tile, bool vertical); + + /** + * Builds a dock where tile is the tile still on land. + * @param tile The tile still on land of the dock. + * @param join_adjacent When building next to an other station, don't create a new station when this flag is true. + * @pre AIMap::IsValidTile(tile). + * @exception AIError::ERR_AREA_NOT_CLEAR + * @exception AIError::ERR_SITE_UNSUITABLE + * @exception AIStation::ERR_STATION_TOO_CLOSE_TO_ANOTHER_STATION + * @exception AIStation::ERR_STATION_TOO_MANY_STATIONS + * @return Whether the dock has been/can be build or not. + */ + static bool BuildDock(TileIndex tile, bool join_adjacent); + + /** + * Builds a buoy on tile. + * @param tile The tile where the buoy will be build. + * @pre AIMap::IsValidTile(tile). + * @exception AIError::ERR_AREA_NOT_CLEAR + * @exception AIError::ERR_SITE_UNSUITABLE + * @exception AIStation::ERR_STATION_TOO_MANY_STATIONS + * @return Whether the buoy has been/can be build or not. + */ + static bool BuildBuoy(TileIndex tile); + + /** + * Builds a lock on tile. + * @param tile The tile where the lock will be build. + * @pre AIMap::IsValidTile(tile). + * @exception AIError::ERR_LAND_SLOPED_WRONG + * @exception AIError::ERR_SITE_UNSUITABLE + * @return Whether the lock has been/can be build or not. + */ + static bool BuildLock(TileIndex tile); + + /** + * Builds a canal on tile. + * @param tile The tile where the canal will be build. + * @pre AIMap::IsValidTile(tile). + * @exception AIError::ERR_AREA_NOT_CLEAR + * @exception AIError::ERR_LAND_SLOPED_WRONG + * @exception AIError::ERR_OWNED_BY_ANOTHER_COMPANY + * @exception AIError::ERR_ALREADY_BUILT + * @return Whether the canal has been/can be build or not. + */ + static bool BuildCanal(TileIndex tile); + + /** + * Removes a water depot. + * @param tile Any tile of the water depot. + * @pre AIMap::IsValidTile(tile). + * @exception AIError::ERR_OWNED_BY_ANOTHER_COMPANY + * @return Whether the water depot has been/can be removed or not. + */ + static bool RemoveWaterDepot(TileIndex tile); + + /** + * Removes a dock. + * @param tile Any tile of the dock. + * @pre AIMap::IsValidTile(tile). + * @exception AIError::ERR_OWNED_BY_ANOTHER_COMPANY + * @return Whether the dock has been/can be removed or not. + */ + static bool RemoveDock(TileIndex tile); + + /** + * Removes a buoy. + * @param tile Any tile of the buoy. + * @pre AIMap::IsValidTile(tile). + * @exception AIError::ERR_OWNED_BY_ANOTHER_COMPANY + * @return Whether the buoy has been/can be removed or not. + */ + static bool RemoveBuoy(TileIndex tile); + + /** + * Removes a lock. + * @param tile Any tile of the lock. + * @pre AIMap::IsValidTile(tile). + * @exception AIError::ERR_OWNED_BY_ANOTHER_COMPANY + * @return Whether the lock has been/can be removed or not. + */ + static bool RemoveLock(TileIndex tile); + + /** + * Removes a canal. + * @param tile Any tile of the canal. + * @pre AIMap::IsValidTile(tile). + * @exception AIError::ERR_OWNED_BY_ANOTHER_COMPANY + * @return Whether the canal has been/can be removed or not. + */ + static bool RemoveCanal(TileIndex tile); +}; + +#endif /* AI_MARINE_HPP */ diff --git a/src/ai/api/ai_marine.hpp.sq b/src/ai/api/ai_marine.hpp.sq new file mode 100644 index 000000000..269c99604 --- /dev/null +++ b/src/ai/api/ai_marine.hpp.sq @@ -0,0 +1,50 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_marine.hpp" + +namespace SQConvert { + /* Allow enums to be used as Squirrel parameters */ + template <> AIMarine::ErrorMessages GetParam(ForceType<AIMarine::ErrorMessages>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AIMarine::ErrorMessages)tmp; } + template <> int Return<AIMarine::ErrorMessages>(HSQUIRRELVM vm, AIMarine::ErrorMessages res) { sq_pushinteger(vm, (int32)res); return 1; } + + /* Allow AIMarine to be used as Squirrel parameter */ + template <> AIMarine *GetParam(ForceType<AIMarine *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIMarine *)instance; } + template <> AIMarine &GetParam(ForceType<AIMarine &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIMarine *)instance; } + template <> const AIMarine *GetParam(ForceType<const AIMarine *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIMarine *)instance; } + template <> const AIMarine &GetParam(ForceType<const AIMarine &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIMarine *)instance; } + template <> int Return<AIMarine *>(HSQUIRRELVM vm, AIMarine *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIMarine", res, NULL, DefSQDestructorCallback<AIMarine>); return 1; } +}; // namespace SQConvert + +void SQAIMarine_Register(Squirrel *engine) { + DefSQClass <AIMarine> SQAIMarine("AIMarine"); + SQAIMarine.PreRegister(engine); + SQAIMarine.AddConstructor<void (AIMarine::*)(), 1>(engine, "x"); + + SQAIMarine.DefSQConst(engine, AIMarine::ERR_MARINE_BASE, "ERR_MARINE_BASE"); + SQAIMarine.DefSQConst(engine, AIMarine::ERR_MARINE_MUST_BE_BUILT_ON_WATER, "ERR_MARINE_MUST_BE_BUILT_ON_WATER"); + + AIError::RegisterErrorMap(STR_3801_MUST_BE_BUILT_ON_WATER, AIMarine::ERR_MARINE_MUST_BE_BUILT_ON_WATER); + + AIError::RegisterErrorMapString(AIMarine::ERR_MARINE_MUST_BE_BUILT_ON_WATER, "ERR_MARINE_MUST_BE_BUILT_ON_WATER"); + + SQAIMarine.DefSQStaticMethod(engine, &AIMarine::GetClassName, "GetClassName", 1, "x"); + SQAIMarine.DefSQStaticMethod(engine, &AIMarine::IsWaterDepotTile, "IsWaterDepotTile", 2, "xi"); + SQAIMarine.DefSQStaticMethod(engine, &AIMarine::IsDockTile, "IsDockTile", 2, "xi"); + SQAIMarine.DefSQStaticMethod(engine, &AIMarine::IsBuoyTile, "IsBuoyTile", 2, "xi"); + SQAIMarine.DefSQStaticMethod(engine, &AIMarine::IsLockTile, "IsLockTile", 2, "xi"); + SQAIMarine.DefSQStaticMethod(engine, &AIMarine::IsCanalTile, "IsCanalTile", 2, "xi"); + SQAIMarine.DefSQStaticMethod(engine, &AIMarine::AreWaterTilesConnected, "AreWaterTilesConnected", 3, "xii"); + SQAIMarine.DefSQStaticMethod(engine, &AIMarine::BuildWaterDepot, "BuildWaterDepot", 3, "xib"); + SQAIMarine.DefSQStaticMethod(engine, &AIMarine::BuildDock, "BuildDock", 3, "xib"); + SQAIMarine.DefSQStaticMethod(engine, &AIMarine::BuildBuoy, "BuildBuoy", 2, "xi"); + SQAIMarine.DefSQStaticMethod(engine, &AIMarine::BuildLock, "BuildLock", 2, "xi"); + SQAIMarine.DefSQStaticMethod(engine, &AIMarine::BuildCanal, "BuildCanal", 2, "xi"); + SQAIMarine.DefSQStaticMethod(engine, &AIMarine::RemoveWaterDepot, "RemoveWaterDepot", 2, "xi"); + SQAIMarine.DefSQStaticMethod(engine, &AIMarine::RemoveDock, "RemoveDock", 2, "xi"); + SQAIMarine.DefSQStaticMethod(engine, &AIMarine::RemoveBuoy, "RemoveBuoy", 2, "xi"); + SQAIMarine.DefSQStaticMethod(engine, &AIMarine::RemoveLock, "RemoveLock", 2, "xi"); + SQAIMarine.DefSQStaticMethod(engine, &AIMarine::RemoveCanal, "RemoveCanal", 2, "xi"); + + SQAIMarine.PostRegister(engine); +} diff --git a/src/ai/api/ai_object.cpp b/src/ai/api/ai_object.cpp new file mode 100644 index 000000000..664c24ecc --- /dev/null +++ b/src/ai/api/ai_object.cpp @@ -0,0 +1,264 @@ +/* $Id$ */ + +/** @file ai_object.cpp Implementation of AIObject. */ + +#include "ai_object.hpp" +#include "ai_log.hpp" +#include "ai_error.hpp" +#include "table/strings.h" +#include "../../openttd.h" +#include "../../command_func.h" +#include "../../map_func.h" +#include "../../network/network.h" +#include "../../company_func.h" +#include "../../signs_func.h" +#include "../../tunnelbridge.h" +#include "../../vehicle_func.h" +#include "../../group.h" +#include "../ai.hpp" +#include "ai_controller.hpp" +#include "../ai_storage.hpp" +#include "../ai_instance.hpp" + +#include <vector> + +static AIStorage *GetStorage() +{ + return AIInstance::GetStorage(); +} + +void AIObject::SetDoCommandDelay(uint ticks) +{ + assert(ticks > 0); + GetStorage()->delay = ticks; +} + +uint AIObject::GetDoCommandDelay() +{ + return GetStorage()->delay; +} + +void AIObject::SetDoCommandMode(AIModeProc *proc, AIObject *instance) +{ + GetStorage()->mode = proc; + GetStorage()->mode_instance = instance; +} + +AIModeProc *AIObject::GetDoCommandMode() +{ + return GetStorage()->mode; +} + +AIObject *AIObject::GetDoCommandModeInstance() +{ + return GetStorage()->mode_instance; +} + +void AIObject::SetDoCommandCosts(Money value) +{ + GetStorage()->costs = CommandCost(value); +} + +void AIObject::IncreaseDoCommandCosts(Money value) +{ + GetStorage()->costs.AddCost(value); +} + +Money AIObject::GetDoCommandCosts() +{ + return GetStorage()->costs.GetCost(); +} + +void AIObject::SetLastError(AIErrorType last_error) +{ + GetStorage()->last_error = last_error; +} + +AIErrorType AIObject::GetLastError() +{ + return GetStorage()->last_error; +} + +void AIObject::SetLastCost(Money last_cost) +{ + GetStorage()->last_cost = last_cost; +} + +Money AIObject::GetLastCost() +{ + return GetStorage()->last_cost; +} + +void AIObject::SetRoadType(RoadType road_type) +{ + GetStorage()->road_type = road_type; +} + +RoadType AIObject::GetRoadType() +{ + return GetStorage()->road_type; +} + +void AIObject::SetRailType(RailType rail_type) +{ + GetStorage()->rail_type = rail_type; +} + +RailType AIObject::GetRailType() +{ + return GetStorage()->rail_type; +} + +void AIObject::SetLastCommandRes(bool res) +{ + GetStorage()->last_command_res = res; + /* Also store the results of various global variables */ + SetNewVehicleID(_new_vehicle_id); + SetNewSignID(_new_sign_id); + SetNewTunnelEndtile(_build_tunnel_endtile); + SetNewGroupID(_new_group_id); +} + +bool AIObject::GetLastCommandRes() +{ + return GetStorage()->last_command_res; +} + +void AIObject::SetNewVehicleID(VehicleID vehicle_id) +{ + GetStorage()->new_vehicle_id = vehicle_id; +} + +VehicleID AIObject::GetNewVehicleID() +{ + return GetStorage()->new_vehicle_id; +} + +void AIObject::SetNewSignID(SignID sign_id) +{ + GetStorage()->new_sign_id = sign_id; +} + +SignID AIObject::GetNewSignID() +{ + return GetStorage()->new_sign_id; +} + +void AIObject::SetNewTunnelEndtile(TileIndex tile) +{ + GetStorage()->new_tunnel_endtile = tile; +} + +TileIndex AIObject::GetNewTunnelEndtile() +{ + return GetStorage()->new_tunnel_endtile; +} + +void AIObject::SetNewGroupID(GroupID group_id) +{ + GetStorage()->new_group_id = group_id; +} + +GroupID AIObject::GetNewGroupID() +{ + return GetStorage()->new_group_id; +} + +void AIObject::SetAllowDoCommand(bool allow) +{ + GetStorage()->allow_do_command = allow; +} + +bool AIObject::GetAllowDoCommand() +{ + return GetStorage()->allow_do_command; +} + +void *&AIObject::GetEventPointer() +{ + return GetStorage()->event_data; +} + +void *&AIObject::GetLogPointer() +{ + return GetStorage()->log_data; +} + +void AIObject::SetCallbackVariable(int index, int value) +{ + if ((size_t)index >= GetStorage()->callback_value.size()) GetStorage()->callback_value.resize(index + 1); + GetStorage()->callback_value[index] = value; +} + +int AIObject::GetCallbackVariable(int index) +{ + return GetStorage()->callback_value[index]; +} + +bool AIObject::DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint cmd, const char *text, AISuspendCallbackProc *callback) +{ + if (AIObject::GetAllowDoCommand() == false) { + AILog::Error("You are not allowed to execute any DoCommand (even indirect) in your constructor, Save(), and Load().\n"); + return false; + } + + CompanyID old_company; + CommandCost res; + + /* Set the default callback to return a true/false result of the DoCommand */ + if (callback == NULL) callback = &AIInstance::DoCommandReturn; + + /* Make sure the last error is reset, so we don't give faulty warnings */ + SetLastError(AIError::ERR_NONE); + + /* First, do a test-run to see if we can do this */ + res = ::DoCommand(tile, p1, p2, CommandFlagsToDCFlags(GetCommandFlags(cmd)), cmd, text); + /* The command failed, so return */ + if (::CmdFailed(res)) { + SetLastError(AIError::StringToError(_error_message)); + return false; + } + + /* Check what the callback wants us to do */ + if (GetDoCommandMode() != NULL && !GetDoCommandMode()(tile, p1, p2, cmd, res)) { + IncreaseDoCommandCosts(res.GetCost()); + return true; + } + +#ifdef ENABLE_NETWORK + /* Send the command */ + if (_networking) { + /* NetworkSend_Command needs _local_company to be set correctly, so + * adjust it, and put it back right after the function */ + old_company = _local_company; + _local_company = _current_company; + ::NetworkSend_Command(tile, p1, p2, cmd, CcAI, text); + _local_company = old_company; + SetLastCost(res.GetCost()); + + /* Suspend the AI till the command is really executed */ + throw AI_VMSuspend(-(int)GetDoCommandDelay(), callback); + } else { +#else + { +#endif + /* For SinglePlayer we execute the command immediatly */ + if (!::DoCommandP(tile, p1, p2, cmd, NULL, text)) res = CMD_ERROR; + SetLastCommandRes(!::CmdFailed(res)); + + if (::CmdFailed(res)) { + SetLastError(AIError::StringToError(_error_message)); + return false; + } + SetLastCost(res.GetCost()); + IncreaseDoCommandCosts(res.GetCost()); + + /* Suspend the AI player for 1+ ticks, so it simulates multiplayer. This + * both avoids confusion when a developer launched his AI in a + * multiplayer game, but also gives time for the GUI and human player + * to interact with the game. */ + throw AI_VMSuspend(GetDoCommandDelay(), callback); + } + + NOT_REACHED(); +} diff --git a/src/ai/api/ai_object.hpp b/src/ai/api/ai_object.hpp new file mode 100644 index 000000000..9dcb0f4cc --- /dev/null +++ b/src/ai/api/ai_object.hpp @@ -0,0 +1,202 @@ +/* $Id$ */ + +/** @file ai_object.hpp Main object, on which all objects depend. */ + +#ifndef AI_OBJECT_HPP +#define AI_OBJECT_HPP + +#include "../../stdafx.h" +#include "../../misc/countedptr.hpp" +#include "../../road_type.h" +#include "../../rail_type.h" + +#include "ai_types.hpp" + +/** + * The callback function when an AI suspends. + */ +typedef void (AISuspendCallbackProc)(class AIInstance *instance); + +/** + * The callback function for Mode-classes. + */ +typedef bool (AIModeProc)(TileIndex tile, uint32 p1, uint32 p2, uint cmd, CommandCost costs); + +/** + * 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 + * internally to have a common place to handle general things, like internal + * command processing, and command-validation checks. + */ +class AIObject : public SimpleCountedObject { +friend void CcAI(bool success, TileIndex tile, uint32 p1, uint32 p2); +friend class AIInstance; +friend class AIController; +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); + + /** + * Sets the DoCommand costs counter to a value. + */ + static void SetDoCommandCosts(Money value); + + /** + * Increase the current value of the DoCommand costs counter. + */ + static void IncreaseDoCommandCosts(Money value); + + /** + * Get the current DoCommand costs counter. + */ + static Money GetDoCommandCosts(); + + /** + * Set the DoCommand last error. + */ + static void SetLastError(AIErrorType last_error); + + /** + * Get the DoCommand last error. + */ + static AIErrorType GetLastError(); + + /** + * Set the road type. + */ + static void SetRoadType(RoadType road_type); + + /** + * Get the road type. + */ + static RoadType GetRoadType(); + + /** + * Set the rail type. + */ + static void SetRailType(RailType rail_type); + + /** + * Get the rail type. + */ + static RailType GetRailType(); + + /** + * Set the current mode of your AI to this proc. + */ + static void SetDoCommandMode(AIModeProc *proc, AIObject *instance); + + /** + * Get the current mode your AI is currently under. + */ + static AIModeProc *GetDoCommandMode(); + + /** + * Get the instance of the current mode your AI is currently under. + */ + static AIObject *GetDoCommandModeInstance(); + + /** + * Set the delay of the DoCommand. + */ + static void SetDoCommandDelay(uint ticks); + + /** + * Get the delay of the DoCommand. + */ + static uint GetDoCommandDelay(); + + /** + * Get the latest result of a DoCommand. + */ + static bool GetLastCommandRes(); + + /** + * Get the latest stored new_vehicle_id. + */ + static VehicleID GetNewVehicleID(); + + /** + * Get the latest stored new_sign_id. + */ + static SignID GetNewSignID(); + + /** + * Get the latest stored new_tunnel_endtile. + */ + static TileIndex GetNewTunnelEndtile(); + + /** + * Get the latest stored new_group_id. + */ + static GroupID GetNewGroupID(); + + /** + * Get the latest stored allow_do_command. + * If this is false, you are not allowed to do any DoCommands. + */ + static bool GetAllowDoCommand(); + + /** + * Get the pointer to store event data in. + */ + static void *&GetEventPointer(); + + static void SetLastCost(Money last_cost); + static Money GetLastCost(); + static void SetCallbackVariable(int index, int value); + static int GetCallbackVariable(int index); + +public: + /** + * Store the latest result of a DoCommand per company. + * @note NEVER use this yourself in your AI! + * @param res The result of the last command. + */ + static void SetLastCommandRes(bool res); + + /** + * Store a new_vehicle_id per company. + * @note NEVER use this yourself in your AI! + * @param vehicle_id The new VehicleID. + */ + static void SetNewVehicleID(VehicleID vehicle_id); + + /** + * Store a new_sign_id per company. + * @note NEVER use this yourself in your AI! + * @param sign_id The new SignID. + */ + static void SetNewSignID(SignID sign_id); + + /** + * Store a new_tunnel_endtile per company. + * @note NEVER use this yourself in your AI! + * @param tile The new TileIndex. + */ + static void SetNewTunnelEndtile(TileIndex tile); + + /** + * Store a new_group_id per company. + * @note NEVER use this yourself in your AI! + * @param group_id The new GroupID. + */ + static void SetNewGroupID(GroupID group_id); + + /** + * Store a allow_do_command per company. + * @note NEVER use this yourself in your AI! + * @param allow The new allow. + */ + static void SetAllowDoCommand(bool allow); + + /** + * Get the pointer to store log message in. + * @note NEVER use this yourself in your AI! + */ + static void *&GetLogPointer(); +}; + +#endif /* AI_OBJECT_HPP */ diff --git a/src/ai/api/ai_order.cpp b/src/ai/api/ai_order.cpp new file mode 100644 index 000000000..f48c5e392 --- /dev/null +++ b/src/ai/api/ai_order.cpp @@ -0,0 +1,296 @@ +/* $Id$ */ + +/** @file ai_order.cpp Implementation of AIOrder. */ + +#include "ai_order.hpp" +#include "ai_map.hpp" +#include "ai_vehicle.hpp" +#include "../ai_instance.hpp" +#include "../../openttd.h" +#include "../../debug.h" +#include "../../vehicle_base.h" +#include "../../depot_base.h" +#include "../../landscape.h" +#include "../../rail_map.h" +#include "../../road_map.h" +#include "../../station_map.h" +#include "../../water_map.h" +#include "../../waypoint.h" + +/** + * Gets the order type given a tile + * @param t the tile to get the order from + * @return the order type, or OT_END when there is no order + */ +static OrderType GetOrderTypeByTile(TileIndex t) +{ + if (!::IsValidTile(t)) return OT_END; + + switch (::GetTileType(t)) { + default: break; + case MP_STATION: return OT_GOTO_STATION; break; + case MP_WATER: if (::IsShipDepot(t)) return OT_GOTO_DEPOT; break; + case MP_ROAD: if (::GetRoadTileType(t) == ROAD_TILE_DEPOT) return OT_GOTO_DEPOT; break; + case MP_RAILWAY: + switch (::GetRailTileType(t)) { + case RAIL_TILE_DEPOT: return OT_GOTO_DEPOT; + case RAIL_TILE_WAYPOINT: return OT_GOTO_WAYPOINT; + default: break; + } + break; + } + + return OT_END; +} + +/* static */ bool AIOrder::IsValidVehicleOrder(VehicleID vehicle_id, OrderPosition order_position) +{ + return AIVehicle::IsValidVehicle(vehicle_id) && order_position >= 0 && (order_position < ::GetVehicle(vehicle_id)->GetNumOrders() || order_position == CURRENT_ORDER); +} + +/* static */ AIOrder::OrderPosition AIOrder::ResolveOrderPosition(VehicleID vehicle_id, OrderPosition order_position) +{ + if (!AIVehicle::IsValidVehicle(vehicle_id)) return INVALID_ORDER; + + if (order_position == CURRENT_ORDER) return (AIOrder::OrderPosition)::GetVehicle(vehicle_id)->cur_order_index; + return (order_position >= 0 && order_position < ::GetVehicle(vehicle_id)->GetNumOrders()) ? order_position : INVALID_ORDER; +} + + +/* static */ bool AIOrder::AreOrderFlagsValid(TileIndex destination, AIOrderFlags order_flags) +{ + switch (::GetOrderTypeByTile(destination)) { + case OT_GOTO_STATION: + return ((order_flags & ~(AIOF_NON_STOP_FLAGS | AIOF_UNLOAD_FLAGS | AIOF_LOAD_FLAGS)) == 0) && + /* Test the different mutual exclusive flags. */ + (((order_flags & AIOF_TRANSFER) == 0) || ((order_flags & AIOF_UNLOAD) == 0)) && + (((order_flags & AIOF_TRANSFER) == 0) || ((order_flags & AIOF_NO_UNLOAD) == 0)) && + (((order_flags & AIOF_UNLOAD) == 0) || ((order_flags & AIOF_NO_UNLOAD) == 0)) && + (((order_flags & AIOF_UNLOAD) == 0) || ((order_flags & AIOF_NO_UNLOAD) == 0)) && + (((order_flags & AIOF_NO_UNLOAD) == 0) || ((order_flags & AIOF_NO_LOAD) == 0)) && + (((order_flags & AIOF_FULL_LOAD_ANY) == 0) || ((order_flags & AIOF_NO_LOAD) == 0)); + + case OT_GOTO_DEPOT: return (order_flags & ~(AIOF_NON_STOP_FLAGS | AIOF_SERVICE_IF_NEEDED)) == 0; + case OT_GOTO_WAYPOINT: return (order_flags & ~(AIOF_NON_STOP_FLAGS)) == 0; + default: return false; + } +} + +/* static */ int32 AIOrder::GetOrderCount(VehicleID vehicle_id) +{ + return AIVehicle::IsValidVehicle(vehicle_id) ? ::GetVehicle(vehicle_id)->GetNumOrders() : -1; +} + +/* static */ TileIndex AIOrder::GetOrderDestination(VehicleID vehicle_id, OrderPosition order_position) +{ + if (!IsValidVehicleOrder(vehicle_id, order_position)) return INVALID_TILE; + + const Order *order; + const Vehicle *v = ::GetVehicle(vehicle_id); + if (order_position == CURRENT_ORDER) { + order = &v->current_order; + } else { + order = v->GetFirstOrder(); + for (int i = 0; i < order_position; i++) order = order->next; + } + + switch (order->GetType()) { + case OT_GOTO_DEPOT: + if (v->type != VEH_AIRCRAFT) return ::GetDepot(order->GetDestination())->xy; + /* FALL THROUGH: aircraft's hangars are referenced by StationID, not DepotID */ + + case OT_GOTO_STATION: return ::GetStation(order->GetDestination())->xy; + case OT_GOTO_WAYPOINT: return ::GetWaypoint(order->GetDestination())->xy; + default: return INVALID_TILE; + } +} + +/* static */ AIOrder::AIOrderFlags AIOrder::GetOrderFlags(VehicleID vehicle_id, OrderPosition order_position) +{ + if (!IsValidVehicleOrder(vehicle_id, order_position)) return AIOF_INVALID; + + const Order *order; + if (order_position == CURRENT_ORDER) { + order = &::GetVehicle(vehicle_id)->current_order; + } else { + order = ::GetVehicle(vehicle_id)->GetFirstOrder(); + for (int i = 0; i < order_position; i++) order = order->next; + } + + AIOrderFlags order_flags = AIOF_NONE; + order_flags |= (AIOrderFlags)order->GetNonStopType(); + switch (order->GetType()) { + case OT_GOTO_DEPOT: + if (order->GetDepotOrderType() & ODTFB_SERVICE) order_flags |= AIOF_SERVICE_IF_NEEDED; + break; + + case OT_GOTO_STATION: + order_flags |= (AIOrderFlags)(order->GetLoadType() << 5); + order_flags |= (AIOrderFlags)(order->GetUnloadType() << 2); + break; + + default: break; + } + + return order_flags; +} + +/* static */ bool AIOrder::AppendOrder(VehicleID vehicle_id, TileIndex destination, AIOrderFlags order_flags) +{ + EnforcePrecondition(false, AIVehicle::IsValidVehicle(vehicle_id)); + return InsertOrder(vehicle_id, (AIOrder::OrderPosition)::GetVehicle(vehicle_id)->GetNumOrders(), destination, order_flags); +} + +/* static */ bool AIOrder::InsertOrder(VehicleID vehicle_id, OrderPosition order_position, TileIndex destination, AIOrder::AIOrderFlags order_flags) +{ + /* IsValidVehicleOrder is not good enough because it does not allow appending. */ + if (order_position == CURRENT_ORDER) order_position = AIOrder::ResolveOrderPosition(vehicle_id, order_position); + + EnforcePrecondition(false, AIVehicle::IsValidVehicle(vehicle_id)); + EnforcePrecondition(false, order_position >= 0 && order_position <= ::GetVehicle(vehicle_id)->GetNumOrders()); + EnforcePrecondition(false, AreOrderFlagsValid(destination, order_flags)); + + Order order; + switch (::GetOrderTypeByTile(destination)) { + case OT_GOTO_DEPOT: + order.MakeGoToDepot(::GetDepotByTile(destination)->index, (OrderDepotTypeFlags)(ODTFB_PART_OF_ORDERS | ((order_flags & AIOF_SERVICE_IF_NEEDED) ? ODTFB_SERVICE : 0))); + break; + + case OT_GOTO_STATION: + order.MakeGoToStation(::GetStationIndex(destination)); + order.SetLoadType((OrderLoadFlags)GB(order_flags, 5, 3)); + order.SetUnloadType((OrderUnloadFlags)GB(order_flags, 2, 3)); + break; + + case OT_GOTO_WAYPOINT: + order.MakeGoToWaypoint(::GetWaypointIndex(destination)); + break; + + default: + return false; + } + + order.SetNonStopType((OrderNonStopFlags)GB(order_flags, 0, 2)); + + return AIObject::DoCommand(0, vehicle_id | (order_position << 16), order.Pack(), CMD_INSERT_ORDER); +} + +/* static */ bool AIOrder::RemoveOrder(VehicleID vehicle_id, OrderPosition order_position) +{ + order_position = AIOrder::ResolveOrderPosition(vehicle_id, order_position); + + EnforcePrecondition(false, IsValidVehicleOrder(vehicle_id, order_position)); + + return AIObject::DoCommand(0, vehicle_id, order_position, CMD_DELETE_ORDER); +} + +/** + * Callback handler as ChangeOrder possibly needs multiple DoCommand calls + * to be able to set all order flags correctly. As we need to wait till the + * command has completed before we know the next bits to change we need to + * call the function multiple times. Each time it'll reduce the difference + * between the wanted and the current order. + * @param instance The AI we are doing the callback for. + */ +static void _DoCommandReturnChangeOrder(class AIInstance *instance) +{ + AIObject::SetLastCommandRes(AIOrder::_ChangeOrder()); + AIInstance::DoCommandReturn(instance); +} + +/* static */ bool AIOrder::_ChangeOrder() +{ + /* Make sure we don't go into an infinite loop */ + int retry = AIObject::GetCallbackVariable(3) - 1; + if (retry < 0) { + DEBUG(ai, 0, "Possible infinite loop in ChangeOrder detected"); + return false; + } + AIObject::SetCallbackVariable(3, retry); + + VehicleID vehicle_id = (VehicleID)AIObject::GetCallbackVariable(0); + OrderPosition order_position = (OrderPosition)AIObject::GetCallbackVariable(1); + AIOrderFlags order_flags = (AIOrderFlags)AIObject::GetCallbackVariable(2); + + order_position = AIOrder::ResolveOrderPosition(vehicle_id, order_position); + + EnforcePrecondition(false, IsValidVehicleOrder(vehicle_id, order_position)); + EnforcePrecondition(false, AreOrderFlagsValid(GetOrderDestination(vehicle_id, order_position), order_flags)); + + Order *order = ::GetVehicle(vehicle_id)->GetFirstOrder(); + for (int i = 0; i < order_position; i++) order = order->next; + + AIOrderFlags current = GetOrderFlags(vehicle_id, order_position); + + if ((current & AIOF_NON_STOP_FLAGS) != (order_flags & AIOF_NON_STOP_FLAGS)) { + return AIObject::DoCommand(0, vehicle_id | (order_position << 16), (order_flags & AIOF_NON_STOP_FLAGS) << 4 | MOF_NON_STOP, CMD_MODIFY_ORDER, NULL, &_DoCommandReturnChangeOrder); + } + + switch (order->GetType()) { + case OT_GOTO_DEPOT: + if ((current & AIOF_SERVICE_IF_NEEDED) != (order_flags & AIOF_SERVICE_IF_NEEDED)) { + return AIObject::DoCommand(0, vehicle_id | (order_position << 16), MOF_DEPOT_ACTION, CMD_MODIFY_ORDER, NULL, &_DoCommandReturnChangeOrder); + } + break; + + case OT_GOTO_STATION: + if ((current & AIOF_UNLOAD_FLAGS) != (order_flags & AIOF_UNLOAD_FLAGS)) { + return AIObject::DoCommand(0, vehicle_id | (order_position << 16), (order_flags & AIOF_UNLOAD_FLAGS) << 2 | MOF_UNLOAD, CMD_MODIFY_ORDER, NULL, &_DoCommandReturnChangeOrder); + } + if ((current & AIOF_LOAD_FLAGS) != (order_flags & AIOF_LOAD_FLAGS)) { + return AIObject::DoCommand(0, vehicle_id | (order_position << 16), (order_flags & AIOF_LOAD_FLAGS) >> 1 | MOF_LOAD, CMD_MODIFY_ORDER, NULL, &_DoCommandReturnChangeOrder); + } + break; + + default: break; + } + + assert(GetOrderFlags(vehicle_id, order_position) == order_flags); + + return true; +} + +/* static */ bool AIOrder::ChangeOrder(VehicleID vehicle_id, OrderPosition order_position, AIOrder::AIOrderFlags order_flags) +{ + AIObject::SetCallbackVariable(0, vehicle_id); + AIObject::SetCallbackVariable(1, order_position); + AIObject::SetCallbackVariable(2, order_flags); + /* In case another client(s) change orders at the same time we could + * end in an infinite loop. This stops that from happening ever. */ + AIObject::SetCallbackVariable(3, 8); + return AIOrder::_ChangeOrder(); +} + +/* static */ bool AIOrder::MoveOrder(VehicleID vehicle_id, OrderPosition order_position_move, OrderPosition order_position_target) +{ + order_position_move = AIOrder::ResolveOrderPosition(vehicle_id, order_position_move); + order_position_target = AIOrder::ResolveOrderPosition(vehicle_id, order_position_target); + + EnforcePrecondition(false, IsValidVehicleOrder(vehicle_id, order_position_move)); + EnforcePrecondition(false, IsValidVehicleOrder(vehicle_id, order_position_target)); + + return AIObject::DoCommand(0, vehicle_id, order_position_move | (order_position_target << 16), CMD_MOVE_ORDER); +} + +/* static */ bool AIOrder::CopyOrders(VehicleID vehicle_id, VehicleID main_vehicle_id) +{ + EnforcePrecondition(false, AIVehicle::IsValidVehicle(vehicle_id)); + EnforcePrecondition(false, AIVehicle::IsValidVehicle(main_vehicle_id)); + + return AIObject::DoCommand(0, vehicle_id | (main_vehicle_id << 16), CO_COPY, CMD_CLONE_ORDER); +} + +/* static */ bool AIOrder::ShareOrders(VehicleID vehicle_id, VehicleID main_vehicle_id) +{ + EnforcePrecondition(false, AIVehicle::IsValidVehicle(vehicle_id)); + EnforcePrecondition(false, AIVehicle::IsValidVehicle(main_vehicle_id)); + + return AIObject::DoCommand(0, vehicle_id | (main_vehicle_id << 16), CO_SHARE, CMD_CLONE_ORDER); +} + +/* static */ bool AIOrder::UnshareOrders(VehicleID vehicle_id) +{ + EnforcePrecondition(false, AIVehicle::IsValidVehicle(vehicle_id)); + + return AIObject::DoCommand(0, vehicle_id, CO_UNSHARE, CMD_CLONE_ORDER); +} diff --git a/src/ai/api/ai_order.hpp b/src/ai/api/ai_order.hpp new file mode 100644 index 000000000..fbda26892 --- /dev/null +++ b/src/ai/api/ai_order.hpp @@ -0,0 +1,253 @@ +/* $Id$ */ + +/** @file ai_order.hpp Everything to query and build orders. */ + +#ifndef AI_ORDER_HPP +#define AI_ORDER_HPP + +#include "ai_object.hpp" +#include "ai_error.hpp" + +/** + * Class that handles all order related functions. + */ +class AIOrder : public AIObject { +public: + static const char *GetClassName() { return "AIOrder"; } + + /** + * All order related error messages. + */ + enum ErrorMessages { + /** Base for all order related errors */ + ERR_ORDER_BASE = AIError::ERR_CAT_ORDER << AIError::ERR_CAT_BIT_SIZE, + + /** No more space for orders */ + ERR_ORDER_TOO_MANY, // [STR_8831_NO_MORE_SPACE_FOR_ORDERS] + + /** Destination of new order is to far away from the previous order */ + ERR_ORDER_TOO_FAR_AWAY_FROM_PREVIOUS_DESTINATION, // [STR_0210_TOO_FAR_FROM_PREVIOUS_DESTINATIO] + }; + + /** + * Flags that can be used to modify the behaviour of orders. + */ + enum AIOrderFlags { + /** Just go to the station/depot, stop unload if possible and load if needed. */ + AIOF_NONE = 0, + + /** Do not stop at the stations that are passed when going to the destination. Only for trains and road vehicles. */ + AIOF_NON_STOP_INTERMEDIATE = 1 << 0, + /** Do not stop at the destionation station. Only for trains and road vehicles. */ + AIOF_NON_STOP_DESTINATION = 1 << 1, + + /** Always unload the vehicle; only for stations. Cannot be set when AIOF_TRANSFER or AIOF_NO_UNLOAD is set. */ + AIOF_UNLOAD = 1 << 2, + /** Transfer instead of deliver the goods; only for stations. Cannot be set when AIOF_UNLOAD or AIOF_NO_UNLOAD is set. */ + AIOF_TRANSFER = 1 << 3, + /** Never unload the vehicle; only for stations. Cannot be set when AIOF_UNLOAD, AIOF_TRANSFER or AIOF_NO_LOAD is set. */ + AIOF_NO_UNLOAD = 1 << 4, + + /** Wait till the vehicle is fully loaded; only for stations. Cannot be set when AIOF_NO_LOAD is set. */ + AIOF_FULL_LOAD = 2 << 5, + /** Wait till at least one cargo of the vehicle is fully loaded; only for stations. Cannot be set when AIOF_NO_LOAD is set. */ + AIOF_FULL_LOAD_ANY = 3 << 5, + /** Do not load any cargo; only for stations. Cannot be set when AIOF_NO_UNLOAD, AIOF_FULL_LOAD or AIOF_FULL_LOAD_ANY is set. */ + AIOF_NO_LOAD = 1 << 7, + + /** Service the vehicle when needed, otherwise skip this order; only for depots. */ + AIOF_SERVICE_IF_NEEDED = 1 << 2, + + /** All flags related to non-stop settings. */ + AIOF_NON_STOP_FLAGS = AIOF_NON_STOP_INTERMEDIATE | AIOF_NON_STOP_DESTINATION, + /** All flags related to unloading. */ + AIOF_UNLOAD_FLAGS = AIOF_TRANSFER | AIOF_UNLOAD | AIOF_NO_UNLOAD, + /** All flags related to loading. */ + AIOF_LOAD_FLAGS = AIOF_FULL_LOAD | AIOF_FULL_LOAD_ANY | AIOF_NO_LOAD, + + /** For marking invalid order flags */ + AIOF_INVALID = 0xFFFF, + }; + + /** Different constants related to the OrderPosition */ + enum OrderPosition { + CURRENT_ORDER = 0xFF, //!< Constant that gets resolved to the current order. + INVALID_ORDER = -1, //!< An invalid order. + }; + + /** + * Checks whether the given order id is valid for the given vehicle. + * @param vehicle_id The vehicle to check the order index for. + * @param order_position The order index to check. + * @pre AIVehicle::IsValidVehicle(vehicle_id). + * @return True if and only if the order_position is valid for the given vehicle. + */ + static bool IsValidVehicleOrder(VehicleID vehicle_id, OrderPosition order_position); + + /** + * Resolves the given order index to the correct index for the given vehicle. + * If the order index was CURRENT_ORDER it will be resolved to the index of + * the current order (as shown in the order list). If the order with the + * given index does not exist it will return INVALID_ORDER. + * @param vehicle_id The vehicle to check the order index for. + * @param order_position The order index to resolve. + * @pre AIVehicle::IsValidVehicle(vehicle_id). + * @return The resolved order index. + */ + static OrderPosition ResolveOrderPosition(VehicleID vehicle_id, OrderPosition order_position); + + /** + * Checks whether the given order flags are valid for the given destination. + * @param destination The destination of the order. + * @param order_flags The flags given to the order. + * @return True if and only if the order_flags are valid for the given location. + */ + static bool AreOrderFlagsValid(TileIndex destination, AIOrderFlags order_flags); + + /** + * Returns the number of orders for the given vehicle. + * @param vehicle_id The vehicle to get the order count of. + * @pre AIVehicle::IsValidVehicle(vehicle_id). + * @return The number of orders for the given vehicle or a negative + * value when the vehicle does not exist. + */ + static int32 GetOrderCount(VehicleID vehicle_id); + + /** + * Gets the destination of the given order for the given vehicle. + * @param vehicle_id The vehicle to get the destination for. + * @param order_position The order to get the destination for. + * @pre IsValidVehicleOrder(vehicle_id, order_position). + * @note Giving CURRENT_ORDER as order_position will give the order that is + * currently being executed by the vehicle. This is not necessarily the + * current order as given by ResolveOrderPosition (the current index in the + * order list) as manual or autoservicing depot orders do not show up + * in the orderlist, but they can be the current order of a vehicle. + * @return The destination tile of the order. + */ + static TileIndex GetOrderDestination(VehicleID vehicle_id, OrderPosition order_position); + + /** + * Gets the AIOrderFlags of the given order for the given vehicle. + * @param vehicle_id The vehicle to get the destination for. + * @param order_position The order to get the destination for. + * @pre IsValidVehicleOrder(vehicle_id, order_position). + * @note Giving CURRENT_ORDER as order_position will give the order that is + * currently being executed by the vehicle. This is not necessarily the + * current order as given by ResolveOrderPosition (the current index in the + * order list) as manual or autoservicing depot orders do not show up + * in the orderlist, but they can be the current order of a vehicle. + * @return The AIOrderFlags of the order. + */ + static AIOrderFlags GetOrderFlags(VehicleID vehicle_id, OrderPosition order_position); + + /** + * Appends an order to the end of the vehicle's order list. + * @param vehicle_id The vehicle to append the order to. + * @param destination The destination of the order. + * @param order_flags The flags given to the order. + * @pre AIVehicle::IsValidVehicle(vehicle_id). + * @pre AreOrderFlagsValid(destination, order_flags). + * @exception AIError::ERR_OWNED_BY_ANOTHER_COMPANY + * @exception AIOrder::ERR_ORDER_NO_MORE_SPACE + * @exception AIOrder::ERR_ORDER_TOO_FAR_AWAY_FROM_PREVIOUS_DESTINATION + * @return True if and only if the order was appended. + */ + static bool AppendOrder(VehicleID vehicle_id, TileIndex destination, AIOrderFlags order_flags); + + /** + * Inserts an order before the given order_position into the vehicle's order list. + * @param vehicle_id The vehicle to add the order to. + * @param order_position The order to place the new order before. + * @param destination The destination of the order. + * @param order_flags The flags given to the order. + * @pre IsValidVehicleOrder(vehicle_id, order_position). + * @pre AreOrderFlagsValid(destination, order_flags). + * @exception AIError::ERR_OWNED_BY_ANOTHER_COMPANY + * @exception AIOrder::ERR_ORDER_NO_MORE_SPACE + * @exception AIOrder::ERR_ORDER_TOO_FAR_AWAY_FROM_PREVIOUS_DESTINATION + * @return True if and only if the order was inserted. + */ + static bool InsertOrder(VehicleID vehicle_id, OrderPosition order_position, TileIndex destination, AIOrderFlags order_flags); + + /** + * Removes an order from the vehicle's order list. + * @param vehicle_id The vehicle to remove the order from. + * @param order_position The order to remove from the order list. + * @pre AIVehicle::IsValidVehicleOrder(vehicle_id, order_position). + * @exception AIError::ERR_OWNED_BY_ANOTHER_COMPANY + * @return True if and only if the order was removed. + */ + static bool RemoveOrder(VehicleID vehicle_id, OrderPosition order_position); + +#ifndef DOXYGEN_SKIP + /** + * Internal function to help ChangeOrder. + */ + static bool _ChangeOrder(); +#endif + + /** + * Changes the order flags of the given order. + * @param vehicle_id The vehicle to change the order of. + * @param order_position The order to change. + * @param order_flags The new flags given to the order. + * @pre IsValidVehicleOrder(vehicle_id, order_position). + * @pre AreOrderFlagsValid(GetOrderDestination(vehicle_id, order_position), order_flags). + * @exception AIError::ERR_OWNED_BY_ANOTHER_COMPANY + * @return True if and only if the order was changed. + */ + static bool ChangeOrder(VehicleID vehicle_id, OrderPosition order_position, AIOrderFlags order_flags); + + /** + * Move an order inside the orderlist + * @param vehicle_id The vehicle to move the orders. + * @param order_position_move The order to move. + * @param order_position_target The target order + * @pre IsValidVehicleOrder(vehicle_id, order_position_move). + * @pre IsValidVehicleOrder(vehicle_id, order_position_target). + * @exception AIError::ERR_OWNED_BY_ANOTHER_COMPANY + * @return True if and only if the order was moved. + * @note If the order is moved to a lower place (e.g. from 7 to 2) + * the target order is moved upwards (e.g. 3). If the order is moved + * to a higher place (e.g. from 7 to 9) the target will be moved + * downwards (e.g. 8). + */ + static bool MoveOrder(VehicleID vehicle_id, OrderPosition order_position_move, OrderPosition order_position_target); + + /** + * Copies the orders from another vehicle. The orders of the main vehicle + * are going to be the orders of the changed vehicle. + * @param vehicle_id The vehicle to copy the orders to. + * @param main_vehicle_id The vehicle to copy the orders from. + * @pre AIVehicle::IsValidVehicle(vehicle_id). + * @pre AIVehicle::IsValidVehicle(main_vehicle_id). + * @exception AIError::ERR_OWNED_BY_ANOTHER_COMPANY + * @exception AIOrder::ERR_ORDER_NO_MORE_SPACE + * @return True if and only if the copying succeeded. + */ + static bool CopyOrders(VehicleID vehicle_id, VehicleID main_vehicle_id); + + /** + * Shares the orders between two vehicles. The orders of the main + * vehicle are going to be the orders of the changed vehicle. + * @param vehicle_id The vehicle to add to the shared order list. + * @param main_vehicle_id The vehicle to share the orders with. + * @pre AIVehicle::IsValidVehicle(vehicle_id). + * @pre AIVehicle::IsValidVehicle(main_vehicle_id). + * @exception AIError::ERR_OWNED_BY_ANOTHER_COMPANY + * @return True if and only if the sharing succeeded. + */ + static bool ShareOrders(VehicleID vehicle_id, VehicleID main_vehicle_id); + + /** + * Removes the given vehicle from a shared orders list. + * @param vehicle_id The vehicle to remove from the shared order list. + * @pre AIVehicle::IsValidVehicle(vehicle_id). + * @return True if and only if the unsharing succeeded. + */ + static bool UnshareOrders(VehicleID vehicle_id); +}; +DECLARE_ENUM_AS_BIT_SET(AIOrder::AIOrderFlags); + +#endif /* AI_ORDER_HPP */ diff --git a/src/ai/api/ai_order.hpp.sq b/src/ai/api/ai_order.hpp.sq new file mode 100644 index 000000000..4128ae029 --- /dev/null +++ b/src/ai/api/ai_order.hpp.sq @@ -0,0 +1,71 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_order.hpp" + +namespace SQConvert { + /* Allow enums to be used as Squirrel parameters */ + template <> AIOrder::ErrorMessages GetParam(ForceType<AIOrder::ErrorMessages>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AIOrder::ErrorMessages)tmp; } + template <> int Return<AIOrder::ErrorMessages>(HSQUIRRELVM vm, AIOrder::ErrorMessages res) { sq_pushinteger(vm, (int32)res); return 1; } + template <> AIOrder::AIOrderFlags GetParam(ForceType<AIOrder::AIOrderFlags>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AIOrder::AIOrderFlags)tmp; } + template <> int Return<AIOrder::AIOrderFlags>(HSQUIRRELVM vm, AIOrder::AIOrderFlags res) { sq_pushinteger(vm, (int32)res); return 1; } + template <> AIOrder::OrderPosition GetParam(ForceType<AIOrder::OrderPosition>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AIOrder::OrderPosition)tmp; } + template <> int Return<AIOrder::OrderPosition>(HSQUIRRELVM vm, AIOrder::OrderPosition res) { sq_pushinteger(vm, (int32)res); return 1; } + + /* Allow AIOrder to be used as Squirrel parameter */ + template <> AIOrder *GetParam(ForceType<AIOrder *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIOrder *)instance; } + template <> AIOrder &GetParam(ForceType<AIOrder &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIOrder *)instance; } + template <> const AIOrder *GetParam(ForceType<const AIOrder *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIOrder *)instance; } + template <> const AIOrder &GetParam(ForceType<const AIOrder &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIOrder *)instance; } + template <> int Return<AIOrder *>(HSQUIRRELVM vm, AIOrder *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIOrder", res, NULL, DefSQDestructorCallback<AIOrder>); return 1; } +}; // namespace SQConvert + +void SQAIOrder_Register(Squirrel *engine) { + DefSQClass <AIOrder> SQAIOrder("AIOrder"); + SQAIOrder.PreRegister(engine); + SQAIOrder.AddConstructor<void (AIOrder::*)(), 1>(engine, "x"); + + SQAIOrder.DefSQConst(engine, AIOrder::ERR_ORDER_BASE, "ERR_ORDER_BASE"); + SQAIOrder.DefSQConst(engine, AIOrder::ERR_ORDER_TOO_MANY, "ERR_ORDER_TOO_MANY"); + SQAIOrder.DefSQConst(engine, AIOrder::ERR_ORDER_TOO_FAR_AWAY_FROM_PREVIOUS_DESTINATION, "ERR_ORDER_TOO_FAR_AWAY_FROM_PREVIOUS_DESTINATION"); + SQAIOrder.DefSQConst(engine, AIOrder::AIOF_NONE, "AIOF_NONE"); + SQAIOrder.DefSQConst(engine, AIOrder::AIOF_NON_STOP_INTERMEDIATE, "AIOF_NON_STOP_INTERMEDIATE"); + SQAIOrder.DefSQConst(engine, AIOrder::AIOF_NON_STOP_DESTINATION, "AIOF_NON_STOP_DESTINATION"); + SQAIOrder.DefSQConst(engine, AIOrder::AIOF_UNLOAD, "AIOF_UNLOAD"); + SQAIOrder.DefSQConst(engine, AIOrder::AIOF_TRANSFER, "AIOF_TRANSFER"); + SQAIOrder.DefSQConst(engine, AIOrder::AIOF_NO_UNLOAD, "AIOF_NO_UNLOAD"); + SQAIOrder.DefSQConst(engine, AIOrder::AIOF_FULL_LOAD, "AIOF_FULL_LOAD"); + SQAIOrder.DefSQConst(engine, AIOrder::AIOF_FULL_LOAD_ANY, "AIOF_FULL_LOAD_ANY"); + SQAIOrder.DefSQConst(engine, AIOrder::AIOF_NO_LOAD, "AIOF_NO_LOAD"); + SQAIOrder.DefSQConst(engine, AIOrder::AIOF_SERVICE_IF_NEEDED, "AIOF_SERVICE_IF_NEEDED"); + SQAIOrder.DefSQConst(engine, AIOrder::AIOF_NON_STOP_FLAGS, "AIOF_NON_STOP_FLAGS"); + SQAIOrder.DefSQConst(engine, AIOrder::AIOF_UNLOAD_FLAGS, "AIOF_UNLOAD_FLAGS"); + SQAIOrder.DefSQConst(engine, AIOrder::AIOF_LOAD_FLAGS, "AIOF_LOAD_FLAGS"); + SQAIOrder.DefSQConst(engine, AIOrder::AIOF_INVALID, "AIOF_INVALID"); + SQAIOrder.DefSQConst(engine, AIOrder::CURRENT_ORDER, "CURRENT_ORDER"); + SQAIOrder.DefSQConst(engine, AIOrder::INVALID_ORDER, "INVALID_ORDER"); + + AIError::RegisterErrorMap(STR_8831_NO_MORE_SPACE_FOR_ORDERS, AIOrder::ERR_ORDER_TOO_MANY); + AIError::RegisterErrorMap(STR_0210_TOO_FAR_FROM_PREVIOUS_DESTINATIO, AIOrder::ERR_ORDER_TOO_FAR_AWAY_FROM_PREVIOUS_DESTINATION); + + AIError::RegisterErrorMapString(AIOrder::ERR_ORDER_TOO_MANY, "ERR_ORDER_TOO_MANY"); + AIError::RegisterErrorMapString(AIOrder::ERR_ORDER_TOO_FAR_AWAY_FROM_PREVIOUS_DESTINATION, "ERR_ORDER_TOO_FAR_AWAY_FROM_PREVIOUS_DESTINATION"); + + SQAIOrder.DefSQStaticMethod(engine, &AIOrder::GetClassName, "GetClassName", 1, "x"); + SQAIOrder.DefSQStaticMethod(engine, &AIOrder::IsValidVehicleOrder, "IsValidVehicleOrder", 3, "xii"); + SQAIOrder.DefSQStaticMethod(engine, &AIOrder::ResolveOrderPosition, "ResolveOrderPosition", 3, "xii"); + SQAIOrder.DefSQStaticMethod(engine, &AIOrder::AreOrderFlagsValid, "AreOrderFlagsValid", 3, "xii"); + SQAIOrder.DefSQStaticMethod(engine, &AIOrder::GetOrderCount, "GetOrderCount", 2, "xi"); + SQAIOrder.DefSQStaticMethod(engine, &AIOrder::GetOrderDestination, "GetOrderDestination", 3, "xii"); + SQAIOrder.DefSQStaticMethod(engine, &AIOrder::GetOrderFlags, "GetOrderFlags", 3, "xii"); + SQAIOrder.DefSQStaticMethod(engine, &AIOrder::AppendOrder, "AppendOrder", 4, "xiii"); + SQAIOrder.DefSQStaticMethod(engine, &AIOrder::InsertOrder, "InsertOrder", 5, "xiiii"); + SQAIOrder.DefSQStaticMethod(engine, &AIOrder::RemoveOrder, "RemoveOrder", 3, "xii"); + SQAIOrder.DefSQStaticMethod(engine, &AIOrder::ChangeOrder, "ChangeOrder", 4, "xiii"); + SQAIOrder.DefSQStaticMethod(engine, &AIOrder::MoveOrder, "MoveOrder", 4, "xiii"); + SQAIOrder.DefSQStaticMethod(engine, &AIOrder::CopyOrders, "CopyOrders", 3, "xii"); + SQAIOrder.DefSQStaticMethod(engine, &AIOrder::ShareOrders, "ShareOrders", 3, "xii"); + SQAIOrder.DefSQStaticMethod(engine, &AIOrder::UnshareOrders, "UnshareOrders", 2, "xi"); + + SQAIOrder.PostRegister(engine); +} diff --git a/src/ai/api/ai_rail.cpp b/src/ai/api/ai_rail.cpp new file mode 100644 index 000000000..f35f98de3 --- /dev/null +++ b/src/ai/api/ai_rail.cpp @@ -0,0 +1,442 @@ +/* $Id$ */ + +/** @file ai_rail.cpp Implementation of AIRail. */ + +#include "ai_rail.hpp" +#include "ai_object.hpp" +#include "ai_map.hpp" +#include "../../openttd.h" +#include "../../debug.h" +#include "../../rail_map.h" +#include "../../road_map.h" +#include "../../command_type.h" +#include "../../station_map.h" +#include "../../company_func.h" +#include "../../core/math_func.hpp" +#include "../../waypoint.h" +#include "../../newgrf.h" +#include "../../newgrf_generic.h" +#include "../../newgrf_station.h" +#include "../../newgrf_callbacks.h" + +/* static */ bool AIRail::IsRailTile(TileIndex tile) +{ + if (!::IsValidTile(tile)) return false; + + return (::IsTileType(tile, MP_RAILWAY) && !::IsRailDepot(tile)) || + (::IsRailwayStationTile(tile) && !::IsStationTileBlocked(tile)) || ::IsLevelCrossingTile(tile); +} + +/* static */ bool AIRail::IsLevelCrossingTile(TileIndex tile) +{ + if (!::IsValidTile(tile)) return false; + + return ::IsLevelCrossingTile(tile); +} + +/* static */ bool AIRail::IsRailDepotTile(TileIndex tile) +{ + if (!::IsValidTile(tile)) return false; + + return ::IsRailDepotTile(tile); +} + +/* static */ bool AIRail::IsRailStationTile(TileIndex tile) +{ + if (!::IsValidTile(tile)) return false; + + return ::IsRailwayStationTile(tile); +} + +/* static */ bool AIRail::IsRailWaypointTile(TileIndex tile) +{ + if (!::IsValidTile(tile)) return false; + + return ::IsTileType(tile, MP_RAILWAY) && ::IsRailWaypointTile(tile); +} + +/* static */ bool AIRail::IsRailTypeAvailable(RailType rail_type) +{ + if ((::RailType)rail_type < RAILTYPE_BEGIN || (::RailType)rail_type >= RAILTYPE_END) return false; + + return ::HasRailtypeAvail(_current_company, (::RailType)rail_type); +} + +/* static */ AIRail::RailType AIRail::GetCurrentRailType() +{ + return (RailType)AIObject::GetRailType(); +} + +/* static */ void AIRail::SetCurrentRailType(RailType rail_type) +{ + if (!IsRailTypeAvailable(rail_type)) return; + + AIObject::SetRailType((::RailType)rail_type); +} + +/* static */ bool AIRail::TrainCanRunOnRail(AIRail::RailType engine_rail_type, AIRail::RailType track_rail_type) +{ + if (!AIRail::IsRailTypeAvailable(engine_rail_type)) return false; + if (!AIRail::IsRailTypeAvailable(track_rail_type)) return false; + + return ::IsCompatibleRail((::RailType)engine_rail_type, (::RailType)track_rail_type); +} + +/* static */ bool AIRail::TrainHasPowerOnRail(AIRail::RailType engine_rail_type, AIRail::RailType track_rail_type) +{\ + if (!AIRail::IsRailTypeAvailable(engine_rail_type)) return false; + if (!AIRail::IsRailTypeAvailable(track_rail_type)) return false; + + return ::HasPowerOnRail((::RailType)engine_rail_type, (::RailType)track_rail_type); +} + +/* static */ AIRail::RailType AIRail::GetRailType(TileIndex tile) +{ + if (!AITile::HasTransportType(tile, AITile::TRANSPORT_RAIL)) return RAILTYPE_INVALID; + + return (RailType)::GetRailType(tile); +} + +/* static */ bool AIRail::ConvertRailType(TileIndex start_tile, TileIndex end_tile, AIRail::RailType convert_to) +{ + EnforcePrecondition(false, ::IsValidTile(start_tile)); + EnforcePrecondition(false, ::IsValidTile(end_tile)); + EnforcePrecondition(false, IsRailTypeAvailable(convert_to)); + + return AIObject::DoCommand(start_tile, end_tile, convert_to, CMD_CONVERT_RAIL); +} + +/* static */ TileIndex AIRail::GetRailDepotFrontTile(TileIndex depot) +{ + if (!IsRailDepotTile(depot)) return INVALID_TILE; + + return depot + ::TileOffsByDiagDir(::GetRailDepotDirection(depot)); +} + +/* static */ AIRail::RailTrack AIRail::GetRailStationDirection(TileIndex tile) +{ + if (!IsRailStationTile(tile)) return RAILTRACK_INVALID; + + return (RailTrack)::GetRailStationTrack(tile); +} + +/* static */ bool AIRail::BuildRailDepot(TileIndex tile, TileIndex front) +{ + EnforcePrecondition(false, tile != front); + EnforcePrecondition(false, ::IsValidTile(tile)); + EnforcePrecondition(false, ::IsValidTile(front)); + EnforcePrecondition(false, ::TileX(tile) == ::TileX(front) || ::TileY(tile) == ::TileY(front)); + EnforcePrecondition(false, IsRailTypeAvailable(GetCurrentRailType())); + + uint entrance_dir = (::TileX(tile) == ::TileX(front)) ? (::TileY(tile) < ::TileY(front) ? 1 : 3) : (::TileX(tile) < ::TileX(front) ? 2 : 0); + + return AIObject::DoCommand(tile, AIObject::GetRailType(), entrance_dir, CMD_BUILD_TRAIN_DEPOT); +} + +/* static */ bool AIRail::BuildRailStation(TileIndex tile, RailTrack direction, uint num_platforms, uint platform_length, bool join_adjacent) +{ + EnforcePrecondition(false, ::IsValidTile(tile)); + EnforcePrecondition(false, direction == RAILTRACK_NW_SE || direction == RAILTRACK_NE_SW); + EnforcePrecondition(false, num_platforms > 0 && num_platforms <= 0xFF); + EnforcePrecondition(false, platform_length > 0 && platform_length <= 0xFF); + EnforcePrecondition(false, IsRailTypeAvailable(GetCurrentRailType())); + + uint32 p1 = GetCurrentRailType() | (platform_length << 16) | (num_platforms << 8); + if (direction == RAILTRACK_NW_SE) p1 |= (1 << 4); + if (!join_adjacent) p1 |= (1 << 24); + return AIObject::DoCommand(tile, p1, INVALID_STATION << 16, CMD_BUILD_RAILROAD_STATION); +} + +/* static */ bool AIRail::BuildNewGRFRailStation(TileIndex tile, RailTrack direction, uint num_platforms, uint platform_length, bool join_adjacent, CargoID cargo_id, IndustryType source_industry, IndustryType goal_industry, int distance, bool source_station) +{ + EnforcePrecondition(false, ::IsValidTile(tile)); + EnforcePrecondition(false, direction == RAILTRACK_NW_SE || direction == RAILTRACK_NE_SW); + EnforcePrecondition(false, num_platforms > 0 && num_platforms <= 0xFF); + EnforcePrecondition(false, platform_length > 0 && platform_length <= 0xFF); + EnforcePrecondition(false, IsRailTypeAvailable(GetCurrentRailType())); + + uint32 p1 = GetCurrentRailType() | (platform_length << 16) | (num_platforms << 8); + if (direction == RAILTRACK_NW_SE) p1 |= 1 << 4; + if (!join_adjacent) p1 |= (1 << 24); + + const GRFFile *file; + uint16 res = GetAiPurchaseCallbackResult(GSF_STATION, cargo_id, 0, source_industry, goal_industry, min(255, distance / 2), AICE_STATION_GET_STATION_ID, source_station ? 0 : 1, min(15, num_platforms) << 4 | min(15, platform_length), &file); + uint32 p2 = INVALID_STATION << 16; + if (res != CALLBACK_FAILED) { + extern StationClass _station_classes[STAT_CLASS_MAX]; + const StationSpec *spec = GetCustomStationSpecByGrf(file->grfid, res); + int index = -1; + for (uint j = 0; j < _station_classes[spec->sclass].stations; j++) { + if (spec == _station_classes[spec->sclass].spec[j]) { + index = j; + break; + } + } + if (index == -1) { + DEBUG(grf, 1, "%s returned an invalid station ID for 'AI construction/purchase selection (18)' callback", file->filename); + } else { + p2 |= spec->sclass | index << 8; + } + + } + return AIObject::DoCommand(tile, p1, p2, CMD_BUILD_RAILROAD_STATION); +} + +/* static */ bool AIRail::RemoveRailStationTileRect(TileIndex tile, TileIndex tile2) +{ + EnforcePrecondition(false, ::IsValidTile(tile)); + EnforcePrecondition(false, ::IsValidTile(tile2)); + + return AIObject::DoCommand(tile, tile2, 0, CMD_REMOVE_FROM_RAILROAD_STATION); +} + +/* static */ uint AIRail::GetRailTracks(TileIndex tile) +{ + if (!IsRailTile(tile)) return RAILTRACK_INVALID; + + if (IsRailWaypointTile(tile)) return ::GetRailWaypointBits(tile); + if (IsRailStationTile(tile)) return ::TrackToTrackBits(::GetRailStationTrack(tile)); + if (IsLevelCrossingTile(tile)) return ::GetCrossingRailBits(tile); + return ::GetTrackBits(tile); +} + +/* static */ bool AIRail::BuildRailTrack(TileIndex tile, RailTrack rail_track) +{ + EnforcePrecondition(false, ::IsValidTile(tile)); + EnforcePrecondition(false, rail_track != 0); + EnforcePrecondition(false, (rail_track & ~::TRACK_BIT_ALL) == 0); + EnforcePrecondition(false, KillFirstBit((uint)rail_track) == 0); + EnforcePrecondition(false, IsRailTypeAvailable(GetCurrentRailType())); + + return AIObject::DoCommand(tile, tile, GetCurrentRailType() | (FindFirstTrack((::TrackBits)rail_track) << 4), CMD_BUILD_RAILROAD_TRACK); +} + +/* static */ bool AIRail::RemoveRailTrack(TileIndex tile, RailTrack rail_track) +{ + EnforcePrecondition(false, ::IsValidTile(tile)); + EnforcePrecondition(false, ::IsTileType(tile, MP_RAILWAY) && ::IsPlainRailTile(tile)); + EnforcePrecondition(false, GetRailTracks(tile) & rail_track); + EnforcePrecondition(false, KillFirstBit((uint)rail_track) == 0); + + return AIObject::DoCommand(tile, tile, GetCurrentRailType() | (FindFirstTrack((::TrackBits)rail_track) << 4), CMD_REMOVE_RAILROAD_TRACK); +} + +/* static */ bool AIRail::AreTilesConnected(TileIndex from, TileIndex tile, TileIndex to) +{ + if (!IsRailTile(tile)) return false; + if (from == to || AIMap::DistanceManhattan(from, tile) != 1 || AIMap::DistanceManhattan(tile, to) != 1) return false; + + if (to < from) ::Swap(from, to); + + if (tile - from == 1) { + if (to - tile == 1) return (GetRailTracks(tile) & RAILTRACK_NE_SW) != 0; + if (to - tile == ::MapSizeX()) return (GetRailTracks(tile) & RAILTRACK_NE_SE) != 0; + } else if (tile - from == ::MapSizeX()) { + if (tile - to == 1) return (GetRailTracks(tile) & RAILTRACK_NW_NE) != 0; + if (to - tile == 1) return (GetRailTracks(tile) & RAILTRACK_NW_SW) != 0; + if (to - tile == ::MapSizeX()) return (GetRailTracks(tile) & RAILTRACK_NW_SE) != 0; + } else { + return (GetRailTracks(tile) & RAILTRACK_SW_SE) != 0; + } + + NOT_REACHED(); +} + +/** + * Prepare the second parameter for CmdBuildRailroadTrack and CmdRemoveRailroadTrack. The direction + * depends on all three tiles. Sometimes the third tile needs to be adjusted. + */ +static uint32 SimulateDrag(TileIndex from, TileIndex tile, TileIndex *to) +{ + int diag_offset = abs(abs((int)::TileX(*to) - (int)::TileX(tile)) - abs((int)::TileY(*to) - (int)::TileY(tile))); + uint32 p2 = AIRail::GetCurrentRailType(); + if (::TileY(from) == ::TileY(*to)) { + p2 |= (TRACK_X << 4); + *to -= Clamp((int)::TileX(*to) - (int)::TileX(tile), -1, 1); + } else if (::TileX(from) == ::TileX(*to)) { + p2 |= (TRACK_Y << 4); + *to -= ::MapSizeX() * Clamp((int)::TileY(*to) - (int)::TileY(tile), -1, 1); + } else if (::TileY(from) < ::TileY(tile)) { + if (::TileX(*to) < ::TileX(tile)) { + p2 |= (TRACK_UPPER << 4); + } else { + p2 |= (TRACK_LEFT << 4); + } + if (diag_offset) { + *to -= Clamp((int)::TileX(*to) - (int)::TileX(tile), -1, 1); + } else { + *to -= ::MapSizeX() * Clamp((int)::TileY(*to) - (int)::TileY(tile), -1, 1); + } + } else if (::TileY(from) > ::TileY(tile)) { + if (::TileX(*to) < ::TileX(tile)) { + p2 |= (TRACK_RIGHT << 4); + } else { + p2 |= (TRACK_LOWER << 4); + } + if (diag_offset) { + *to -= Clamp((int)::TileX(*to) - (int)::TileX(tile), -1, 1); + } else { + *to -= ::MapSizeX() * Clamp((int)::TileY(*to) - (int)::TileY(tile), -1, 1); + } + } else if (::TileX(from) < ::TileX(tile)) { + if (::TileY(*to) < ::TileY(tile)) { + p2 |= (TRACK_UPPER << 4); + } else { + p2 |= (TRACK_RIGHT << 4); + } + if (!diag_offset) { + *to -= Clamp((int)::TileX(*to) - (int)::TileX(tile), -1, 1); + } else { + *to -= ::MapSizeX() * Clamp((int)::TileY(*to) - (int)::TileY(tile), -1, 1); + } + } else if (::TileX(from) > ::TileX(tile)) { + if (::TileY(*to) < ::TileY(tile)) { + p2 |= (TRACK_LEFT << 4); + } else { + p2 |= (TRACK_LOWER << 4); + } + if (!diag_offset) { + *to -= Clamp((int)::TileX(*to) - (int)::TileX(tile), -1, 1); + } else { + *to -= ::MapSizeX() * Clamp((int)::TileY(*to) - (int)::TileY(tile), -1, 1); + } + } + return p2; +} + +/* static */ bool AIRail::BuildRail(TileIndex from, TileIndex tile, TileIndex to) +{ + EnforcePrecondition(false, ::IsValidTile(from)); + EnforcePrecondition(false, ::IsValidTile(tile)); + EnforcePrecondition(false, ::IsValidTile(to)); + EnforcePrecondition(false, ::DistanceManhattan(from, tile) == 1); + EnforcePrecondition(false, ::DistanceManhattan(tile,to) >= 1); + EnforcePrecondition(false, IsRailTypeAvailable(GetCurrentRailType())); + int diag_offset = abs(abs((int)::TileX(to) - (int)::TileX(tile)) - abs((int)::TileY(to) - (int)::TileY(tile))); + EnforcePrecondition(false, diag_offset <= 1 || + (::TileX(from) == ::TileX(tile) && ::TileX(tile) == ::TileX(to)) || + (::TileY(from) == ::TileY(tile) && ::TileY(tile) == ::TileY(to))); + + uint32 p2 = SimulateDrag(from, tile, &to); + return AIObject::DoCommand(tile, to, p2, CMD_BUILD_RAILROAD_TRACK); +} + +/* static */ bool AIRail::RemoveRail(TileIndex from, TileIndex tile, TileIndex to) +{ + EnforcePrecondition(false, ::IsValidTile(from)); + EnforcePrecondition(false, ::IsValidTile(tile)); + EnforcePrecondition(false, ::IsValidTile(to)); + EnforcePrecondition(false, ::DistanceManhattan(from, tile) == 1); + EnforcePrecondition(false, ::DistanceManhattan(tile,to) >= 1); + int diag_offset = abs(abs((int)::TileX(to) - (int)::TileX(tile)) - abs((int)::TileY(to) - (int)::TileY(tile))); + EnforcePrecondition(false, diag_offset <= 1 || + (::TileX(from) == ::TileX(tile) && ::TileX(tile) == ::TileX(to)) || + (::TileY(from) == ::TileY(tile) && ::TileY(tile) == ::TileY(to))); + + if (!IsRailTypeAvailable(GetCurrentRailType())) SetCurrentRailType(GetRailType(tile)); + uint32 p2 = SimulateDrag(from, tile, &to); + return AIObject::DoCommand(tile, to, p2, CMD_REMOVE_RAILROAD_TRACK); +} + +/** + * Contains information about the trackdir that belongs to a track when entering + * from a specific direction. + */ +struct AIRailSignalData { + Track track; //!< The track that will be taken to travel. + Trackdir trackdir; //!< The Trackdir belonging to that track. + uint signal_cycles; //!< How many times the signal should be cycled in order to build it in the correct direction. +}; + +static const int NUM_TRACK_DIRECTIONS = 3; //!< The number of directions you can go when entering a tile. + +/** + * List information about the trackdir and number of needed cycles for building signals when + * entering a track from a specific direction. The first index is the difference between the + * TileIndex of the previous and current tile, where (-)MapSizeX is replaced with -2 / 2 and + * 2 it added. + */ +static const AIRailSignalData _possible_trackdirs[5][NUM_TRACK_DIRECTIONS] = { + {{TRACK_UPPER, TRACKDIR_UPPER_E, 0}, {TRACK_Y, TRACKDIR_Y_SE, 0}, {TRACK_LEFT, TRACKDIR_LEFT_S, 1}}, + {{TRACK_RIGHT, TRACKDIR_RIGHT_S, 1}, {TRACK_X, TRACKDIR_X_SW, 1}, {TRACK_UPPER, TRACKDIR_UPPER_W, 1}}, + {{INVALID_TRACK, INVALID_TRACKDIR, 0}, {INVALID_TRACK, INVALID_TRACKDIR, 0}, {INVALID_TRACK, INVALID_TRACKDIR, 0}}, + {{TRACK_LOWER, TRACKDIR_LOWER_E, 0}, {TRACK_X, TRACKDIR_X_NE, 0}, {TRACK_LEFT, TRACKDIR_LEFT_N, 0}}, + {{TRACK_RIGHT, TRACKDIR_RIGHT_N, 0}, {TRACK_Y, TRACKDIR_Y_NW, 1}, {TRACK_LOWER, TRACKDIR_LOWER_W, 1}} +}; + +/* static */ AIRail::SignalType AIRail::GetSignalType(TileIndex tile, TileIndex front) +{ + if (AIMap::DistanceManhattan(tile, front) != 1) return SIGNALTYPE_NONE; + if (!::IsTileType(tile, MP_RAILWAY) || !::HasSignals(tile)) return SIGNALTYPE_NONE; + + int data_index = 2 + (::TileX(front) - ::TileX(tile)) + 2 * (::TileY(front) - ::TileY(tile)); + + for (int i = 0; i < NUM_TRACK_DIRECTIONS; i++) { + const Track &track = _possible_trackdirs[data_index][i].track; + if (!(::TrackToTrackBits(track) & GetRailTracks(tile))) continue; + if (!HasSignalOnTrack(tile, track)) continue; + if (!HasSignalOnTrackdir(tile, _possible_trackdirs[data_index][i].trackdir)) continue; + SignalType st = (SignalType)::GetSignalType(tile, track); + if (HasSignalOnTrackdir(tile, ::ReverseTrackdir(_possible_trackdirs[data_index][i].trackdir))) st = (SignalType)(st | SIGNALTYPE_TWOWAY); + return st; + } + + return SIGNALTYPE_NONE; +} + +/** + * Check if signal_type is a valid SignalType. + */ +static bool IsValidSignalType(int signal_type) +{ + if (signal_type < AIRail::SIGNALTYPE_NORMAL || signal_type > AIRail::SIGNALTYPE_COMBO_TWOWAY) return false; + if (signal_type > AIRail::SIGNALTYPE_PBS_ONEWAY && signal_type < AIRail::SIGNALTYPE_NORMAL_TWOWAY) return false; + return true; +} + +/* static */ bool AIRail::BuildSignal(TileIndex tile, TileIndex front, SignalType signal) +{ + EnforcePrecondition(false, AIMap::DistanceManhattan(tile, front) == 1) + EnforcePrecondition(false, ::IsTileType(tile, MP_RAILWAY) && ::IsPlainRailTile(tile)); + EnforcePrecondition(false, ::IsValidSignalType(signal)); + + Track track = INVALID_TRACK; + uint signal_cycles; + + int data_index = 2 + (::TileX(front) - ::TileX(tile)) + 2 * (::TileY(front) - ::TileY(tile)); + for (int i = 0; i < NUM_TRACK_DIRECTIONS; i++) { + const Track &t = _possible_trackdirs[data_index][i].track; + if (!(::TrackToTrackBits(t) & GetRailTracks(tile))) continue; + track = t; + signal_cycles = _possible_trackdirs[data_index][i].signal_cycles; + break; + } + EnforcePrecondition(false, track != INVALID_TRACK); + + uint p1 = track; + if (signal < SIGNALTYPE_TWOWAY) { + if (signal != SIGNALTYPE_PBS && signal != SIGNALTYPE_PBS_ONEWAY) signal_cycles++; + p1 |= (signal_cycles << 15); + } + p1 |= ((signal >= SIGNALTYPE_TWOWAY ? signal ^ SIGNALTYPE_TWOWAY : signal) << 5); + + return AIObject::DoCommand(tile, p1, 0, CMD_BUILD_SIGNALS); +} + +/* static */ bool AIRail::RemoveSignal(TileIndex tile, TileIndex front) +{ + EnforcePrecondition(false, AIMap::DistanceManhattan(tile, front) == 1) + EnforcePrecondition(false, GetSignalType(tile, front) != SIGNALTYPE_NONE); + + Track track = INVALID_TRACK; + int data_index = 2 + (::TileX(front) - ::TileX(tile)) + 2 * (::TileY(front) - ::TileY(tile)); + for (int i = 0; i < NUM_TRACK_DIRECTIONS; i++) { + const Track &t = _possible_trackdirs[data_index][i].track; + if (!(::TrackToTrackBits(t) & GetRailTracks(tile))) continue; + track = t; + break; + } + EnforcePrecondition(false, track != INVALID_TRACK); + + return AIObject::DoCommand(tile, track, 0, CMD_REMOVE_SIGNALS); +} diff --git a/src/ai/api/ai_rail.hpp b/src/ai/api/ai_rail.hpp new file mode 100644 index 000000000..5acb995ff --- /dev/null +++ b/src/ai/api/ai_rail.hpp @@ -0,0 +1,390 @@ +/* $Id$ */ + +/** @file ai_rail.hpp Everything to query and build rails. */ + +#ifndef AI_RAIL_HPP +#define AI_RAIL_HPP + +#include "ai_object.hpp" +#include "ai_error.hpp" +#include "ai_tile.hpp" + +/** + * Class that handles all rail related functions. + */ +class AIRail : public AIObject { +public: + static const char *GetClassName() { return "AIRail"; } + + /** + * All rail related error messages. + */ + enum ErrorMessages { + /** Base for rail building / maintaining errors */ + ERR_RAIL_BASE = AIError::ERR_CAT_RAIL << AIError::ERR_CAT_BIT_SIZE, + + /** One-way roads cannot have crossings */ + ERR_CROSSING_ON_ONEWAY_ROAD, // [STR_ERR_CROSSING_ON_ONEWAY_ROAD] + + /** Track not suitable for signals */ + ERR_UNSUITABLE_TRACK, // [STR_1005_NO_SUITABLE_RAILROAD_TRACK] + + /** Non-uniform stations is diabled */ + ERR_NONUNIFORM_STATIONS_DISABLED, // [STR_NONUNIFORM_STATIONS_DISALLOWED] + }; + + /** + * Types of rail known to the game. + */ + enum RailType { + /* Note: the values _are_ important as they represent an in-game value */ + RAILTYPE_INVALID = 0xFF, //!< Invalid RailType. + }; + + /** + * A bitmap with all possible rail tracks on a tile. + */ + enum RailTrack { + /* Note: the values _are_ important as they represent an in-game value */ + RAILTRACK_NE_SW = 1 << 0, //!< Track along the x-axis (north-east to south-west). + RAILTRACK_NW_SE = 1 << 1, //!< Track along the y-axis (north-west to south-east). + RAILTRACK_NW_NE = 1 << 2, //!< Track in the upper corner of the tile (north). + RAILTRACK_SW_SE = 1 << 3, //!< Track in the lower corner of the tile (south). + RAILTRACK_NW_SW = 1 << 4, //!< Track in the left corner of the tile (west). + RAILTRACK_NE_SE = 1 << 5, //!< Track in the right corner of the tile (east). + RAILTRACK_INVALID = 0xFF, //!< Flag for an invalid track. + }; + + /** + * Types of signal known to the game. + */ + enum SignalType { + /* Note: the values _are_ important as they represent an in-game value */ + SIGNALTYPE_NORMAL = 0, //!< Normal signal. + SIGNALTYPE_ENTRY = 1, //!< Entry presignal. + SIGNALTYPE_EXIT = 2, //!< Exit signal. + SIGNALTYPE_COMBO = 3, //!< Combo signal. + SIGNALTYPE_PBS = 4, //!< Normal PBS signal. + SIGNALTYPE_PBS_ONEWAY = 5, //!< No-entry PBS signal. + SIGNALTYPE_TWOWAY = 8, //!< Bit mask for twoway signal. + SIGNALTYPE_NORMAL_TWOWAY = SIGNALTYPE_NORMAL | SIGNALTYPE_TWOWAY, //!< Normal twoway signal. + SIGNALTYPE_ENTRY_TWOWAY = SIGNALTYPE_ENTRY | SIGNALTYPE_TWOWAY, //!< Entry twoway signal. + SIGNALTYPE_EXIT_TWOWAY = SIGNALTYPE_EXIT | SIGNALTYPE_TWOWAY, //!< Exit twoway signal. + SIGNALTYPE_COMBO_TWOWAY = SIGNALTYPE_COMBO | SIGNALTYPE_TWOWAY, //!< Combo twoway signal. + SIGNALTYPE_NONE = 0xFF, //!< No signal. + }; + + /** + * Checks whether the given tile is actually a tile with rail that can be + * used to traverse a tile. This excludes rail depots but includes + * stations and waypoints. + * @param tile The tile to check. + * @pre AIMap::IsValidTile(tile). + * @return True if and only if the tile has rail. + */ + static bool IsRailTile(TileIndex tile); + + /** + * Checks whether there is a road / rail crossing on a tile. + * @param tile The tile to check. + * @return True if and only if there is a road / rail crossing. + */ + static bool IsLevelCrossingTile(TileIndex tile); + + /** + * Checks whether the given tile is actually a tile with a rail depot. + * @param tile The tile to check. + * @pre AIMap::IsValidTile(tile). + * @return True if and only if the tile has a rail depot. + */ + static bool IsRailDepotTile(TileIndex tile); + + /** + * Checks whether the given tile is actually a tile with a rail station. + * @param tile The tile to check. + * @pre AIMap::IsValidTile(tile). + * @return True if and only if the tile has a rail station. + */ + static bool IsRailStationTile(TileIndex tile); + + /** + * Checks whether the given tile is actually a tile with a rail waypoint. + * @param tile The tile to check. + * @pre AIMap::IsValidTile(tile). + * @return True if and only if the tile has a rail waypoint. + */ + static bool IsRailWaypointTile(TileIndex tile); + + /** + * Check if a given RailType is available. + * @param rail_type The RailType to check for. + * @return True if this RailType can be used. + */ + static bool IsRailTypeAvailable(RailType rail_type); + + /** + * Get the current RailType set for all AIRail functions. + * @return The RailType currently set. + */ + static RailType GetCurrentRailType(); + + /** + * Set the RailType for all further AIRail functions. + * @param rail_type The RailType to set. + */ + static void SetCurrentRailType(RailType rail_type); + + /** + * Check if a train build for a rail type can run on another rail type. + * @param engine_rail_type The rail type the train is build for. + * @param track_rail_type The type you want to check. + * @pre AIRail::IsRailTypeAvailable(engine_rail_type). + * @pre AIRail::IsRailTypeAvailable(track_rail_type). + * @return Whether a train build for 'engine_rail_type' can run on 'track_rail_type'. + * @note Even if a train can run on a RailType that doesn't mean that it'll be + * able to power the train. Use TrainHasPowerOnRail for that. + */ + static bool TrainCanRunOnRail(AIRail::RailType engine_rail_type, AIRail::RailType track_rail_type); + + /** + * Check if a train build for a rail type has power on another rail type. + * @param engine_rail_type The rail type the train is build for. + * @param track_rail_type The type you want to check. + * @pre AIRail::IsRailTypeAvailable(engine_rail_type). + * @pre AIRail::IsRailTypeAvailable(track_rail_type). + * @return Whether a train build for 'engine_rail_type' has power on 'track_rail_type'. + */ + static bool TrainHasPowerOnRail(AIRail::RailType engine_rail_type, AIRail::RailType track_rail_type); + + /** + * Get the RailType that is used on a tile. + * @param tile The tile to check. + * @pre AITile::HasTransportType(tile, AITile.TRANSPORT_RAIL). + * @return The RailType that is used on a tile. + */ + static RailType GetRailType(TileIndex tile); + + /** + * Convert the tracks on all tiles within a rectangle to another RailType. + * @param start_tile One corner of the rectangle. + * @param end_tile The opposite corner of the rectangle. + * @param convert_to The RailType you want to convert the rails to. + * @pre AIMap::IsValidTile(start_tile). + * @pre AIMap::IsValidTile(end_tile). + * @pre IsRailTypeAvailable(convert_to). + * @exception AIRail::ERR_UNSUITABLE_TRACK + * @return Whether at least some rail has been converted succesfully. + */ + static bool ConvertRailType(TileIndex start_tile, TileIndex end_tile, AIRail::RailType convert_to); + + /** + * Gets the tile in front of a rail depot. + * @param depot The rail depot tile. + * @pre IsRailDepotTile(depot). + * @return The tile in front of the depot. + */ + static TileIndex GetRailDepotFrontTile(TileIndex depot); + + /** + * Gets the direction of a rail station tile. + * @param tile The rail station tile. + * @pre IsRailStationTile(tile). + * @return The direction of the station (either RAILTRACK_NE_SW or RAILTRACK_NW_SE). + */ + static RailTrack GetRailStationDirection(TileIndex tile); + + /** + * Builds a rail depot. + * @param tile Place to build the depot. + * @param front The tile exactly in front of the depot. + * @pre AIMap::IsValidTile(tile). + * @pre AIMap::IsValidTile(front). + * @pre 'tile' is not equal to 'front', but in a straight line of it. + * @pre IsRailTypeAvailable(GetCurrentRailType()). + * @exception AIError::ERR_FLAT_LAND_REQUIRED + * @exception AIError::ERR_AREA_NOT_CLEAR + * @return Whether the rail depot has been/can be build or not. + */ + static bool BuildRailDepot(TileIndex tile, TileIndex front); + + /** + * Build a rail station. + * @param tile Place to build the station. + * @param direction The direction to build the station. + * @param num_platforms The number of platforms to build. + * @param platform_length The length of each platform. + * @param join_adjacent When building next to an other station, don't create a new station when this flag is true. + * @pre IsRailTypeAvailable(GetCurrentRailType()). + * @pre AIMap::IsValidTile(tile). + * @pre direction == RAILTRACK_NW_SE || direction == RAILTRACK_NE_SW. + * @pre num_platforms > 0 && num_platforms <= 255. + * @pre platform_length > 0 && platform_length <= 255. + * @exception AIError::ERR_OWNED_BY_ANOTHER_COMPANY + * @exception AIError::ERR_AREA_NOT_CLEAR + * @exception AIError::ERR_FLAT_LAND_REQUIRED + * @exception AIStation::ERR_STATION_TOO_CLOSE_TO_ANOTHER_STATION + * @exception AIStation::ERR_STATION_TOO_MANY_STATIONS + * @exception AIStation::ERR_STATION_TOO_MANY_STATIONS_IN_TOWN + * @return Whether the station has been/can be build or not. + */ + static bool BuildRailStation(TileIndex tile, RailTrack direction, uint num_platforms, uint platform_length, bool join_adjacent); + + /** + * Build a NewGRF rail station. This calls callback 18 to let a NewGRF + * provide the station class / id to build, so we don't end up with + * only the default stations on the map. + * @param tile Place to build the station. + * @param direction The direction to build the station. + * @param num_platforms The number of platforms to build. + * @param platform_length The length of each platform. + * @param join_adjacent When building next to an other station, don't create a new station when this flag is true. + * @param cargo_id The CargoID of the cargo that will be transported from / to this station. + * @param source_industry The IndustryType of the industry you'll transport goods from. + * @param goal_industry The IndustryType of the industry you'll transport goods to. + * @param distance The manhattan distance you'll transport the cargo over. + * @param source_station True if this is the source station, false otherwise. + * @pre IsRailTypeAvailable(GetCurrentRailType()). + * @pre AIMap::IsValidTile(tile). + * @pre direction == RAILTRACK_NW_SE || direction == RAILTRACK_NE_SW. + * @pre num_platforms > 0 && num_platforms <= 255. + * @pre platform_length > 0 && platform_length <= 255. + * @exception AIError::ERR_OWNED_BY_ANOTHER_COMPANY + * @exception AIError::ERR_AREA_NOT_CLEAR + * @exception AIError::ERR_FLAT_LAND_REQUIRED + * @exception AIStation::ERR_STATION_TOO_CLOSE_TO_ANOTHER_STATION + * @exception AIStation::ERR_STATION_TOO_MANY_STATIONS + * @exception AIStation::ERR_STATION_TOO_MANY_STATIONS_IN_TOWN + * @return Whether the station has been/can be build or not. + */ + static bool BuildNewGRFRailStation(TileIndex tile, RailTrack direction, uint num_platforms, uint platform_length, bool join_adjacent, CargoID cargo_id, IndustryType source_industry, IndustryType goal_industry, int distance, bool source_station); + + /** + * Remove a rectangle of platform pieces from a rail station. + * @param tile One corner of the rectangle to clear. + * @param tile2 The oppposite corner. + * @pre IsValidTile(tile). + * @pre IsValidTile(tile2). + * @return Whether at least one tile has been/can be cleared or not. + */ + static bool RemoveRailStationTileRect(TileIndex tile, TileIndex tile2); + + /** + * Get all RailTracks on the given tile. + * @param tile The tile to check. + * @pre IsRailTile(tile). + * @return A bitmask of RailTrack with all RailTracks on the tile. + */ + static uint GetRailTracks(TileIndex tile); + + /** + * Build rail on the given tile. + * @param tile The tile to build on. + * @param rail_track The RailTrack to build. + * @pre AIMap::IsValidTile(tile). + * @pre IsRailTypeAvailable(GetCurrentRailType()). + * @exception AIError::ERR_AREA_NOT_CLEAR + * @exception AIError::ERR_LAND_SLOPED_WRONG + * @exception AIRoad::ERR_ROAD_WORKS_IN_PROGRESS + * @exception AIRail::ERR_CROSSING_ON_ONEWAY_ROAD + * @exception AIError::ERR_ALREADY_BUILT + * @return Whether the rail has been/can be build or not. + * @note You can only build a single track with this function so do not + * use the values from RailTrack as bitmask. + */ + static bool BuildRailTrack(TileIndex tile, RailTrack rail_track); + + /** + * Remove rail on the given tile. + * @param tile The tile to remove rail from. + * @param rail_track The RailTrack to remove. + * @pre AIMap::IsValidTile(tile). + * @pre (GetRailTracks(tile) & rail_track) != 0. + * @return Whether the rail has been/can be removed or not. + * @note You can only remove a single track with this function so do not + * use the values from RailTrack as bitmask. + */ + static bool RemoveRailTrack(TileIndex tile, RailTrack rail_track); + + /** + * Check if a tile connects two adjacent tiles. + * @param from The first tile to connect. + * @param tile The tile that is checked. + * @param to The second tile to connect. + * @pre from != to. + * @pre AIMap::DistanceManhattan(from, tile) == 1. + * @pre AIMap::DistanceManhattan(to, tile) == 1. + * @return True if 'tile' connects 'from' and 'to'. + */ + static bool AreTilesConnected(TileIndex from, TileIndex tile, TileIndex to); + + /** + * Build a rail connection between two tiles. + * @param from The tile just before the tile to build on. + * @param tile The first tile to build on. + * @param to The tile just after the last tile to build on. + * @pre from != to. + * @pre AIMap::DistanceManhattan(from, tile) == 1. + * @pre AIMap::DistanceManhattan(to, tile) >= 1. + * @pre (abs(abs(AIMap::GetTileX(to) - AIMap::GetTileX(tile)) - + * abs(AIMap::GetTileY(to) - AIMap::GetTileY(tile))) <= 1) || + * (AIMap::GetTileX(from) == AIMap::GetTileX(tile) && AIMap::GetTileX(tile) == AIMap::GetTileX(to)) || + * (AIMap::GetTileY(from) == AIMap::GetTileY(tile) && AIMap::GetTileY(tile) == AIMap::GetTileY(to)). + * @pre IsRailTypeAvailable(GetCurrentRailType()). + * @exception AIError::ERR_AREA_NOT_CLEAR + * @exception AIError::ERR_LAND_SLOPED_WRONG + * @exception AIRail::ERR_CROSSING_ON_ONEWAY_ROAD + * @exception AIRoad::ERR_ROAD_WORKS_IN_PROGRESS + * @exception AIError::ERR_ALREADY_BUILT + * @return Whether the rail has been/can be build or not. + */ + static bool BuildRail(TileIndex from, TileIndex tile, TileIndex to); + + /** + * Remove a rail connection between two tiles. + * @param from The tile just before the tile to remove rail from. + * @param tile The first tile to remove rail from. + * @param to The tile just after the last tile to remove rail from. + * @pre from != to. + * @pre AIMap::DistanceManhattan(from, tile) == 1. + * @pre AIMap::DistanceManhattan(to, tile) >= 1. + * @pre (abs(abs(AIMap::GetTileX(to) - AIMap::GetTileX(tile)) - + * abs(AIMap::GetTileY(to) - AIMap::GetTileY(tile))) <= 1) || + * (AIMap::GetTileX(from) == AIMap::GetTileX(tile) && AIMap::GetTileX(tile) == AIMap::GetTileX(to)) || + * (AIMap::GetTileY(from) == AIMap::GetTileY(tile) && AIMap::GetTileY(tile) == AIMap::GetTileY(to)). + * @return Whether the rail has been/can be removed or not. + */ + static bool RemoveRail(TileIndex from, TileIndex tile, TileIndex to); + + /** + * Get the SignalType of the signal on a tile or SIGNALTYPE_NONE if there is no signal. + * @pre AIMap::DistanceManhattan(tile, front) == 1. + * @param tile The tile that might have a signal. + * @param front The tile in front of 'tile'. + * @return The SignalType of the signal on 'tile' with it's front to 'front'. + */ + static SignalType GetSignalType(TileIndex tile, TileIndex front); + + /** + * Build a signal on a tile. + * @param tile The tile to build on. + * @param front The tile in front of the signal. + * @param signal The SignalType to build. + * @pre AIMap::DistanceManhattan(tile, front) == 1. + * @pre IsRailTile(tile) && !IsRailStationTile(tile) && !IsRailWaypointTile(tile). + * @exception AIRail::ERR_UNSUITABLE_TRACK + * @return Whether the signal has been/can be build or not. + */ + static bool BuildSignal(TileIndex tile, TileIndex front, SignalType signal); + + /** + * Remove a signal. + * @param tile The tile to remove the signal from. + * @param front The tile in front of the signal. + * @pre AIMap::DistanceManhattan(tile, front) == 1. + * @pre GetSignalType(tile, front) != SIGNALTYPE_NONE. + * @return Whether the signal has been/can be removed or not. + */ + static bool RemoveSignal(TileIndex tile, TileIndex front); +}; + +#endif /* AI_RAIL_HPP */ diff --git a/src/ai/api/ai_rail.hpp.sq b/src/ai/api/ai_rail.hpp.sq new file mode 100644 index 000000000..a77d34c70 --- /dev/null +++ b/src/ai/api/ai_rail.hpp.sq @@ -0,0 +1,93 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_rail.hpp" + +namespace SQConvert { + /* Allow enums to be used as Squirrel parameters */ + template <> AIRail::ErrorMessages GetParam(ForceType<AIRail::ErrorMessages>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AIRail::ErrorMessages)tmp; } + template <> int Return<AIRail::ErrorMessages>(HSQUIRRELVM vm, AIRail::ErrorMessages res) { sq_pushinteger(vm, (int32)res); return 1; } + template <> AIRail::RailType GetParam(ForceType<AIRail::RailType>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AIRail::RailType)tmp; } + template <> int Return<AIRail::RailType>(HSQUIRRELVM vm, AIRail::RailType res) { sq_pushinteger(vm, (int32)res); return 1; } + template <> AIRail::RailTrack GetParam(ForceType<AIRail::RailTrack>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AIRail::RailTrack)tmp; } + template <> int Return<AIRail::RailTrack>(HSQUIRRELVM vm, AIRail::RailTrack res) { sq_pushinteger(vm, (int32)res); return 1; } + template <> AIRail::SignalType GetParam(ForceType<AIRail::SignalType>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AIRail::SignalType)tmp; } + template <> int Return<AIRail::SignalType>(HSQUIRRELVM vm, AIRail::SignalType res) { sq_pushinteger(vm, (int32)res); return 1; } + + /* Allow AIRail to be used as Squirrel parameter */ + template <> AIRail *GetParam(ForceType<AIRail *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIRail *)instance; } + template <> AIRail &GetParam(ForceType<AIRail &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIRail *)instance; } + template <> const AIRail *GetParam(ForceType<const AIRail *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIRail *)instance; } + template <> const AIRail &GetParam(ForceType<const AIRail &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIRail *)instance; } + template <> int Return<AIRail *>(HSQUIRRELVM vm, AIRail *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIRail", res, NULL, DefSQDestructorCallback<AIRail>); return 1; } +}; // namespace SQConvert + +void SQAIRail_Register(Squirrel *engine) { + DefSQClass <AIRail> SQAIRail("AIRail"); + SQAIRail.PreRegister(engine); + SQAIRail.AddConstructor<void (AIRail::*)(), 1>(engine, "x"); + + SQAIRail.DefSQConst(engine, AIRail::ERR_RAIL_BASE, "ERR_RAIL_BASE"); + SQAIRail.DefSQConst(engine, AIRail::ERR_CROSSING_ON_ONEWAY_ROAD, "ERR_CROSSING_ON_ONEWAY_ROAD"); + SQAIRail.DefSQConst(engine, AIRail::ERR_UNSUITABLE_TRACK, "ERR_UNSUITABLE_TRACK"); + SQAIRail.DefSQConst(engine, AIRail::ERR_NONUNIFORM_STATIONS_DISABLED, "ERR_NONUNIFORM_STATIONS_DISABLED"); + SQAIRail.DefSQConst(engine, AIRail::RAILTYPE_INVALID, "RAILTYPE_INVALID"); + SQAIRail.DefSQConst(engine, AIRail::RAILTRACK_NE_SW, "RAILTRACK_NE_SW"); + SQAIRail.DefSQConst(engine, AIRail::RAILTRACK_NW_SE, "RAILTRACK_NW_SE"); + SQAIRail.DefSQConst(engine, AIRail::RAILTRACK_NW_NE, "RAILTRACK_NW_NE"); + SQAIRail.DefSQConst(engine, AIRail::RAILTRACK_SW_SE, "RAILTRACK_SW_SE"); + SQAIRail.DefSQConst(engine, AIRail::RAILTRACK_NW_SW, "RAILTRACK_NW_SW"); + SQAIRail.DefSQConst(engine, AIRail::RAILTRACK_NE_SE, "RAILTRACK_NE_SE"); + SQAIRail.DefSQConst(engine, AIRail::RAILTRACK_INVALID, "RAILTRACK_INVALID"); + SQAIRail.DefSQConst(engine, AIRail::SIGNALTYPE_NORMAL, "SIGNALTYPE_NORMAL"); + SQAIRail.DefSQConst(engine, AIRail::SIGNALTYPE_ENTRY, "SIGNALTYPE_ENTRY"); + SQAIRail.DefSQConst(engine, AIRail::SIGNALTYPE_EXIT, "SIGNALTYPE_EXIT"); + SQAIRail.DefSQConst(engine, AIRail::SIGNALTYPE_COMBO, "SIGNALTYPE_COMBO"); + SQAIRail.DefSQConst(engine, AIRail::SIGNALTYPE_PBS, "SIGNALTYPE_PBS"); + SQAIRail.DefSQConst(engine, AIRail::SIGNALTYPE_PBS_ONEWAY, "SIGNALTYPE_PBS_ONEWAY"); + SQAIRail.DefSQConst(engine, AIRail::SIGNALTYPE_TWOWAY, "SIGNALTYPE_TWOWAY"); + SQAIRail.DefSQConst(engine, AIRail::SIGNALTYPE_NORMAL_TWOWAY, "SIGNALTYPE_NORMAL_TWOWAY"); + SQAIRail.DefSQConst(engine, AIRail::SIGNALTYPE_ENTRY_TWOWAY, "SIGNALTYPE_ENTRY_TWOWAY"); + SQAIRail.DefSQConst(engine, AIRail::SIGNALTYPE_EXIT_TWOWAY, "SIGNALTYPE_EXIT_TWOWAY"); + SQAIRail.DefSQConst(engine, AIRail::SIGNALTYPE_COMBO_TWOWAY, "SIGNALTYPE_COMBO_TWOWAY"); + SQAIRail.DefSQConst(engine, AIRail::SIGNALTYPE_NONE, "SIGNALTYPE_NONE"); + + AIError::RegisterErrorMap(STR_ERR_CROSSING_ON_ONEWAY_ROAD, AIRail::ERR_CROSSING_ON_ONEWAY_ROAD); + AIError::RegisterErrorMap(STR_1005_NO_SUITABLE_RAILROAD_TRACK, AIRail::ERR_UNSUITABLE_TRACK); + AIError::RegisterErrorMap(STR_NONUNIFORM_STATIONS_DISALLOWED, AIRail::ERR_NONUNIFORM_STATIONS_DISABLED); + + AIError::RegisterErrorMapString(AIRail::ERR_CROSSING_ON_ONEWAY_ROAD, "ERR_CROSSING_ON_ONEWAY_ROAD"); + AIError::RegisterErrorMapString(AIRail::ERR_UNSUITABLE_TRACK, "ERR_UNSUITABLE_TRACK"); + AIError::RegisterErrorMapString(AIRail::ERR_NONUNIFORM_STATIONS_DISABLED, "ERR_NONUNIFORM_STATIONS_DISABLED"); + + SQAIRail.DefSQStaticMethod(engine, &AIRail::GetClassName, "GetClassName", 1, "x"); + SQAIRail.DefSQStaticMethod(engine, &AIRail::IsRailTile, "IsRailTile", 2, "xi"); + SQAIRail.DefSQStaticMethod(engine, &AIRail::IsLevelCrossingTile, "IsLevelCrossingTile", 2, "xi"); + SQAIRail.DefSQStaticMethod(engine, &AIRail::IsRailDepotTile, "IsRailDepotTile", 2, "xi"); + SQAIRail.DefSQStaticMethod(engine, &AIRail::IsRailStationTile, "IsRailStationTile", 2, "xi"); + SQAIRail.DefSQStaticMethod(engine, &AIRail::IsRailWaypointTile, "IsRailWaypointTile", 2, "xi"); + SQAIRail.DefSQStaticMethod(engine, &AIRail::IsRailTypeAvailable, "IsRailTypeAvailable", 2, "xi"); + SQAIRail.DefSQStaticMethod(engine, &AIRail::GetCurrentRailType, "GetCurrentRailType", 1, "x"); + SQAIRail.DefSQStaticMethod(engine, &AIRail::SetCurrentRailType, "SetCurrentRailType", 2, "xi"); + SQAIRail.DefSQStaticMethod(engine, &AIRail::TrainCanRunOnRail, "TrainCanRunOnRail", 3, "xii"); + SQAIRail.DefSQStaticMethod(engine, &AIRail::TrainHasPowerOnRail, "TrainHasPowerOnRail", 3, "xii"); + SQAIRail.DefSQStaticMethod(engine, &AIRail::GetRailType, "GetRailType", 2, "xi"); + SQAIRail.DefSQStaticMethod(engine, &AIRail::ConvertRailType, "ConvertRailType", 4, "xiii"); + SQAIRail.DefSQStaticMethod(engine, &AIRail::GetRailDepotFrontTile, "GetRailDepotFrontTile", 2, "xi"); + SQAIRail.DefSQStaticMethod(engine, &AIRail::GetRailStationDirection, "GetRailStationDirection", 2, "xi"); + SQAIRail.DefSQStaticMethod(engine, &AIRail::BuildRailDepot, "BuildRailDepot", 3, "xii"); + SQAIRail.DefSQStaticMethod(engine, &AIRail::BuildRailStation, "BuildRailStation", 6, "xiiiib"); + SQAIRail.DefSQStaticMethod(engine, &AIRail::BuildNewGRFRailStation, "BuildNewGRFRailStation", 11, "xiiiibiiiib"); + SQAIRail.DefSQStaticMethod(engine, &AIRail::RemoveRailStationTileRect, "RemoveRailStationTileRect", 3, "xii"); + SQAIRail.DefSQStaticMethod(engine, &AIRail::GetRailTracks, "GetRailTracks", 2, "xi"); + SQAIRail.DefSQStaticMethod(engine, &AIRail::BuildRailTrack, "BuildRailTrack", 3, "xii"); + SQAIRail.DefSQStaticMethod(engine, &AIRail::RemoveRailTrack, "RemoveRailTrack", 3, "xii"); + SQAIRail.DefSQStaticMethod(engine, &AIRail::AreTilesConnected, "AreTilesConnected", 4, "xiii"); + SQAIRail.DefSQStaticMethod(engine, &AIRail::BuildRail, "BuildRail", 4, "xiii"); + SQAIRail.DefSQStaticMethod(engine, &AIRail::RemoveRail, "RemoveRail", 4, "xiii"); + SQAIRail.DefSQStaticMethod(engine, &AIRail::GetSignalType, "GetSignalType", 3, "xii"); + SQAIRail.DefSQStaticMethod(engine, &AIRail::BuildSignal, "BuildSignal", 4, "xiii"); + SQAIRail.DefSQStaticMethod(engine, &AIRail::RemoveSignal, "RemoveSignal", 3, "xii"); + + SQAIRail.PostRegister(engine); +} diff --git a/src/ai/api/ai_railtypelist.cpp b/src/ai/api/ai_railtypelist.cpp new file mode 100644 index 000000000..494cb5222 --- /dev/null +++ b/src/ai/api/ai_railtypelist.cpp @@ -0,0 +1,14 @@ +/* $Id$ */ + +/** @file ai_railtypelist.cpp Implementation of AIRailTypeList and friends. */ + +#include "ai_railtypelist.hpp" +#include "../../rail.h" +#include "../../company_func.h" + +AIRailTypeList::AIRailTypeList() +{ + for (RailType rt = RAILTYPE_BEGIN; rt != RAILTYPE_END; rt++) { + if (::HasRailtypeAvail(_current_company, rt)) this->AddItem(rt); + } +} diff --git a/src/ai/api/ai_railtypelist.hpp b/src/ai/api/ai_railtypelist.hpp new file mode 100644 index 000000000..f9837d7b6 --- /dev/null +++ b/src/ai/api/ai_railtypelist.hpp @@ -0,0 +1,20 @@ +/* $Id$ */ + +/** @file ai_railtypelist.hpp List all available railtypes. */ + +#ifndef AI_RAILTYPELIST_HPP +#define AI_RAILTYPELIST_HPP + +#include "ai_abstractlist.hpp" + +/** + * Creates a list of all available railtypes. + * @ingroup AIList + */ +class AIRailTypeList : public AIAbstractList { +public: + static const char *GetClassName() { return "AIRailTypeList"; } + AIRailTypeList(); +}; + +#endif /* AI_RAILTYPELIST_HPP */ diff --git a/src/ai/api/ai_railtypelist.hpp.sq b/src/ai/api/ai_railtypelist.hpp.sq new file mode 100644 index 000000000..255124bf6 --- /dev/null +++ b/src/ai/api/ai_railtypelist.hpp.sq @@ -0,0 +1,23 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_railtypelist.hpp" + +namespace SQConvert { + /* Allow AIRailTypeList to be used as Squirrel parameter */ + template <> AIRailTypeList *GetParam(ForceType<AIRailTypeList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIRailTypeList *)instance; } + template <> AIRailTypeList &GetParam(ForceType<AIRailTypeList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIRailTypeList *)instance; } + template <> const AIRailTypeList *GetParam(ForceType<const AIRailTypeList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIRailTypeList *)instance; } + template <> const AIRailTypeList &GetParam(ForceType<const AIRailTypeList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIRailTypeList *)instance; } + template <> int Return<AIRailTypeList *>(HSQUIRRELVM vm, AIRailTypeList *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIRailTypeList", res, NULL, DefSQDestructorCallback<AIRailTypeList>); return 1; } +}; // namespace SQConvert + +void SQAIRailTypeList_Register(Squirrel *engine) { + DefSQClass <AIRailTypeList> SQAIRailTypeList("AIRailTypeList"); + SQAIRailTypeList.PreRegister(engine, "AIAbstractList"); + SQAIRailTypeList.AddConstructor<void (AIRailTypeList::*)(), 1>(engine, "x"); + + SQAIRailTypeList.DefSQStaticMethod(engine, &AIRailTypeList::GetClassName, "GetClassName", 1, "x"); + + SQAIRailTypeList.PostRegister(engine); +} diff --git a/src/ai/api/ai_road.cpp b/src/ai/api/ai_road.cpp new file mode 100644 index 000000000..a6a14656c --- /dev/null +++ b/src/ai/api/ai_road.cpp @@ -0,0 +1,548 @@ +/* $Id$ */ + +/** @file ai_road.cpp Implementation of AIRoad. */ + +#include "ai_road.hpp" +#include "ai_map.hpp" +#include "ai_list.hpp" +#include "../../openttd.h" +#include "../../road_map.h" +#include "../../station_map.h" +#include "../../tunnelbridge_map.h" +#include "../../command_type.h" +#include "../../company_func.h" +#include "../../settings_type.h" +#include "../../script/squirrel_helper_type.hpp" + +/* static */ bool AIRoad::IsRoadTile(TileIndex tile) +{ + if (!::IsValidTile(tile)) return false; + + return (::IsTileType(tile, MP_ROAD) && ::GetRoadTileType(tile) != ROAD_TILE_DEPOT) || + IsDriveThroughRoadStationTile(tile); +} + +/* static */ bool AIRoad::IsRoadDepotTile(TileIndex tile) +{ + if (!::IsValidTile(tile)) return false; + + return ::IsTileType(tile, MP_ROAD) && ::GetRoadTileType(tile) == ROAD_TILE_DEPOT && + (::RoadTypeToRoadTypes((::RoadType)GetCurrentRoadType()) & ::GetRoadTypes(tile)) != 0; +} + +/* static */ bool AIRoad::IsRoadStationTile(TileIndex tile) +{ + if (!::IsValidTile(tile)) return false; + + return ::IsRoadStopTile(tile) && (::RoadTypeToRoadTypes((::RoadType)GetCurrentRoadType()) & ::GetRoadTypes(tile)) != 0; +} + +/* static */ bool AIRoad::IsDriveThroughRoadStationTile(TileIndex tile) +{ + if (!::IsValidTile(tile)) return false; + + return ::IsDriveThroughStopTile(tile) && (::RoadTypeToRoadTypes((::RoadType)GetCurrentRoadType()) & ::GetRoadTypes(tile)) != 0; +} + +/* static */ bool AIRoad::IsRoadTypeAvailable(RoadType road_type) +{ + return ::HasRoadTypesAvail(_current_company, ::RoadTypeToRoadTypes((::RoadType)road_type)); +} + +/* static */ AIRoad::RoadType AIRoad::GetCurrentRoadType() +{ + return (RoadType)AIObject::GetRoadType(); +} + +/* static */ void AIRoad::SetCurrentRoadType(RoadType road_type) +{ + if (!IsRoadTypeAvailable(road_type)) return; + + AIObject::SetRoadType((::RoadType)road_type); +} + +/* static */ bool AIRoad::HasRoadType(TileIndex tile, RoadType road_type) +{ + if (!AIMap::IsValidTile(tile)) return false; + if (!IsRoadTypeAvailable(road_type)) return false; + return ::GetAnyRoadBits(tile, (::RoadType)road_type, false) != ROAD_NONE; +} + +/* static */ bool AIRoad::AreRoadTilesConnected(TileIndex t1, TileIndex t2) +{ + if (!::IsValidTile(t1)) return false; + if (!::IsValidTile(t2)) return false; + + /* Tiles not neighbouring */ + if ((abs((int)::TileX(t1) - (int)::TileX(t2)) + abs((int)::TileY(t1) - (int)::TileY(t2))) != 1) return false; + + RoadBits r1 = ::GetAnyRoadBits(t1, AIObject::GetRoadType()); + RoadBits r2 = ::GetAnyRoadBits(t2, AIObject::GetRoadType()); + + uint dir_1 = (::TileX(t1) == ::TileX(t2)) ? (::TileY(t1) < ::TileY(t2) ? 2 : 0) : (::TileX(t1) < ::TileX(t2) ? 1 : 3); + uint dir_2 = 2 ^ dir_1; + + DisallowedRoadDirections drd2 = IsNormalRoadTile(t2) ? GetDisallowedRoadDirections(t2) : DRD_NONE; + + return HasBit(r1, dir_1) && HasBit(r2, dir_2) && drd2 != DRD_BOTH && drd2 != (dir_1 > dir_2 ? DRD_SOUTHBOUND : DRD_NORTHBOUND); +} + +/* Helper functions for AIRoad::CanBuildConnectedRoadParts(). */ + +/** + * Check whether the given existing bits the start and end part can be build. + * As the function assumes the bits being build on a slope that does not + * allow level foundations all of the existing parts will always be in + * a straight line. This also needs to hold for the start and end parts, + * otherwise it is for sure not valid. Finally a check will be done to + * determine whether the existing road parts match the to-be-build parts. + * As they can only be placed in one direction, just checking the start + * part with the first existing part is enough. + * @param existing The existing road parts. + * @param start The part that should be build first. + * @param end The part that will be build second. + * @return True if and only if the road bits can be build. + */ +static bool CheckAutoExpandedRoadBits(const Array *existing, int32 start, int32 end) +{ + return (start + end == 0) && (existing->size == 0 || existing->array[0] == start || existing->array[0] == end); +} + +/** + * Lookup function for building road parts when building on slopes is disabled. + * @param slope The slope of the tile to examine. + * @param existing The existing road parts. + * @param start The part that should be build first. + * @param end The part that will be build second. + * @return 0 when the build parts do not connect, 1 when they do connect once + * they are build or 2 when building the first part automatically + * builds the second part. + */ +static int32 LookupWithoutBuildOnSlopes(::Slope slope, const Array *existing, int32 start, int32 end) +{ + switch (slope) { + /* Flat slopes can always be build. */ + case SLOPE_FLAT: + return 1; + + /* Only 4 of the slopes can be build upon. Testing the existing bits is + * necessary because these bits can be something else when the settings + * in the game have been changed. + */ + case SLOPE_NE: case SLOPE_SW: + return (CheckAutoExpandedRoadBits(existing, start, end) && (start == 1 || end == 1)) ? (existing->size == 0 ? 2 : 1) : 0; + case SLOPE_SE: case SLOPE_NW: + return (CheckAutoExpandedRoadBits(existing, start, end) && (start != 1 && end != 1)) ? (existing->size == 0 ? 2 : 1) : 0; + + /* Any other tile cannot be built on. */ + default: + return 0; + } +} + +/** + * Rotate a neighbour bit a single time clockwise. + * @param neighbour The neighbour. + * @return The rotate neighbour data. + */ +static int32 RotateNeighbour(int32 neighbour) +{ + switch (neighbour) { + case -2: return -1; + case -1: return 2; + case 1: return -2; + case 2: return 1; + default: NOT_REACHED(); + } +} + +/** + * Convert a neighbour to a road bit representation for easy internal use. + * @param neighbour The neighbour. + * @return The bits representing the direction. + */ +static RoadBits NeighbourToRoadBits(int32 neighbour) +{ + switch (neighbour) { + case -2: return ROAD_NW; + case -1: return ROAD_NE; + case 2: return ROAD_SE; + case 1: return ROAD_SW; + default: NOT_REACHED(); + } +} + +/** + * Lookup function for building road parts when building on slopes is enabled. + * @param slope The slope of the tile to examine. + * @param existing The existing neighbours. + * @param start The part that should be build first. + * @param end The part that will be build second. + * @return 0 when the build parts do not connect, 1 when they do connect once + * they are build or 2 when building the first part automatically + * builds the second part. + */ +static int32 LookupWithBuildOnSlopes(::Slope slope, Array *existing, int32 start, int32 end) +{ + if (::IsSteepSlope(slope)) { + switch (slope) { + /* On steep slopes one can only build straight roads that will be + * automatically expanded to a straight road. Just check that the existing + * road parts are in the same direction. */ + case SLOPE_STEEP_S: + case SLOPE_STEEP_W: + case SLOPE_STEEP_N: + case SLOPE_STEEP_E: + return CheckAutoExpandedRoadBits(existing, start, end) ? (existing->size == 0 ? 2 : 1) : 0; + + /* All other slopes are invalid slopes!. */ + default: + return -1; + } + } + + /* The slope is not steep. Furthermore lots of slopes are generally the + * same but are only rotated. So to reduce the amount of lookup work that + * needs to be done the data is made uniform. This means rotating the + * existing parts and updating the slope. */ + static const ::Slope base_slopes[] = { + SLOPE_FLAT, SLOPE_W, SLOPE_W, SLOPE_SW, + SLOPE_W, SLOPE_EW, SLOPE_SW, SLOPE_WSE, + SLOPE_W, SLOPE_SW, SLOPE_EW, SLOPE_WSE, + SLOPE_SW, SLOPE_WSE, SLOPE_WSE}; + static const byte base_rotates[] = {0, 0, 1, 0, 2, 0, 1, 0, 3, 3, 2, 3, 2, 2, 1}; + + if (slope >= (::Slope)lengthof(base_slopes)) { + /* This slope is an invalid slope, so ignore it. */ + return -1; + } + byte base_rotate = base_rotates[slope]; + slope = base_slopes[slope]; + + /* Some slopes don't need rotating, so return early when we know we do + * not need to rotate. */ + switch (slope) { + case SLOPE_FLAT: + /* Flat slopes can always be build. */ + return 1; + + case SLOPE_EW: + case SLOPE_WSE: + /* A slope similar to a SLOPE_EW or SLOPE_WSE will always cause + * foundations which makes them accessible from all sides. */ + return 1; + + case SLOPE_W: + case SLOPE_SW: + /* A slope for which we need perform some calculations. */ + break; + + default: + /* An invalid slope. */ + return -1; + } + + /* Now perform the actual rotation. */ + for (int j = 0; j < base_rotate; j++) { + for (int i = 0; i < existing->size; i++) { + existing->array[i] = RotateNeighbour(existing->array[i]); + } + start = RotateNeighbour(start); + end = RotateNeighbour(end); + } + + /* Create roadbits out of the data for easier handling. */ + RoadBits start_roadbits = NeighbourToRoadBits(start); + RoadBits new_roadbits = start_roadbits | NeighbourToRoadBits(end); + RoadBits existing_roadbits = ROAD_NONE; + for (int i = 0; i < existing->size; i++) { + existing_roadbits |= NeighbourToRoadBits(existing->array[i]); + } + + switch (slope) { + case SLOPE_W: + /* A slope similar to a SLOPE_W. */ + switch (new_roadbits) { + case 6: // ROAD_SE | ROAD_SW: + case 9: // ROAD_NE | ROAD_NW: + case 12: // ROAD_NE | ROAD_SE: + /* Cannot build anything with a turn from the low side. */ + return 0; + + case 5: // ROAD_SE | ROAD_NW: + case 10: // ROAD_NE | ROAD_SW: + /* A 'sloped' tile is going to be build. */ + if ((existing_roadbits | new_roadbits) != new_roadbits) { + /* There is already a foundation on the tile, or at least + * another slope that is not compatible with the new one. */ + return 0; + } + /* If the start is in the low part, it is automatically + * building the second part too. */ + return ((start_roadbits & (ROAD_NE | ROAD_SE)) && !(existing_roadbits & (ROAD_SW | ROAD_NW))) ? 2 : 1; + + default: + /* Roadbits causing a foundation are going to be build. + * When the existing roadbits are slopes (the lower bits + * are used), this cannot be done. */ + if ((existing_roadbits | new_roadbits) == new_roadbits) return 1; + return (existing_roadbits & (ROAD_NE | ROAD_SE)) ? 0 : 1; + } + + case SLOPE_SW: + /* A slope similar to a SLOPE_SW. */ + switch (new_roadbits) { + case 9: // ROAD_NE | ROAD_NW: + case 12: // ROAD_NE | ROAD_SE: + /* Cannot build anything with a turn from the low side. */ + return 0; + + case 10: // ROAD_NE | ROAD_SW: + /* A 'sloped' tile is going to be build. */ + if ((existing_roadbits | new_roadbits) != new_roadbits) { + /* There is already a foundation on the tile, or at least + * another slope that is not compatible with the new one. */ + return 0; + } + /* If the start is in the low part, it is automatically + * building the second part too. */ + return ((start_roadbits & ROAD_NE) && !(existing_roadbits & ROAD_SW)) ? 2 : 1; + + default: + /* Roadbits causing a foundation are going to be build. + * When the existing roadbits are slopes (the lower bits + * are used), this cannot be done. */ + return (existing_roadbits & ROAD_NE) ? 0 : 1; + } + + default: + NOT_REACHED(); + } +} + +/** + * Normalise all input data so we can easily handle it without needing + * to call the API lots of times or create large if-elseif-elseif-else + * constructs. + * In this case it means that a TileXY(0, -1) becomes -2 and TileXY(0, 1) + * becomes 2. TileXY(-1, 0) and TileXY(1, 0) stay respectively -1 and 1. + * Any other value means that it is an invalid tile offset. + * @param tile The tile to normalise. + * @return True if and only if the tile offset is valid. + */ +static bool NormaliseTileOffset(int32 *tile) +{ + if (*tile == 1 || *tile == -1) return true; + if (*tile == ::TileDiffXY(0, -1)) { + *tile = -2; + return true; + } + if (*tile == ::TileDiffXY(0, 1)) { + *tile = 2; + return true; + } + return false; +} + +/* static */ int32 AIRoad::CanBuildConnectedRoadParts(AITile::Slope slope_, Array *existing, TileIndex start_, TileIndex end_) +{ + ::Slope slope = (::Slope)slope_; + int32 start = start_; + int32 end = end_; + + /* The start tile and end tile cannot be the same tile either. */ + if (start == end) return -1; + + for (int i = 0; i < existing->size; i++) { + if (!NormaliseTileOffset(&existing->array[i])) return -1; + } + + if (!NormaliseTileOffset(&start)) return -1; + if (!NormaliseTileOffset(&end)) return -1; + + /* Without build on slopes the characteristics are vastly different, so use + * a different helper function (one that is much simpler). */ + return _settings_game.construction.build_on_slopes ? LookupWithBuildOnSlopes(slope, existing, start, end) : LookupWithoutBuildOnSlopes(slope, existing, start, end); +} + +/* static */ int32 AIRoad::CanBuildConnectedRoadPartsHere(TileIndex tile, TileIndex start, TileIndex end) +{ + if (!::IsValidTile(tile) || !::IsValidTile(start) || !::IsValidTile(end)) return -1; + if (::DistanceManhattan(tile, start) != 1 || ::DistanceManhattan(tile, end) != 1) return -1; + + /* ROAD_NW ROAD_SW ROAD_SE ROAD_NE */ + static const TileIndex neighbours[] = {::TileDiffXY(0, -1), ::TileDiffXY(1, 0), ::TileDiffXY(0, 1), ::TileDiffXY(-1, 0)}; + Array *existing = (Array*)alloca(sizeof(Array) + lengthof(neighbours) * sizeof(int32)); + existing->size = 0; + + ::RoadBits rb = ::ROAD_NONE; + if (::IsNormalRoadTile(tile)) { + rb = ::GetAllRoadBits(tile); + } else { + for (::RoadType rt = ::ROADTYPE_BEGIN; rt < ::ROADTYPE_END; rt++) rb |= ::GetAnyRoadBits(tile, rt); + } + for (uint i = 0; i < lengthof(neighbours); i++) { + if (HasBit(rb, i)) existing->array[existing->size++] = neighbours[i]; + } + + return AIRoad::CanBuildConnectedRoadParts(AITile::GetSlope(tile), existing, start - tile, end - tile); +} + +/** + * Check whether one can reach (possibly by building) a road piece the center + * of the neighbouring tile. This includes roads and (drive through) stations. + * @param start_tile The tile to "enter" the neighbouring tile. + * @param neighbour The direction to the neighbouring tile to "enter". + * @return true if and only if the tile is reachable. + */ +static bool NeighbourHasReachableRoad(::RoadTypes rts, TileIndex start_tile, DiagDirection neighbour) +{ + TileIndex neighbour_tile = ::TileAddByDiagDir(start_tile, neighbour); + if ((rts & ::GetRoadTypes(neighbour_tile)) == 0) return false; + + switch (::GetTileType(neighbour_tile)) { + case MP_ROAD: + return (::GetRoadTileType(neighbour_tile) != ROAD_TILE_DEPOT); + + case MP_STATION: + if (::IsDriveThroughStopTile(neighbour_tile)) { + return (::DiagDirToAxis(neighbour) == ::DiagDirToAxis(::GetRoadStopDir(neighbour_tile))); + } + return false; + + default: + return false; + } +} + +/* static */ int32 AIRoad::GetNeighbourRoadCount(TileIndex tile) +{ + if (!::IsValidTile(tile)) return false; + + ::RoadTypes rts = ::RoadTypeToRoadTypes((::RoadType)GetCurrentRoadType()); + int32 neighbour = 0; + + if (NeighbourHasReachableRoad(rts, tile, DIAGDIR_NE)) neighbour++; + if (NeighbourHasReachableRoad(rts, tile, DIAGDIR_SE)) neighbour++; + if (NeighbourHasReachableRoad(rts, tile, DIAGDIR_SW)) neighbour++; + if (NeighbourHasReachableRoad(rts, tile, DIAGDIR_NW)) neighbour++; + + return neighbour; +} + +/* static */ TileIndex AIRoad::GetRoadDepotFrontTile(TileIndex depot) +{ + if (!IsRoadDepotTile(depot)) return INVALID_TILE; + + return depot + ::TileOffsByDiagDir(::GetRoadDepotDirection(depot)); +} + +/* static */ TileIndex AIRoad::GetRoadStationFrontTile(TileIndex station) +{ + if (!IsRoadStationTile(station)) return INVALID_TILE; + + return station + ::TileOffsByDiagDir(::GetRoadStopDir(station)); +} + +/* static */ TileIndex AIRoad::GetDriveThroughBackTile(TileIndex station) +{ + if (!IsDriveThroughRoadStationTile(station)) return INVALID_TILE; + + return station + ::TileOffsByDiagDir(::ReverseDiagDir(::GetRoadStopDir(station))); +} + +/* static */ bool AIRoad::_BuildRoadInternal(TileIndex start, TileIndex end, bool one_way, bool full) +{ + EnforcePrecondition(false, start != end); + EnforcePrecondition(false, ::IsValidTile(start)); + EnforcePrecondition(false, ::IsValidTile(end)); + EnforcePrecondition(false, ::TileX(start) == ::TileX(end) || ::TileY(start) == ::TileY(end)); + EnforcePrecondition(false, !one_way || AIObject::GetRoadType() == ::ROADTYPE_ROAD); + + return AIObject::DoCommand(end, start, (::TileY(start) != ::TileY(end) ? 4 : 0) | (((start < end) == !full) ? 1 : 2) | (AIObject::GetRoadType() << 3) | ((one_way ? 1 : 0) << 5), CMD_BUILD_LONG_ROAD); +} + +/* static */ bool AIRoad::BuildRoad(TileIndex start, TileIndex end) +{ + return _BuildRoadInternal(start, end, false, false); +} + +/* static */ bool AIRoad::BuildOneWayRoad(TileIndex start, TileIndex end) +{ + return _BuildRoadInternal(start, end, true, false); +} + +/* static */ bool AIRoad::BuildRoadFull(TileIndex start, TileIndex end) +{ + return _BuildRoadInternal(start, end, false, true); +} + +/* static */ bool AIRoad::BuildOneWayRoadFull(TileIndex start, TileIndex end) +{ + return _BuildRoadInternal(start, end, true, true); +} + +/* static */ bool AIRoad::BuildRoadDepot(TileIndex tile, TileIndex front) +{ + EnforcePrecondition(false, tile != front); + EnforcePrecondition(false, ::IsValidTile(tile)); + EnforcePrecondition(false, ::IsValidTile(front)); + EnforcePrecondition(false, ::TileX(tile) == ::TileX(front) || ::TileY(tile) == ::TileY(front)); + + uint entrance_dir = (::TileX(tile) == ::TileX(front)) ? (::TileY(tile) < ::TileY(front) ? 1 : 3) : (::TileX(tile) < ::TileX(front) ? 2 : 0); + + return AIObject::DoCommand(tile, entrance_dir | (AIObject::GetRoadType() << 2), 0, CMD_BUILD_ROAD_DEPOT); +} + +/* static */ bool AIRoad::BuildRoadStation(TileIndex tile, TileIndex front, bool truck, bool drive_through, bool join_adjacent) +{ + EnforcePrecondition(false, tile != front); + EnforcePrecondition(false, ::IsValidTile(tile)); + EnforcePrecondition(false, ::IsValidTile(front)); + EnforcePrecondition(false, ::TileX(tile) == ::TileX(front) || ::TileY(tile) == ::TileY(front)); + + uint entrance_dir; + if (drive_through) { + entrance_dir = ::TileY(tile) != ::TileY(front); + } else { + entrance_dir = (::TileX(tile) == ::TileX(front)) ? (::TileY(tile) < ::TileY(front) ? 1 : 3) : (::TileX(tile) < ::TileX(front) ? 2 : 0); + } + + return AIObject::DoCommand(tile, entrance_dir, (join_adjacent ? 0 : 32) | (drive_through ? 2 : 0) | (truck ? 1 : 0) | (::RoadTypeToRoadTypes(AIObject::GetRoadType()) << 2) | (INVALID_STATION << 16), CMD_BUILD_ROAD_STOP); +} + +/* static */ bool AIRoad::RemoveRoad(TileIndex start, TileIndex end) +{ + EnforcePrecondition(false, ::IsValidTile(start)); + EnforcePrecondition(false, ::IsValidTile(end)); + EnforcePrecondition(false, ::TileX(start) == ::TileX(end) || ::TileY(start) == ::TileY(end)); + + return AIObject::DoCommand(end, start, (::TileY(start) != ::TileY(end) ? 4 : 0) | (start < end ? 1 : 2) | (AIObject::GetRoadType() << 3), CMD_REMOVE_LONG_ROAD); +} + +/* static */ bool AIRoad::RemoveRoadFull(TileIndex start, TileIndex end) +{ + EnforcePrecondition(false, ::IsValidTile(start)); + EnforcePrecondition(false, ::IsValidTile(end)); + EnforcePrecondition(false, ::TileX(start) == ::TileX(end) || ::TileY(start) == ::TileY(end)); + + return AIObject::DoCommand(end, start, (::TileY(start) != ::TileY(end) ? 4 : 0) | (start < end ? 2 : 1) | (AIObject::GetRoadType() << 3), CMD_REMOVE_LONG_ROAD); +} + +/* static */ bool AIRoad::RemoveRoadDepot(TileIndex tile) +{ + EnforcePrecondition(false, ::IsValidTile(tile)); + EnforcePrecondition(false, IsTileType(tile, MP_ROAD)) + EnforcePrecondition(false, GetRoadTileType(tile) == ROAD_TILE_DEPOT); + + return AIObject::DoCommand(tile, 0, 0, CMD_LANDSCAPE_CLEAR); +} + +/* static */ bool AIRoad::RemoveRoadStation(TileIndex tile) +{ + EnforcePrecondition(false, ::IsValidTile(tile)); + EnforcePrecondition(false, IsTileType(tile, MP_STATION)); + EnforcePrecondition(false, IsRoadStop(tile)); + + return AIObject::DoCommand(tile, 0, GetRoadStopType(tile), CMD_REMOVE_ROAD_STOP); +} diff --git a/src/ai/api/ai_road.hpp b/src/ai/api/ai_road.hpp new file mode 100644 index 000000000..c80f63177 --- /dev/null +++ b/src/ai/api/ai_road.hpp @@ -0,0 +1,408 @@ +/* $Id$ */ + +/** @file ai_road.hpp Everything to query and build roads. */ + +#ifndef AI_ROAD_HPP +#define AI_ROAD_HPP + +#include "ai_object.hpp" +#include "ai_error.hpp" +#include "ai_tile.hpp" + +/** + * Class that handles all road related functions. + */ +class AIRoad : public AIObject { +public: + static const char *GetClassName() { return "AIRoad"; } + + /** + * All road related error messages. + */ + enum ErrorMessages { + /** Base for road building / maintaining errors */ + ERR_ROAD_BASE = AIError::ERR_CAT_ROAD << AIError::ERR_CAT_BIT_SIZE, + + /** Road works are in progress */ + ERR_ROAD_WORKS_IN_PROGRESS, // [STR_ROAD_WORKS_IN_PROGRESS] + + /** Drive through is in the wrong direction */ + ERR_ROAD_DRIVE_THROUGH_WRONG_DIRECTION, // [STR_DRIVE_THROUGH_ERROR_DIRECTION] + + /** Drive through roads can't be build on town owned roads */ + ERR_ROAD_CANNOT_BUILD_ON_TOWN_ROAD, // [STR_DRIVE_THROUGH_ERROR_ON_TOWN_ROAD] + + + /** One way roads can't have junctions */ + ERR_ROAD_ONE_WAY_ROADS_CANNOT_HAVE_JUNCTIONS, // [STR_ERR_ONEWAY_ROADS_CAN_T_HAVE_JUNCTION] + }; + + /** + * Types of road known to the game. + */ + enum RoadType { + /* Values are important, as they represent the internal state of the game. */ + ROADTYPE_ROAD = 0, //!< Build road objects. + ROADTYPE_TRAM = 1, //!< Build tram objects. + + ROADTYPE_INVALID = -1, //!< Invalid RoadType. + }; + + /** + * Checks whether the given tile is actually a tile with road that can be + * used to traverse a tile. This excludes road depots and 'normal' road + * stations, but includes drive through stations. + * @param tile The tile to check. + * @pre AIMap::IsValidTile(tile). + * @return True if and only if the tile has road. + */ + static bool IsRoadTile(TileIndex tile); + + /** + * Checks whether the given tile is actually a tile with a road depot. + * @param tile The tile to check. + * @pre AIMap::IsValidTile(tile). + * @return True if and only if the tile has a road depot. + */ + static bool IsRoadDepotTile(TileIndex tile); + + /** + * Checks whether the given tile is actually a tile with a road station. + * @param tile The tile to check. + * @pre AIMap::IsValidTile(tile). + * @return True if and only if the tile has a road station. + */ + static bool IsRoadStationTile(TileIndex tile); + + /** + * Checks whether the given tile is actually a tile with a drive through + * road station. + * @param tile The tile to check. + * @pre AIMap::IsValidTile(tile). + * @return True if and only if the tile has a drive through road station. + */ + static bool IsDriveThroughRoadStationTile(TileIndex tile); + + /** + * Check if a given RoadType is available. + * @param road_type The RoadType to check for. + * @return True if this RoadType can be used. + */ + static bool IsRoadTypeAvailable(RoadType road_type); + + /** + * Get the current RoadType set for all AIRoad functions. + * @return The RoadType currently set. + */ + static RoadType GetCurrentRoadType(); + + /** + * Set the RoadType for all further AIRoad functions. + * @param road_type The RoadType to set. + */ + static void SetCurrentRoadType(RoadType road_type); + + /** + * Check if a given tile has RoadType. + * @param tile The tile to check. + * @param road_type The RoadType to check for. + * @pre AIMap::IsValidTile(tile). + * @pre IsRoadTypeAvailable(road_type). + * @return True if the tile contains a RoadType object. + */ + static bool HasRoadType(TileIndex tile, RoadType road_type); + + /** + * Checks whether the given tiles are directly connected, i.e. whether + * a road vehicle can travel from the center of the first tile to the + * center of the second tile. + * @param tile_from The source tile. + * @param tile_to The destination tile. + * @pre AIMap::IsValidTile(tile_from). + * @pre AIMap::IsValidTile(tile_to). + * @pre 'tile_from' and 'tile_to' are directly neighbouring tiles. + * @return True if and only if a road vehicle can go from tile_from to tile_to. + */ + static bool AreRoadTilesConnected(TileIndex tile_from, TileIndex tile_to); + + /** + * Lookup function for building road parts independend on whether the + * "building on slopes" setting is enabled or not. + * This implementation can be used for abstract reasoning about a tile as + * it needs the slope and existing road parts of the tile as information. + * @param slope The slope of the tile to examine. + * @param existing An array with the existing neighbours in the same format + * as "start" and "end", e.g. AIMap.GetTileIndex(0, 1). + * As a result of this all values of the existing array + * must be of type integer. + * @param start The tile from where the 'tile to be considered' will be + * entered. This is a relative tile, so valid parameters are: + * AIMap.GetTileIndex(0, 1), AIMap.GetTileIndex(0, -1), + * AIMap.GetTileIndex(1, 0) and AIMap.GetTileIndex(-1, 0). + * @param end The tile from where the 'tile to be considered' will be + * exited. This is a relative tile, sovalid parameters are: + * AIMap.GetTileIndex(0, 1), AIMap.GetTileIndex(0, -1), + * AIMap.GetTileIndex(1, 0) and AIMap.GetTileIndex(-1, 0). + * @pre start != end. + * @pre slope must be a valid slope, i.e. one specified in AITile::Slope. + * @note Passing data that would be invalid in-game, e.g. existing containing + * road parts that can not be build on a tile with the given slope, + * does not necessarily means that -1 is returned, i.e. not all + * preconditions written here or assumed by the game are extensively + * checked to make sure the data entered is valid. + * @return 0 when the build parts do not connect, 1 when they do connect once + * they are build or 2 when building the first part automatically + * builds the second part. -1 means the preconditions are not met. + */ + static int32 CanBuildConnectedRoadParts(AITile::Slope slope, struct Array *existing, TileIndex start, TileIndex end); + + /** + * Lookup function for building road parts independend on whether the + * "building on slopes" setting is enabled or not. + * This implementation can be used for reasoning about an existing tile. + * @param tile The the tile to examine. + * @param start The tile from where "tile" will be entered. + * @param end The tile from where "tile" will be exited. + * @pre start != end. + * @pre tile != start. + * @pre tile != end. + * @pre AIMap.IsValidTile(tile). + * @pre AIMap.IsValidTile(start). + * @pre AIMap.IsValidTile(end). + * @pre AIMap.GetDistanceManhattanToTile(tile, start) == 1. + * @pre AIMap.GetDistanceManhattanToTile(tile, end) == 1. + * @return 0 when the build parts do not connect, 1 when they do connect once + * they are build or 2 when building the first part automatically + * builds the second part. -1 means the preconditions are not met. + */ + static int32 CanBuildConnectedRoadPartsHere(TileIndex tile, TileIndex start, TileIndex end); + + /** + * Count how many neighbours are road. + * @param tile The tile to check on. + * @pre AIMap::IsValidTile(tile). + * @return 0 means no neighbour road; max value is 4. + */ + static int32 GetNeighbourRoadCount(TileIndex tile); + + /** + * Gets the tile in front of a road depot. + * @param depot The road depot tile. + * @pre IsRoadDepotTile(depot). + * @return The tile in front of the depot. + */ + static TileIndex GetRoadDepotFrontTile(TileIndex depot); + + /** + * Gets the tile in front of a road station. + * @param station The road station tile. + * @pre IsRoadStationTile(station). + * @return The tile in front of the road station. + */ + static TileIndex GetRoadStationFrontTile(TileIndex station); + + /** + * Gets the tile at the back of a drive through road station. + * So, one side of the drive through station is retrieved with + * GetTileInFrontOfStation, the other with this function. + * @param station The road station tile. + * @pre IsDriveThroughRoadStationTile(station). + * @return The tile at the back of the drive through road station. + */ + static TileIndex GetDriveThroughBackTile(TileIndex station); + + /** + * Builds a road from the center of tile start to the center of tile end. + * @param start The start tile of the road. + * @param end The end tile of the road. + * @pre 'start' is not equal to 'end'. + * @pre AIMap::IsValidTile(start). + * @pre AIMap::IsValidTile(end). + * @pre 'start' and 'end' are in a straight line, i.e. + * AIMap::GetTileX(start) == AIMap::GetTileX(end) or + * AIMap::GetTileY(start) == AIMap::GetTileY(end). + * @exception AIError::ERR_ALREADY_BUILT + * @exception AIError::ERR_LAND_SLOPED_WRONG + * @exception AIError::ERR_AREA_NOT_CLEAR + * @exception AIRoad::ERR_ROAD_ONE_WAY_ROADS_CANNOT_HAVE_JUNCTIONS + * @exception AIRoad::ERR_ROAD_WORKS_IN_PROGRESS + * @exception AIError::ERR_VEHICLE_IN_THE_WAY + * @return Whether the road has been/can be build or not. + */ + static bool BuildRoad(TileIndex start, TileIndex end); + + /** + * Builds a one-way road from the center of tile start to the center + * of tile end. If the road already exists, it is made one-way road. + * If the road already exists and is already one-way in this direction, + * the road is made two-way again. If the road already exists but is + * one-way in the other direction, it's made a 'no'-way road (it's + * forbidden to enter the tile from any direction). + * @param start The start tile of the road. + * @param end The end tile of the road. + * @pre 'start' is not equal to 'end'. + * @pre AIMap::IsValidTile(start). + * @pre AIMap::IsValidTile(end). + * @pre 'start' and 'end' are in a straight line, i.e. + * AIMap::GetTileX(start) == AIMap::GetTileX(end) or + * AIMap::GetTileY(start) == AIMap::GetTileY(end). + * @pre GetCurrentRoadType() == ROADTYPE_ROAD. + * @exception AIError::ERR_ALREADY_BUILT + * @exception AIError::ERR_LAND_SLOPED_WRONG + * @exception AIError::ERR_AREA_NOT_CLEAR + * @exception AIRoad::ERR_ROAD_ONE_WAY_ROADS_CANNOT_HAVE_JUNCTIONS + * @exception AIRoad::ERR_ROAD_WORKS_IN_PROGRESS + * @exception AIError::ERR_VEHICLE_IN_THE_WAY + * @return Whether the road has been/can be build or not. + */ + static bool BuildOneWayRoad(TileIndex start, TileIndex end); + + /** + * Builds a road from the edge of tile start to the edge of tile end (both + * included). + * @param start The start tile of the road. + * @param end The end tile of the road. + * @pre 'start' is not equal to 'end'. + * @pre AIMap::IsValidTile(start). + * @pre AIMap::IsValidTile(end). + * @pre 'start' and 'end' are in a straight line, i.e. + * AIMap::GetTileX(start) == AIMap::GetTileX(end) or + * AIMap::GetTileY(start) == AIMap::GetTileY(end). + * @exception AIError::ERR_ALREADY_BUILT + * @exception AIError::ERR_LAND_SLOPED_WRONG + * @exception AIError::ERR_AREA_NOT_CLEAR + * @exception AIRoad::ERR_ROAD_ONE_WAY_ROADS_CANNOT_HAVE_JUNCTIONS + * @exception AIRoad::ERR_ROAD_WORKS_IN_PROGRESS + * @exception AIError::ERR_VEHICLE_IN_THE_WAY + * @return Whether the road has been/can be build or not. + */ + static bool BuildRoadFull(TileIndex start, TileIndex end); + + /** + * Builds a one-way road from the edge of tile start to the edge of tile end + * (both included). If the road already exists, it is made one-way road. + * If the road already exists and is already one-way in this direction, + * the road is made two-way again. If the road already exists but is + * one-way in the other direction, it's made a 'no'-way road (it's + * forbidden to enter the tile from any direction). + * @param start The start tile of the road. + * @param start The start tile of the road. + * @param end The end tile of the road. + * @pre 'start' is not equal to 'end'. + * @pre AIMap::IsValidTile(start). + * @pre AIMap::IsValidTile(end). + * @pre 'start' and 'end' are in a straight line, i.e. + * AIMap::GetTileX(start) == AIMap::GetTileX(end) or + * AIMap::GetTileY(start) == AIMap::GetTileY(end). + * @pre GetCurrentRoadType() == ROADTYPE_ROAD. + * @exception AIError::ERR_ALREADY_BUILT + * @exception AIError::ERR_LAND_SLOPED_WRONG + * @exception AIError::ERR_AREA_NOT_CLEAR + * @exception AIRoad::ERR_ROAD_ONE_WAY_ROADS_CANNOT_HAVE_JUNCTIONS + * @exception AIRoad::ERR_ROAD_WORKS_IN_PROGRESS + * @exception AIError::ERR_VEHICLE_IN_THE_WAY + * @return Whether the road has been/can be build or not. + */ + static bool BuildOneWayRoadFull(TileIndex start, TileIndex end); + + /** + * Builds a road depot. + * @param tile Place to build the depot. + * @param front The tile exactly in front of the depot. + * @pre AIMap::IsValidTile(tile). + * @pre AIMap::IsValidTile(front). + * @pre 'tile' is not equal to 'front', but in a straight line of it. + * @exception AIError::ERR_FLAT_LAND_REQUIRED + * @exception AIError::ERR_AREA_NOT_CLEAR + * @return Whether the road depot has been/can be build or not. + */ + static bool BuildRoadDepot(TileIndex tile, TileIndex front); + + /** + * Builds a road bus or truck station. + * @param tile Place to build the depot. + * @param front The tile exactly in front of the station. + * For drive-through stations either entrance side can be used. + * @param truck Whether to build a truck (true) or bus (false) station. + * @param drive_through Whether to make the station drive through or not. + * @param join_adjacent When building next to an other station, don't create a new station when this flag is true. + * @pre AIMap::IsValidTile(tile). + * @pre AIMap::IsValidTile(front). + * @pre 'tile' is not equal to 'front', but in a straight line of it. + * @exception AIError::ERR_OWNED_BY_ANOTHER_COMPANY + * @exception AIError::ERR_AREA_NOT_CLEAR + * @exception AIError::ERR_FLAT_LAND_REQUIRED + * @exception AIRoad::ERR_ROAD_DRIVE_THROUGH_WRONG_DIRECTION + * @exception AIRoad::ERR_ROAD_CANNOT_BUILD_ON_TOWN_ROAD + * @exception AIError:ERR_VEHICLE_IN_THE_WAY + * @exception AIStation::ERR_STATION_TOO_CLOSE_TO_ANOTHER_STATION + * @exception AIStation::ERR_STATION_TOO_MANY_STATIONS + * @exception AIStation::ERR_STATION_TOO_MANY_STATIONS_IN_TOWN + * @return Whether the station has been/can be build or not. + */ + static bool BuildRoadStation(TileIndex tile, TileIndex front, bool truck, bool drive_through, bool join_adjacent); + + /** + * Removes a road from the center of tile start to the center of tile end. + * @param start The start tile of the road. + * @param end The end tile of the road. + * @pre AIMap::IsValidTile(start). + * @pre AIMap::IsValidTile(end). + * @pre 'start' and 'end' are in a straight line, i.e. + * AIMap::GetTileX(start) == AIMap::GetTileX(end) or + * AIMap::GetTileY(start) == AIMap::GetTileY(end). + * @exception AIError::ERR_OWNED_BY_ANOTHER_COMPANY + * @exception AIError::ERR_VEHICLE_IN_THE_WAY + * @exception AIRoad::ERR_ROAD_WORKS_IN_PROGRESS + * @return Whether the road has been/can be removed or not. + */ + static bool RemoveRoad(TileIndex start, TileIndex end); + + /** + * Removes a road from the edge of tile start to the edge of tile end (both + * included). + * @param start The start tile of the road. + * @param end The end tile of the road. + * @pre AIMap::IsValidTile(start). + * @pre AIMap::IsValidTile(end). + * @pre 'start' and 'end' are in a straight line, i.e. + * AIMap::GetTileX(start) == AIMap::GetTileX(end) or + * AIMap::GetTileY(start) == AIMap::GetTileY(end). + * @exception AIError::ERR_OWNED_BY_ANOTHER_COMPANY + * @exception AIError::ERR_VEHICLE_IN_THE_WAY + * @exception AIRoad::ERR_ROAD_WORKS_IN_PROGRESS + * @return Whether the road has been/can be removed or not. + */ + static bool RemoveRoadFull(TileIndex start, TileIndex end); + + /** + * Removes a road depot. + * @param tile Place to remove the depot from. + * @pre AIMap::IsValidTile(tile). + * @pre Tile is a road depot. + * @exception AIError::ERR_OWNED_BY_ANOTHER_COMPANY + * @exception AIError::ERR_VEHICLE_IN_THE_WAY + * @return Whether the road depot has been/can be removed or not. + */ + static bool RemoveRoadDepot(TileIndex tile); + + /** + * Removes a road bus or truck station. + * @param tile Place to remove the station from. + * @pre AIMap::IsValidTile(tile). + * @pre Tile is a road station. + * @exception AIError::ERR_OWNED_BY_ANOTHER_COMPANY + * @exception AIError::ERR_VEHICLE_IN_THE_WAY + * @return Whether the station has been/can be removed or not. + */ + static bool RemoveRoadStation(TileIndex tile); + +private: + + /** + * Internal function used by Build(OneWay)Road(Full). + */ + static bool _BuildRoadInternal(TileIndex start, TileIndex end, bool one_way, bool full); +}; + +#endif /* AI_ROAD_HPP */ diff --git a/src/ai/api/ai_road.hpp.sq b/src/ai/api/ai_road.hpp.sq new file mode 100644 index 000000000..6439ae193 --- /dev/null +++ b/src/ai/api/ai_road.hpp.sq @@ -0,0 +1,73 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_road.hpp" + +namespace SQConvert { + /* Allow enums to be used as Squirrel parameters */ + template <> AIRoad::ErrorMessages GetParam(ForceType<AIRoad::ErrorMessages>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AIRoad::ErrorMessages)tmp; } + template <> int Return<AIRoad::ErrorMessages>(HSQUIRRELVM vm, AIRoad::ErrorMessages res) { sq_pushinteger(vm, (int32)res); return 1; } + template <> AIRoad::RoadType GetParam(ForceType<AIRoad::RoadType>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AIRoad::RoadType)tmp; } + template <> int Return<AIRoad::RoadType>(HSQUIRRELVM vm, AIRoad::RoadType res) { sq_pushinteger(vm, (int32)res); return 1; } + + /* Allow AIRoad to be used as Squirrel parameter */ + template <> AIRoad *GetParam(ForceType<AIRoad *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIRoad *)instance; } + template <> AIRoad &GetParam(ForceType<AIRoad &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIRoad *)instance; } + template <> const AIRoad *GetParam(ForceType<const AIRoad *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIRoad *)instance; } + template <> const AIRoad &GetParam(ForceType<const AIRoad &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIRoad *)instance; } + template <> int Return<AIRoad *>(HSQUIRRELVM vm, AIRoad *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIRoad", res, NULL, DefSQDestructorCallback<AIRoad>); return 1; } +}; // namespace SQConvert + +void SQAIRoad_Register(Squirrel *engine) { + DefSQClass <AIRoad> SQAIRoad("AIRoad"); + SQAIRoad.PreRegister(engine); + SQAIRoad.AddConstructor<void (AIRoad::*)(), 1>(engine, "x"); + + SQAIRoad.DefSQConst(engine, AIRoad::ERR_ROAD_BASE, "ERR_ROAD_BASE"); + SQAIRoad.DefSQConst(engine, AIRoad::ERR_ROAD_WORKS_IN_PROGRESS, "ERR_ROAD_WORKS_IN_PROGRESS"); + SQAIRoad.DefSQConst(engine, AIRoad::ERR_ROAD_DRIVE_THROUGH_WRONG_DIRECTION, "ERR_ROAD_DRIVE_THROUGH_WRONG_DIRECTION"); + SQAIRoad.DefSQConst(engine, AIRoad::ERR_ROAD_CANNOT_BUILD_ON_TOWN_ROAD, "ERR_ROAD_CANNOT_BUILD_ON_TOWN_ROAD"); + SQAIRoad.DefSQConst(engine, AIRoad::ERR_ROAD_ONE_WAY_ROADS_CANNOT_HAVE_JUNCTIONS, "ERR_ROAD_ONE_WAY_ROADS_CANNOT_HAVE_JUNCTIONS"); + SQAIRoad.DefSQConst(engine, AIRoad::ROADTYPE_ROAD, "ROADTYPE_ROAD"); + SQAIRoad.DefSQConst(engine, AIRoad::ROADTYPE_TRAM, "ROADTYPE_TRAM"); + SQAIRoad.DefSQConst(engine, AIRoad::ROADTYPE_INVALID, "ROADTYPE_INVALID"); + + AIError::RegisterErrorMap(STR_ROAD_WORKS_IN_PROGRESS, AIRoad::ERR_ROAD_WORKS_IN_PROGRESS); + AIError::RegisterErrorMap(STR_DRIVE_THROUGH_ERROR_DIRECTION, AIRoad::ERR_ROAD_DRIVE_THROUGH_WRONG_DIRECTION); + AIError::RegisterErrorMap(STR_DRIVE_THROUGH_ERROR_ON_TOWN_ROAD, AIRoad::ERR_ROAD_CANNOT_BUILD_ON_TOWN_ROAD); + AIError::RegisterErrorMap(STR_ERR_ONEWAY_ROADS_CAN_T_HAVE_JUNCTION, AIRoad::ERR_ROAD_ONE_WAY_ROADS_CANNOT_HAVE_JUNCTIONS); + + AIError::RegisterErrorMapString(AIRoad::ERR_ROAD_WORKS_IN_PROGRESS, "ERR_ROAD_WORKS_IN_PROGRESS"); + AIError::RegisterErrorMapString(AIRoad::ERR_ROAD_DRIVE_THROUGH_WRONG_DIRECTION, "ERR_ROAD_DRIVE_THROUGH_WRONG_DIRECTION"); + AIError::RegisterErrorMapString(AIRoad::ERR_ROAD_CANNOT_BUILD_ON_TOWN_ROAD, "ERR_ROAD_CANNOT_BUILD_ON_TOWN_ROAD"); + AIError::RegisterErrorMapString(AIRoad::ERR_ROAD_ONE_WAY_ROADS_CANNOT_HAVE_JUNCTIONS, "ERR_ROAD_ONE_WAY_ROADS_CANNOT_HAVE_JUNCTIONS"); + + SQAIRoad.DefSQStaticMethod(engine, &AIRoad::GetClassName, "GetClassName", 1, "x"); + SQAIRoad.DefSQStaticMethod(engine, &AIRoad::IsRoadTile, "IsRoadTile", 2, "xi"); + SQAIRoad.DefSQStaticMethod(engine, &AIRoad::IsRoadDepotTile, "IsRoadDepotTile", 2, "xi"); + SQAIRoad.DefSQStaticMethod(engine, &AIRoad::IsRoadStationTile, "IsRoadStationTile", 2, "xi"); + SQAIRoad.DefSQStaticMethod(engine, &AIRoad::IsDriveThroughRoadStationTile, "IsDriveThroughRoadStationTile", 2, "xi"); + SQAIRoad.DefSQStaticMethod(engine, &AIRoad::IsRoadTypeAvailable, "IsRoadTypeAvailable", 2, "xi"); + SQAIRoad.DefSQStaticMethod(engine, &AIRoad::GetCurrentRoadType, "GetCurrentRoadType", 1, "x"); + SQAIRoad.DefSQStaticMethod(engine, &AIRoad::SetCurrentRoadType, "SetCurrentRoadType", 2, "xi"); + SQAIRoad.DefSQStaticMethod(engine, &AIRoad::HasRoadType, "HasRoadType", 3, "xii"); + SQAIRoad.DefSQStaticMethod(engine, &AIRoad::AreRoadTilesConnected, "AreRoadTilesConnected", 3, "xii"); + SQAIRoad.DefSQStaticMethod(engine, &AIRoad::CanBuildConnectedRoadParts, "CanBuildConnectedRoadParts", 5, "xiaii"); + SQAIRoad.DefSQStaticMethod(engine, &AIRoad::CanBuildConnectedRoadPartsHere, "CanBuildConnectedRoadPartsHere", 4, "xiii"); + SQAIRoad.DefSQStaticMethod(engine, &AIRoad::GetNeighbourRoadCount, "GetNeighbourRoadCount", 2, "xi"); + SQAIRoad.DefSQStaticMethod(engine, &AIRoad::GetRoadDepotFrontTile, "GetRoadDepotFrontTile", 2, "xi"); + SQAIRoad.DefSQStaticMethod(engine, &AIRoad::GetRoadStationFrontTile, "GetRoadStationFrontTile", 2, "xi"); + SQAIRoad.DefSQStaticMethod(engine, &AIRoad::GetDriveThroughBackTile, "GetDriveThroughBackTile", 2, "xi"); + SQAIRoad.DefSQStaticMethod(engine, &AIRoad::BuildRoad, "BuildRoad", 3, "xii"); + SQAIRoad.DefSQStaticMethod(engine, &AIRoad::BuildOneWayRoad, "BuildOneWayRoad", 3, "xii"); + SQAIRoad.DefSQStaticMethod(engine, &AIRoad::BuildRoadFull, "BuildRoadFull", 3, "xii"); + SQAIRoad.DefSQStaticMethod(engine, &AIRoad::BuildOneWayRoadFull, "BuildOneWayRoadFull", 3, "xii"); + SQAIRoad.DefSQStaticMethod(engine, &AIRoad::BuildRoadDepot, "BuildRoadDepot", 3, "xii"); + SQAIRoad.DefSQStaticMethod(engine, &AIRoad::BuildRoadStation, "BuildRoadStation", 6, "xiibbb"); + SQAIRoad.DefSQStaticMethod(engine, &AIRoad::RemoveRoad, "RemoveRoad", 3, "xii"); + SQAIRoad.DefSQStaticMethod(engine, &AIRoad::RemoveRoadFull, "RemoveRoadFull", 3, "xii"); + SQAIRoad.DefSQStaticMethod(engine, &AIRoad::RemoveRoadDepot, "RemoveRoadDepot", 2, "xi"); + SQAIRoad.DefSQStaticMethod(engine, &AIRoad::RemoveRoadStation, "RemoveRoadStation", 2, "xi"); + + SQAIRoad.PostRegister(engine); +} diff --git a/src/ai/api/ai_sign.cpp b/src/ai/api/ai_sign.cpp new file mode 100644 index 000000000..561b5c857 --- /dev/null +++ b/src/ai/api/ai_sign.cpp @@ -0,0 +1,73 @@ +/* $Id$ */ + +/** @file ai_sign.cpp Implementation of AISign. */ + +#include "ai_sign.hpp" +#include "table/strings.h" +#include "../ai_instance.hpp" +#include "../../openttd.h" +#include "../../command_func.h" +#include "../../core/alloc_func.hpp" +#include "../../signs_base.h" +#include "../../string_func.h" +#include "../../strings_func.h" +#include "../../tile_map.h" +#include "../../company_func.h" + +/* static */ SignID AISign::GetMaxSignID() +{ + return ::GetMaxSignIndex(); +} + +/* static */ bool AISign::IsValidSign(SignID sign_id) +{ + return ::IsValidSignID(sign_id) && ::GetSign(sign_id)->owner == _current_company; +} + +/* static */ bool AISign::SetName(SignID sign_id, const char *name) +{ + EnforcePrecondition(false, IsValidSign(sign_id)); + EnforcePrecondition(false, !::StrEmpty(name)); + EnforcePreconditionCustomError(false, ::strlen(name) < MAX_LENGTH_SIGN_NAME_BYTES, AIError::ERR_PRECONDITION_STRING_TOO_LONG); + + return AIObject::DoCommand(0, sign_id, 0, CMD_RENAME_SIGN, name); +} + +/* static */ const char *AISign::GetName(SignID sign_id) +{ + if (!IsValidSign(sign_id)) return NULL; + + static const int len = 64; + char *sign_name = MallocT<char>(len); + + ::SetDParam(0, sign_id); + ::GetString(sign_name, STR_SIGN_NAME, &sign_name[len - 1]); + + return sign_name; +} + +/* static */ TileIndex AISign::GetLocation(SignID sign_id) +{ + if (!IsValidSign(sign_id)) return INVALID_TILE; + + const Sign *sign = ::GetSign(sign_id); + return ::TileVirtXY(sign->x, sign->y); +} + +/* static */ bool AISign::RemoveSign(SignID sign_id) +{ + EnforcePrecondition(false, IsValidSign(sign_id)); + return AIObject::DoCommand(0, sign_id, 0, CMD_RENAME_SIGN, ""); +} + +/* static */ SignID AISign::BuildSign(TileIndex location, const char *text) +{ + EnforcePrecondition(INVALID_SIGN, ::IsValidTile(location)); + EnforcePrecondition(INVALID_SIGN, !::StrEmpty(text)); + EnforcePreconditionCustomError(false, ::strlen(text) < MAX_LENGTH_SIGN_NAME_BYTES, AIError::ERR_PRECONDITION_STRING_TOO_LONG); + + if (!AIObject::DoCommand(location, 0, 0, CMD_PLACE_SIGN, text, &AIInstance::DoCommandReturnSignID)) return INVALID_SIGN; + + /* In case of test-mode, we return SignID 0 */ + return 0; +} diff --git a/src/ai/api/ai_sign.hpp b/src/ai/api/ai_sign.hpp new file mode 100644 index 000000000..8bf7e0b79 --- /dev/null +++ b/src/ai/api/ai_sign.hpp @@ -0,0 +1,96 @@ +/* $Id$ */ + +/** @file ai_sign.hpp Everything to query and build signs. */ + +#ifndef AI_SIGN_HPP +#define AI_SIGN_HPP + +#include "ai_object.hpp" +#include "ai_error.hpp" +#include "ai_company.hpp" + +/** + * Class that handles all sign related functions. + */ +class AISign : public AIObject { +public: + static const char *GetClassName() { return "AISign"; } + + /** + * All sign related error messages. + */ + enum ErrorMessages { + + /** Base for sign building related errors */ + ERR_SIGN_BASE = AIError::ERR_CAT_SIGN << AIError::ERR_CAT_BIT_SIZE, + + /** Too many signs have been placed */ + ERR_SIGN_TOO_MANY_SIGNS, // [STR_2808_TOO_MANY_SIGNS] + }; + + /** + * Gets the maximum sign index; there are no valid signs with a higher index. + * @return The maximum sign index. + * @post Return value is always non-negative. + */ + static SignID GetMaxSignID(); + + /** + * Checks whether the given sign index is valid. + * @param sign_id The index to check. + * @return True if and only if the sign is valid. + */ + static bool IsValidSign(SignID sign_id); + + /** + * Set the name of a sign. + * @param sign_id The sign to set the name for. + * @param name The name for the sign. + * @pre IsValidSign(sign_id). + * @pre 'name' must have at least one character. + * @pre 'name' must have at most 30 characters. + * @exception AIError::ERR_NAME_IS_NOT_UNIQUE + * @return True if and only if the name was changed. + */ + static bool SetName(SignID sign_id, const char *name); + + /** + * Get the name of the sign. + * @param sign_id The sign to get the name of. + * @pre IsValidSign(sign_id). + * @return The name of the sign. + */ + static const char *GetName(SignID sign_id); + + /** + * Gets the location of the sign. + * @param sign_id The sign to get the location of. + * @pre IsValidSign(sign_id). + * @return The location of the sign. + */ + static TileIndex GetLocation(SignID sign_id); + + /** + * Builds a sign on the map. + * @param location The place to build the sign. + * @param text The text to place on the sign. + * @pre AIMap::IsValidTile(location). + * @pre 'text' must have at least one character. + * @pre 'text' must have at most 30 characters. + * @exception AISign::ERR_SIGN_TOO_MANY_SIGNS + * @return The SignID of the build sign (use IsValidSign() to check for validity). + * In test-mode it returns 0 if successful, or any other value to indicate + * failure. + */ + static SignID BuildSign(TileIndex location, const char *text); + + /** + * Removes a sign from the map. + * @param sign_id The sign to remove. + * @pre IsValidSign(sign_id). + * @return True if and only if the sign has been removed. + */ + static bool RemoveSign(SignID sign_id); +}; + +#endif /* AI_SIGN_HPP */ diff --git a/src/ai/api/ai_sign.hpp.sq b/src/ai/api/ai_sign.hpp.sq new file mode 100644 index 000000000..4a46e1bd8 --- /dev/null +++ b/src/ai/api/ai_sign.hpp.sq @@ -0,0 +1,41 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_sign.hpp" + +namespace SQConvert { + /* Allow enums to be used as Squirrel parameters */ + template <> AISign::ErrorMessages GetParam(ForceType<AISign::ErrorMessages>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AISign::ErrorMessages)tmp; } + template <> int Return<AISign::ErrorMessages>(HSQUIRRELVM vm, AISign::ErrorMessages res) { sq_pushinteger(vm, (int32)res); return 1; } + + /* Allow AISign to be used as Squirrel parameter */ + template <> AISign *GetParam(ForceType<AISign *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AISign *)instance; } + template <> AISign &GetParam(ForceType<AISign &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AISign *)instance; } + template <> const AISign *GetParam(ForceType<const AISign *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AISign *)instance; } + template <> const AISign &GetParam(ForceType<const AISign &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AISign *)instance; } + template <> int Return<AISign *>(HSQUIRRELVM vm, AISign *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AISign", res, NULL, DefSQDestructorCallback<AISign>); return 1; } +}; // namespace SQConvert + +void SQAISign_Register(Squirrel *engine) { + DefSQClass <AISign> SQAISign("AISign"); + SQAISign.PreRegister(engine); + SQAISign.AddConstructor<void (AISign::*)(), 1>(engine, "x"); + + SQAISign.DefSQConst(engine, AISign::ERR_SIGN_BASE, "ERR_SIGN_BASE"); + SQAISign.DefSQConst(engine, AISign::ERR_SIGN_TOO_MANY_SIGNS, "ERR_SIGN_TOO_MANY_SIGNS"); + + AIError::RegisterErrorMap(STR_2808_TOO_MANY_SIGNS, AISign::ERR_SIGN_TOO_MANY_SIGNS); + + AIError::RegisterErrorMapString(AISign::ERR_SIGN_TOO_MANY_SIGNS, "ERR_SIGN_TOO_MANY_SIGNS"); + + SQAISign.DefSQStaticMethod(engine, &AISign::GetClassName, "GetClassName", 1, "x"); + SQAISign.DefSQStaticMethod(engine, &AISign::GetMaxSignID, "GetMaxSignID", 1, "x"); + SQAISign.DefSQStaticMethod(engine, &AISign::IsValidSign, "IsValidSign", 2, "xi"); + SQAISign.DefSQStaticMethod(engine, &AISign::SetName, "SetName", 3, "xis"); + SQAISign.DefSQStaticMethod(engine, &AISign::GetName, "GetName", 2, "xi"); + SQAISign.DefSQStaticMethod(engine, &AISign::GetLocation, "GetLocation", 2, "xi"); + SQAISign.DefSQStaticMethod(engine, &AISign::BuildSign, "BuildSign", 3, "xis"); + SQAISign.DefSQStaticMethod(engine, &AISign::RemoveSign, "RemoveSign", 2, "xi"); + + SQAISign.PostRegister(engine); +} diff --git a/src/ai/api/ai_station.cpp b/src/ai/api/ai_station.cpp new file mode 100644 index 000000000..ab171a307 --- /dev/null +++ b/src/ai/api/ai_station.cpp @@ -0,0 +1,146 @@ +/* $Id$ */ + +/** @file ai_station.cpp Implementation of AIStation. */ + +#include "ai_station.hpp" +#include "ai_cargo.hpp" +#include "ai_map.hpp" +#include "ai_town.hpp" +#include "../../openttd.h" +#include "../../command_func.h" +#include "../../debug.h" +#include "../../station_map.h" +#include "../../variables.h" +#include "../../string_func.h" +#include "../../strings_func.h" +#include "../../core/alloc_func.hpp" +#include "../../company_func.h" +#include "../../settings_type.h" +#include "../../town.h" +#include "table/strings.h" + +/* static */ bool AIStation::IsValidStation(StationID station_id) +{ + return ::IsValidStationID(station_id) && ::GetStation(station_id)->owner == _current_company; +} + +/* static */ StationID AIStation::GetStationID(TileIndex tile) +{ + if (!::IsTileType(tile, MP_STATION)) return INVALID_STATION; + return ::GetStationIndex(tile); +} + +/* static */ const char *AIStation::GetName(StationID station_id) +{ + if (!IsValidStation(station_id)) return NULL; + + static const int len = 64; + char *station_name = MallocT<char>(len); + + ::SetDParam(0, GetStation(station_id)->index); + ::GetString(station_name, STR_STATION, &station_name[len - 1]); + return station_name; +} + +/* static */ bool AIStation::SetName(StationID station_id, const char *name) +{ + EnforcePrecondition(false, IsValidStation(station_id)); + EnforcePrecondition(false, !::StrEmpty(name)); + EnforcePreconditionCustomError(false, ::strlen(name) < MAX_LENGTH_STATION_NAME_BYTES, AIError::ERR_PRECONDITION_STRING_TOO_LONG); + + return AIObject::DoCommand(0, station_id, 0, CMD_RENAME_STATION, name); +} + +/* static */ TileIndex AIStation::GetLocation(StationID station_id) +{ + if (!IsValidStation(station_id)) return INVALID_TILE; + + return ::GetStation(station_id)->xy; +} + +/* static */ int32 AIStation::GetCargoWaiting(StationID station_id, CargoID cargo_id) +{ + if (!IsValidStation(station_id)) return -1; + if (!AICargo::IsValidCargo(cargo_id)) return -1; + + return ::GetStation(station_id)->goods[cargo_id].cargo.Count(); +} + +/* static */ int32 AIStation::GetCargoRating(StationID station_id, CargoID cargo_id) +{ + if (!IsValidStation(station_id)) return -1; + if (!AICargo::IsValidCargo(cargo_id)) return -1; + + return ::GetStation(station_id)->goods[cargo_id].rating * 101 >> 8; +} + +/* static */ int32 AIStation::GetCoverageRadius(AIStation::StationType station_type) +{ + if (station_type == STATION_AIRPORT) { + DEBUG(ai, 0, "GetCoverageRadius(): coverage radius of airports needs to be requested via AIAirport::GetAirportCoverageRadius(), as it requires AirportType"); + return -1; + } + if (CountBits(station_type) != 1) return -1; + if (!_settings_game.station.modified_catchment) return CA_UNMODIFIED; + + switch (station_type) { + case STATION_TRAIN: return CA_TRAIN; + case STATION_TRUCK_STOP: return CA_TRUCK; + case STATION_BUS_STOP: return CA_BUS; + case STATION_DOCK: return CA_DOCK; + default: return CA_NONE; + } +} + +/* static */ int32 AIStation::GetDistanceManhattanToTile(StationID station_id, TileIndex tile) +{ + if (!IsValidStation(station_id)) return -1; + + return AIMap::DistanceManhattan(tile, GetLocation(station_id)); +} + +/* static */ int32 AIStation::GetDistanceSquareToTile(StationID station_id, TileIndex tile) +{ + if (!IsValidStation(station_id)) return -1; + + return AIMap::DistanceSquare(tile, GetLocation(station_id)); +} + +/* static */ bool AIStation::IsWithinTownInfluence(StationID station_id, TownID town_id) +{ + if (!IsValidStation(station_id)) return false; + + return AITown::IsWithinTownInfluence(town_id, GetLocation(station_id)); +} + +/* static */ bool AIStation::HasStationType(StationID station_id, StationType station_type) +{ + if (!IsValidStation(station_id)) return false; + if (CountBits(station_type) != 1) return false; + + return (::GetStation(station_id)->facilities & station_type) != 0; +} + +/* static */ bool AIStation::HasRoadType(StationID station_id, AIRoad::RoadType road_type) +{ + if (!IsValidStation(station_id)) return false; + if (!AIRoad::IsRoadTypeAvailable(road_type)) return false; + + ::RoadTypes r = RoadTypeToRoadTypes((::RoadType)road_type); + + for (const RoadStop *rs = ::GetStation(station_id)->GetPrimaryRoadStop(ROADSTOP_BUS); rs != NULL; rs = rs->next) { + if ((::GetRoadTypes(rs->xy) & r) != 0) return true; + } + for (const RoadStop *rs = ::GetStation(station_id)->GetPrimaryRoadStop(ROADSTOP_TRUCK); rs != NULL; rs = rs->next) { + if ((::GetRoadTypes(rs->xy) & r) != 0) return true; + } + + return false; +} + +/* static */ TownID AIStation::GetNearestTown(StationID station_id) +{ + if (!IsValidStation(station_id)) return INVALID_TOWN; + + return ::GetStation(station_id)->town->index; +} diff --git a/src/ai/api/ai_station.hpp b/src/ai/api/ai_station.hpp new file mode 100644 index 000000000..6e263c912 --- /dev/null +++ b/src/ai/api/ai_station.hpp @@ -0,0 +1,184 @@ +/* $Id$ */ + +/** @file ai_station.hpp Everything to query and build stations. */ + +#ifndef AI_STATION_HPP +#define AI_STATION_HPP + +#include "ai_object.hpp" +#include "ai_error.hpp" +#include "ai_road.hpp" + +/** + * Class that handles all station related functions. + */ +class AIStation : public AIObject { +public: + static const char *GetClassName() { return "AIStation"; } + + /** + * All station related error messages. + */ + enum ErrorMessages { + /** Base for station related errors */ + ERR_STATION_BASE = AIError::ERR_CAT_STATION << AIError::ERR_CAT_BIT_SIZE, + + /** The station size exceeds the station spread */ + ERR_STATION_TOO_LARGE, // [STR_306C_STATION_TOO_SPREAD_OUT] + + /** The station is build too close to another station, airport or dock */ + ERR_STATION_TOO_CLOSE_TO_ANOTHER_STATION, // [STR_300D_TOO_CLOSE_TO_ANOTHER_AIRPORT, STR_3009_TOO_CLOSE_TO_ANOTHER_STATION, STR_304C_TOO_CLOSE_TO_ANOTHER_DOCK] + + /** There are too many stations, airports and docks in the game */ + ERR_STATION_TOO_MANY_STATIONS, // [STR_3008_TOO_MANY_STATIONS_LOADING, STR_TOO_MANY_TRUCK_STOPS, STR_TOO_MANY_BUS_STOPS] + + /** There are too many stations, airports of docks in a town */ + ERR_STATION_TOO_MANY_STATIONS_IN_TOWN, // [STR_3007_TOO_MANY_STATIONS_LOADING] + }; + + /** + * Type of stations known in the game. + */ + enum StationType { + /* Values are important, as they represent the internal state of the game. */ + STATION_TRAIN = 0x01, //!< Train station + STATION_TRUCK_STOP = 0x02, //!< Truck station + STATION_BUS_STOP = 0x04, //!< Bus station + STATION_AIRPORT = 0x08, //!< Airport + STATION_DOCK = 0x10, //!< Dock + STATION_ANY = 0x1F, //!< All station types + }; + + /** + * Checks whether the given station is valid and owned by you. + * @param station_id The station to check. + * @return True if and only if the station is valid. + */ + static bool IsValidStation(StationID station_id); + + /** + * Get the StationID of a tile, if there is a station. + * @param tile The tile to find the stationID of + * @return StationID of the station. + * @post Use IsValidStation() to see if the station is valid. + */ + static StationID GetStationID(TileIndex tile); + + /** + * Get the name of a station. + * @param station_id The station to get the name of. + * @pre IsValidStation(station_id). + * @return The name of the station. + */ + static const char *GetName(StationID station_id); + + /** + * Set the name this station. + * @param station_id The station to set the name of. + * @param name The new name of the station. + * @pre IsValidStation(station_id). + * @pre 'name' must have at least one character. + * @pre 'name' must have at most 30 characters. + * @exception AIError::ERR_NAME_IS_NOT_UNIQUE + * @return True if the name was changed. + */ + static bool SetName(StationID station_id, const char *name); + + /** + * Get the current location of a station. + * @param station_id The station to get the location of. + * @pre IsValidStation(station_id). + * @return The tile the station is currently on. + */ + static TileIndex GetLocation(StationID station_id); + + /** + * See how much cargo there is waiting on a station. + * @param station_id The station to get the cargo-waiting of. + * @param cargo_id The cargo to get the cargo-waiting of. + * @pre IsValidStation(station_id). + * @pre IsValidCargo(cargo_id). + * @return The amount of units waiting at the station. + */ + static int32 GetCargoWaiting(StationID station_id, CargoID cargo_id); + + /** + * See how high the rating is of a cargo on a station. + * @param station_id The station to get the cargo-rating of. + * @param cargo_id The cargo to get the cargo-rating of. + * @pre IsValidStation(station_id). + * @pre IsValidCargo(cargo_id). + * @return The rating in percent of the cargo on the station. + */ + static int32 GetCargoRating(StationID station_id, CargoID cargo_id); + + /** + * Get the coverage radius of this type of station. + * @param station_type The type of station. + * @return The radius in tiles. + */ + static int32 GetCoverageRadius(AIStation::StationType station_type); + + /** + * Get the manhattan distance from the tile to the AIStation::GetLocation() + * of the station. + * @param station_id The station to get the distance to. + * @param tile The tile to get the distance to. + * @pre IsValidStation(station_id). + * @return The distance between station and tile. + */ + static int32 GetDistanceManhattanToTile(StationID station_id, TileIndex tile); + + /** + * Get the square distance from the tile to the AIStation::GetLocation() + * of the station. + * @param station_id The station to get the distance to. + * @param tile The tile to get the distance to. + * @pre IsValidStation(station_id). + * @return The distance between station and tile. + */ + static int32 GetDistanceSquareToTile(StationID station_id, TileIndex tile); + + /** + * Find out if this station is within the rating influence of a town. + * Stations within the radius influence the rating of the town. + * @param station_id The station to check. + * @param town_id The town to check. + * @return True if the tile is within the rating influence of the town. + */ + static bool IsWithinTownInfluence(StationID station_id, TownID town_id); + + /** + * Check if any part of the station contains a station of the type + * StationType + * @param station_id The station to look at. + * @param station_type The StationType to look for. + * @return True if the station has a station part of the type StationType. + */ + static bool HasStationType(StationID station_id, StationType station_type); + + /** + * Check if any part of the station contains a station of the type + * RoadType. + * @param station_id The station to look at. + * @param road_type The RoadType to look for. + * @return True if the station has a station part of the type RoadType. + */ + static bool HasRoadType(StationID station_id, AIRoad::RoadType road_type); + + /** + * Get the town that was nearest to the given station when the station was built. + * @param station_id The station to look at. + * @return The TownID of the town whose center tile was closest to the station + * at the time the station was built. + * @note There is no guarantee that the station is even near the returned town + * nor that the returns town is closest to the station now. A station that was + * 'walked' to the other end of the map will still return the same town. Also, + * towns grow, towns change. So don't depend on this value too much. + */ + static TownID GetNearestTown(StationID station_id); +}; + +DECLARE_ENUM_AS_BIT_SET(AIStation::StationType); + +#endif /* AI_STATION_HPP */ diff --git a/src/ai/api/ai_station.hpp.sq b/src/ai/api/ai_station.hpp.sq new file mode 100644 index 000000000..f752d8d9a --- /dev/null +++ b/src/ai/api/ai_station.hpp.sq @@ -0,0 +1,69 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_station.hpp" + +namespace SQConvert { + /* Allow enums to be used as Squirrel parameters */ + template <> AIStation::ErrorMessages GetParam(ForceType<AIStation::ErrorMessages>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AIStation::ErrorMessages)tmp; } + template <> int Return<AIStation::ErrorMessages>(HSQUIRRELVM vm, AIStation::ErrorMessages res) { sq_pushinteger(vm, (int32)res); return 1; } + template <> AIStation::StationType GetParam(ForceType<AIStation::StationType>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AIStation::StationType)tmp; } + template <> int Return<AIStation::StationType>(HSQUIRRELVM vm, AIStation::StationType res) { sq_pushinteger(vm, (int32)res); return 1; } + + /* Allow AIStation to be used as Squirrel parameter */ + template <> AIStation *GetParam(ForceType<AIStation *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIStation *)instance; } + template <> AIStation &GetParam(ForceType<AIStation &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIStation *)instance; } + template <> const AIStation *GetParam(ForceType<const AIStation *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIStation *)instance; } + template <> const AIStation &GetParam(ForceType<const AIStation &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIStation *)instance; } + template <> int Return<AIStation *>(HSQUIRRELVM vm, AIStation *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIStation", res, NULL, DefSQDestructorCallback<AIStation>); return 1; } +}; // namespace SQConvert + +void SQAIStation_Register(Squirrel *engine) { + DefSQClass <AIStation> SQAIStation("AIStation"); + SQAIStation.PreRegister(engine); + SQAIStation.AddConstructor<void (AIStation::*)(), 1>(engine, "x"); + + SQAIStation.DefSQConst(engine, AIStation::ERR_STATION_BASE, "ERR_STATION_BASE"); + SQAIStation.DefSQConst(engine, AIStation::ERR_STATION_TOO_LARGE, "ERR_STATION_TOO_LARGE"); + SQAIStation.DefSQConst(engine, AIStation::ERR_STATION_TOO_CLOSE_TO_ANOTHER_STATION, "ERR_STATION_TOO_CLOSE_TO_ANOTHER_STATION"); + SQAIStation.DefSQConst(engine, AIStation::ERR_STATION_TOO_MANY_STATIONS, "ERR_STATION_TOO_MANY_STATIONS"); + SQAIStation.DefSQConst(engine, AIStation::ERR_STATION_TOO_MANY_STATIONS_IN_TOWN, "ERR_STATION_TOO_MANY_STATIONS_IN_TOWN"); + SQAIStation.DefSQConst(engine, AIStation::STATION_TRAIN, "STATION_TRAIN"); + SQAIStation.DefSQConst(engine, AIStation::STATION_TRUCK_STOP, "STATION_TRUCK_STOP"); + SQAIStation.DefSQConst(engine, AIStation::STATION_BUS_STOP, "STATION_BUS_STOP"); + SQAIStation.DefSQConst(engine, AIStation::STATION_AIRPORT, "STATION_AIRPORT"); + SQAIStation.DefSQConst(engine, AIStation::STATION_DOCK, "STATION_DOCK"); + SQAIStation.DefSQConst(engine, AIStation::STATION_ANY, "STATION_ANY"); + + AIError::RegisterErrorMap(STR_306C_STATION_TOO_SPREAD_OUT, AIStation::ERR_STATION_TOO_LARGE); + AIError::RegisterErrorMap(STR_300D_TOO_CLOSE_TO_ANOTHER_AIRPORT, AIStation::ERR_STATION_TOO_CLOSE_TO_ANOTHER_STATION); + AIError::RegisterErrorMap(STR_3009_TOO_CLOSE_TO_ANOTHER_STATION, AIStation::ERR_STATION_TOO_CLOSE_TO_ANOTHER_STATION); + AIError::RegisterErrorMap(STR_304C_TOO_CLOSE_TO_ANOTHER_DOCK, AIStation::ERR_STATION_TOO_CLOSE_TO_ANOTHER_STATION); + AIError::RegisterErrorMap(STR_3008_TOO_MANY_STATIONS_LOADING, AIStation::ERR_STATION_TOO_MANY_STATIONS); + AIError::RegisterErrorMap(STR_TOO_MANY_TRUCK_STOPS, AIStation::ERR_STATION_TOO_MANY_STATIONS); + AIError::RegisterErrorMap(STR_TOO_MANY_BUS_STOPS, AIStation::ERR_STATION_TOO_MANY_STATIONS); + AIError::RegisterErrorMap(STR_3007_TOO_MANY_STATIONS_LOADING, AIStation::ERR_STATION_TOO_MANY_STATIONS_IN_TOWN); + + AIError::RegisterErrorMapString(AIStation::ERR_STATION_TOO_LARGE, "ERR_STATION_TOO_LARGE"); + AIError::RegisterErrorMapString(AIStation::ERR_STATION_TOO_CLOSE_TO_ANOTHER_STATION, "ERR_STATION_TOO_CLOSE_TO_ANOTHER_STATION"); + AIError::RegisterErrorMapString(AIStation::ERR_STATION_TOO_MANY_STATIONS, "ERR_STATION_TOO_MANY_STATIONS"); + AIError::RegisterErrorMapString(AIStation::ERR_STATION_TOO_MANY_STATIONS_IN_TOWN, "ERR_STATION_TOO_MANY_STATIONS_IN_TOWN"); + + SQAIStation.DefSQStaticMethod(engine, &AIStation::GetClassName, "GetClassName", 1, "x"); + SQAIStation.DefSQStaticMethod(engine, &AIStation::IsValidStation, "IsValidStation", 2, "xi"); + SQAIStation.DefSQStaticMethod(engine, &AIStation::GetStationID, "GetStationID", 2, "xi"); + SQAIStation.DefSQStaticMethod(engine, &AIStation::GetName, "GetName", 2, "xi"); + SQAIStation.DefSQStaticMethod(engine, &AIStation::SetName, "SetName", 3, "xis"); + SQAIStation.DefSQStaticMethod(engine, &AIStation::GetLocation, "GetLocation", 2, "xi"); + SQAIStation.DefSQStaticMethod(engine, &AIStation::GetCargoWaiting, "GetCargoWaiting", 3, "xii"); + SQAIStation.DefSQStaticMethod(engine, &AIStation::GetCargoRating, "GetCargoRating", 3, "xii"); + SQAIStation.DefSQStaticMethod(engine, &AIStation::GetCoverageRadius, "GetCoverageRadius", 2, "xi"); + SQAIStation.DefSQStaticMethod(engine, &AIStation::GetDistanceManhattanToTile, "GetDistanceManhattanToTile", 3, "xii"); + SQAIStation.DefSQStaticMethod(engine, &AIStation::GetDistanceSquareToTile, "GetDistanceSquareToTile", 3, "xii"); + SQAIStation.DefSQStaticMethod(engine, &AIStation::IsWithinTownInfluence, "IsWithinTownInfluence", 3, "xii"); + SQAIStation.DefSQStaticMethod(engine, &AIStation::HasStationType, "HasStationType", 3, "xii"); + SQAIStation.DefSQStaticMethod(engine, &AIStation::HasRoadType, "HasRoadType", 3, "xii"); + SQAIStation.DefSQStaticMethod(engine, &AIStation::GetNearestTown, "GetNearestTown", 2, "xi"); + + SQAIStation.PostRegister(engine); +} diff --git a/src/ai/api/ai_stationlist.cpp b/src/ai/api/ai_stationlist.cpp new file mode 100644 index 000000000..43cb33878 --- /dev/null +++ b/src/ai/api/ai_stationlist.cpp @@ -0,0 +1,29 @@ +/* $Id$ */ + +/** @file ai_stationlist.cpp Implementation of AIStationList and friends. */ + +#include "ai_stationlist.hpp" +#include "ai_vehicle.hpp" +#include "../../openttd.h" +#include "../../company_func.h" +#include "../../station_base.h" +#include "../../vehicle_base.h" + +AIStationList::AIStationList(AIStation::StationType station_type) +{ + Station *st; + FOR_ALL_STATIONS(st) { + if (st->owner == _current_company && (st->facilities & station_type) != 0) this->AddItem(st->index); + } +} + +AIStationList_Vehicle::AIStationList_Vehicle(VehicleID vehicle_id) +{ + if (!AIVehicle::IsValidVehicle(vehicle_id)) return; + + Vehicle *v = ::GetVehicle(vehicle_id); + + for (Order *o = v->GetFirstOrder(); o != NULL; o = o->next) { + if (o->IsType(OT_GOTO_STATION)) this->AddItem(o->GetDestination()); + } +} diff --git a/src/ai/api/ai_stationlist.hpp b/src/ai/api/ai_stationlist.hpp new file mode 100644 index 000000000..b6b1a7d1f --- /dev/null +++ b/src/ai/api/ai_stationlist.hpp @@ -0,0 +1,39 @@ +/* $Id$ */ + +/** @file ai_stationlist.hpp List all the stations (you own). */ + +#ifndef AI_STATIONLIST_HPP +#define AI_STATIONLIST_HPP + +#include "ai_abstractlist.hpp" +#include "ai_station.hpp" + +/** + * Creates a list of stations of which you are the owner. + * @ingroup AIList + */ +class AIStationList : public AIAbstractList { +public: + static const char *GetClassName() { return "AIStationList"; } + + /** + * @param station_type The type of station to make a list of stations for. + */ + AIStationList(AIStation::StationType station_type); +}; + +/** + * Creates a list of stations which the vehicle has in its orders. + * @ingroup AIList + */ +class AIStationList_Vehicle : public AIAbstractList { +public: + static const char *GetClassName() { return "AIStationList_Vehicle"; } + + /** + * @param vehicle_id The vehicle to get the list of stations he has in its orders from. + */ + AIStationList_Vehicle(VehicleID vehicle_id); +}; + +#endif /* AI_STATIONLIST_HPP */ diff --git a/src/ai/api/ai_stationlist.hpp.sq b/src/ai/api/ai_stationlist.hpp.sq new file mode 100644 index 000000000..d3e5bcd4c --- /dev/null +++ b/src/ai/api/ai_stationlist.hpp.sq @@ -0,0 +1,42 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_stationlist.hpp" + +namespace SQConvert { + /* Allow AIStationList to be used as Squirrel parameter */ + template <> AIStationList *GetParam(ForceType<AIStationList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIStationList *)instance; } + template <> AIStationList &GetParam(ForceType<AIStationList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIStationList *)instance; } + template <> const AIStationList *GetParam(ForceType<const AIStationList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIStationList *)instance; } + template <> const AIStationList &GetParam(ForceType<const AIStationList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIStationList *)instance; } + template <> int Return<AIStationList *>(HSQUIRRELVM vm, AIStationList *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIStationList", res, NULL, DefSQDestructorCallback<AIStationList>); return 1; } +}; // namespace SQConvert + +void SQAIStationList_Register(Squirrel *engine) { + DefSQClass <AIStationList> SQAIStationList("AIStationList"); + SQAIStationList.PreRegister(engine, "AIAbstractList"); + SQAIStationList.AddConstructor<void (AIStationList::*)(AIStation::StationType station_type), 2>(engine, "xi"); + + SQAIStationList.DefSQStaticMethod(engine, &AIStationList::GetClassName, "GetClassName", 1, "x"); + + SQAIStationList.PostRegister(engine); +} + +namespace SQConvert { + /* Allow AIStationList_Vehicle to be used as Squirrel parameter */ + template <> AIStationList_Vehicle *GetParam(ForceType<AIStationList_Vehicle *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIStationList_Vehicle *)instance; } + template <> AIStationList_Vehicle &GetParam(ForceType<AIStationList_Vehicle &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIStationList_Vehicle *)instance; } + template <> const AIStationList_Vehicle *GetParam(ForceType<const AIStationList_Vehicle *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIStationList_Vehicle *)instance; } + template <> const AIStationList_Vehicle &GetParam(ForceType<const AIStationList_Vehicle &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIStationList_Vehicle *)instance; } + template <> int Return<AIStationList_Vehicle *>(HSQUIRRELVM vm, AIStationList_Vehicle *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIStationList_Vehicle", res, NULL, DefSQDestructorCallback<AIStationList_Vehicle>); return 1; } +}; // namespace SQConvert + +void SQAIStationList_Vehicle_Register(Squirrel *engine) { + DefSQClass <AIStationList_Vehicle> SQAIStationList_Vehicle("AIStationList_Vehicle"); + SQAIStationList_Vehicle.PreRegister(engine, "AIAbstractList"); + SQAIStationList_Vehicle.AddConstructor<void (AIStationList_Vehicle::*)(VehicleID vehicle_id), 2>(engine, "xi"); + + SQAIStationList_Vehicle.DefSQStaticMethod(engine, &AIStationList_Vehicle::GetClassName, "GetClassName", 1, "x"); + + SQAIStationList_Vehicle.PostRegister(engine); +} diff --git a/src/ai/api/ai_subsidy.cpp b/src/ai/api/ai_subsidy.cpp new file mode 100644 index 000000000..893b65e7d --- /dev/null +++ b/src/ai/api/ai_subsidy.cpp @@ -0,0 +1,94 @@ +/* $Id$ */ + +/** @file ai_subsidy.cpp Implementation of AISubsidy. */ + +#include "ai_subsidy.hpp" +#include "ai_error.hpp" +#include "ai_company.hpp" +#include "ai_date.hpp" +#include "../../openttd.h" +#include "../../economy_func.h" +#include "../../station_base.h" +#include "../../cargotype.h" + +/* static */ bool AISubsidy::IsValidSubsidy(SubsidyID subsidy_id) +{ + return subsidy_id < lengthof(_subsidies) && _subsidies[subsidy_id].cargo_type != CT_INVALID; +} + +/* static */ bool AISubsidy::IsAwarded(SubsidyID subsidy_id) +{ + if (!IsValidSubsidy(subsidy_id)) return false; + + return _subsidies[subsidy_id].age >= 12; +} + +/* static */ AICompany::CompanyID AISubsidy::GetAwardedTo(SubsidyID subsidy_id) +{ + if (!IsAwarded(subsidy_id)) return AICompany::INVALID_COMPANY; + + return (AICompany::CompanyID)((byte)GetStation(_subsidies[subsidy_id].from)->owner); +} + +/* static */ int32 AISubsidy::GetExpireDate(SubsidyID subsidy_id) +{ + if (!IsValidSubsidy(subsidy_id)) return -1; + + int year = AIDate::GetYear(AIDate::GetCurrentDate()); + int month = AIDate::GetMonth(AIDate::GetCurrentDate()); + + if (IsAwarded(subsidy_id)) { + month += 24 - _subsidies[subsidy_id].age; + } else { + month += 12 - _subsidies[subsidy_id].age; + } + + year += (month - 1) / 12; + month = ((month - 1) % 12) + 1; + + return AIDate::GetDate(year, month, 1); +} + +/* static */ CargoID AISubsidy::GetCargoType(SubsidyID subsidy_id) +{ + if (!IsValidSubsidy(subsidy_id)) return CT_INVALID; + + return _subsidies[subsidy_id].cargo_type; +} + +/* static */ bool AISubsidy::SourceIsTown(SubsidyID subsidy_id) +{ + if (!IsValidSubsidy(subsidy_id) || IsAwarded(subsidy_id)) return false; + + return GetCargo(GetCargoType(subsidy_id))->town_effect == TE_PASSENGERS || + GetCargo(GetCargoType(subsidy_id))->town_effect == TE_MAIL; +} + +/* static */ int32 AISubsidy::GetSource(SubsidyID subsidy_id) +{ + if (!IsValidSubsidy(subsidy_id)) return INVALID_STATION; + + return _subsidies[subsidy_id].from; +} + +/* static */ bool AISubsidy::DestinationIsTown(SubsidyID subsidy_id) +{ + if (!IsValidSubsidy(subsidy_id) || IsAwarded(subsidy_id)) return false; + + switch (GetCargo(GetCargoType(subsidy_id))->town_effect) { + case TE_PASSENGERS: + case TE_MAIL: + case TE_GOODS: + case TE_FOOD: + return true; + default: + return false; + } +} + +/* static */ int32 AISubsidy::GetDestination(SubsidyID subsidy_id) +{ + if (!IsValidSubsidy(subsidy_id)) return INVALID_STATION; + + return _subsidies[subsidy_id].to; +} diff --git a/src/ai/api/ai_subsidy.hpp b/src/ai/api/ai_subsidy.hpp new file mode 100644 index 000000000..7be86b0e8 --- /dev/null +++ b/src/ai/api/ai_subsidy.hpp @@ -0,0 +1,101 @@ +/* $Id$ */ + +/** @file ai_subsidy.hpp Everything to query subsidies. */ + +#ifndef AI_SUBSIDY_HPP +#define AI_SUBSIDY_HPP + +#include "ai_object.hpp" +#include "ai_company.hpp" + +/** + * Class that handles all subsidy related functions. + */ +class AISubsidy : public AIObject { +public: + static const char *GetClassName() { return "AISubsidy"; } + + /** + * Check whether this is a valid SubsidyID. + * @param subsidy_id The SubsidyID to check. + * @return True if and only if this subsidy is still valid. + */ + static bool IsValidSubsidy(SubsidyID subsidy_id); + + /** + * Checks whether this subsidy is already awarded to some company. + * @param subsidy_id The SubsidyID to check. + * @pre IsValidSubsidy(subsidy). + * @return True if and only if this subsidy is already awarded. + */ + static bool IsAwarded(SubsidyID subsidy_id); + + /** + * Get the company index of the company this subsidy is awarded to. + * @param subsidy_id The SubsidyID to check. + * @pre IsAwarded(subsidy_id). + * @return The companyindex of the company this subsidy is awarded to. + */ + static AICompany::CompanyID GetAwardedTo(SubsidyID subsidy_id); + + /** + * Get the date this subsidy expires. In case the subsidy is already + * awarded, return the date the subsidy expires, else, return the date the + * offer expires. + * @param subsidy_id The SubsidyID to check. + * @pre IsValidSubsidy(subsidy_id). + * @return The last valid date of this subsidy. + * @note The return value of this function will change if the subsidy is + * awarded. + */ + static int32 GetExpireDate(SubsidyID subsidy_id); + + /** + * Get the cargo type that has to be transported in order to be awarded this + * subsidy. + * @param subsidy_id The SubsidyID to check. + * @pre IsValidSubsidy(subsidy_id). + * @return The cargo type to transport. + */ + static CargoID GetCargoType(SubsidyID subsidy_id); + + /** + * Is the source of the subsidy a town or an industry. + * @param subsidy_id The SubsidyID to check. + * @pre IsValidSubsidy(subsidy_id) && !IsAwarded(subsidy_id). + * @return True if the source is a town, false if it is an industry. + */ + static bool SourceIsTown(SubsidyID subsidy_id); + + /** + * Return the source TownID/IndustryID/StationID the subsidy is for. + * 1) IsAwarded(subsidy_id) -> return the StationID the subsidy is awarded to. + * 2) !IsAwarded(subsidy_id) && SourceIsTown(subsidy_id) -> return the TownID. + * 3) !IsAwarded(subsidy_id) && !SourceIsTown(subsidy_id) -> return the IndustryID. + * @param subsidy_id The SubsidyID to check. + * @pre IsValidSubsidy(subsidy_id). + * @return One of TownID/IndustryID/StationID. + */ + static int32 GetSource(SubsidyID subsidy_id); + + /** + * Is the destination of the subsidy a town or an industry. + * @param subsidy_id The SubsidyID to check. + * @pre IsValidSubsidy(subsidy_id) && !IsAwarded(subsidy_id). + * @return True if the destination is a town, false if it is an industry. + */ + static bool DestinationIsTown(SubsidyID subsidy_id); + + /** + * Return the destination TownID/IndustryID/StationID the subsidy is for. + * 1) IsAwarded(subsidy_id) -> return the StationID the subsidy is awarded to. + * 2) !IsAwarded(subsidy_id) && SourceIsTown(subsidy_id) -> return the TownID. + * 3) !IsAwarded(subsidy_id) && !SourceIsTown(subsidy_id) -> return the IndustryID. + * @param subsidy_id the SubsidyID to check. + * @pre IsValidSubsidy(subsidy_id). + * @return One of TownID/IndustryID/StationID. + */ + static int32 GetDestination(SubsidyID subsidy_id); +}; + +#endif /* AI_SUBSIDY_HPP */ diff --git a/src/ai/api/ai_subsidy.hpp.sq b/src/ai/api/ai_subsidy.hpp.sq new file mode 100644 index 000000000..41ed75077 --- /dev/null +++ b/src/ai/api/ai_subsidy.hpp.sq @@ -0,0 +1,32 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_subsidy.hpp" + +namespace SQConvert { + /* Allow AISubsidy to be used as Squirrel parameter */ + template <> AISubsidy *GetParam(ForceType<AISubsidy *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AISubsidy *)instance; } + template <> AISubsidy &GetParam(ForceType<AISubsidy &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AISubsidy *)instance; } + template <> const AISubsidy *GetParam(ForceType<const AISubsidy *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AISubsidy *)instance; } + template <> const AISubsidy &GetParam(ForceType<const AISubsidy &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AISubsidy *)instance; } + template <> int Return<AISubsidy *>(HSQUIRRELVM vm, AISubsidy *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AISubsidy", res, NULL, DefSQDestructorCallback<AISubsidy>); return 1; } +}; // namespace SQConvert + +void SQAISubsidy_Register(Squirrel *engine) { + DefSQClass <AISubsidy> SQAISubsidy("AISubsidy"); + SQAISubsidy.PreRegister(engine); + SQAISubsidy.AddConstructor<void (AISubsidy::*)(), 1>(engine, "x"); + + SQAISubsidy.DefSQStaticMethod(engine, &AISubsidy::GetClassName, "GetClassName", 1, "x"); + SQAISubsidy.DefSQStaticMethod(engine, &AISubsidy::IsValidSubsidy, "IsValidSubsidy", 2, "xi"); + SQAISubsidy.DefSQStaticMethod(engine, &AISubsidy::IsAwarded, "IsAwarded", 2, "xi"); + SQAISubsidy.DefSQStaticMethod(engine, &AISubsidy::GetAwardedTo, "GetAwardedTo", 2, "xi"); + SQAISubsidy.DefSQStaticMethod(engine, &AISubsidy::GetExpireDate, "GetExpireDate", 2, "xi"); + SQAISubsidy.DefSQStaticMethod(engine, &AISubsidy::GetCargoType, "GetCargoType", 2, "xi"); + SQAISubsidy.DefSQStaticMethod(engine, &AISubsidy::SourceIsTown, "SourceIsTown", 2, "xi"); + SQAISubsidy.DefSQStaticMethod(engine, &AISubsidy::GetSource, "GetSource", 2, "xi"); + SQAISubsidy.DefSQStaticMethod(engine, &AISubsidy::DestinationIsTown, "DestinationIsTown", 2, "xi"); + SQAISubsidy.DefSQStaticMethod(engine, &AISubsidy::GetDestination, "GetDestination", 2, "xi"); + + SQAISubsidy.PostRegister(engine); +} diff --git a/src/ai/api/ai_subsidylist.cpp b/src/ai/api/ai_subsidylist.cpp new file mode 100644 index 000000000..8e5d96ad9 --- /dev/null +++ b/src/ai/api/ai_subsidylist.cpp @@ -0,0 +1,14 @@ +/* $Id$ */ + +/** @file ai_subsidylist.cpp Implementation of AISubsidyList. */ + +#include "ai_subsidylist.hpp" +#include "ai_subsidy.hpp" +#include "../../economy_func.h" + +AISubsidyList::AISubsidyList() +{ + for (uint i = 0; i < lengthof(_subsidies); i++) { + if (AISubsidy::IsValidSubsidy(i)) this->AddItem(i); + } +} diff --git a/src/ai/api/ai_subsidylist.hpp b/src/ai/api/ai_subsidylist.hpp new file mode 100644 index 000000000..dde3ba351 --- /dev/null +++ b/src/ai/api/ai_subsidylist.hpp @@ -0,0 +1,20 @@ +/* $Id$ */ + +/** @file ai_subsidylist.hpp List all the subsidies. */ + +#ifndef AI_SUBSIDYLIST_HPP +#define AI_SUBSIDYLIST_HPP + +#include "ai_abstractlist.hpp" + +/** + * Creates a list of all current subsidies. + * @ingroup AIList + */ +class AISubsidyList : public AIAbstractList { +public: + static const char *GetClassName() { return "AISubsidyList"; } + AISubsidyList(); +}; + +#endif /* AI_SUBSIDYLIST_HPP */ diff --git a/src/ai/api/ai_subsidylist.hpp.sq b/src/ai/api/ai_subsidylist.hpp.sq new file mode 100644 index 000000000..db86a6ab6 --- /dev/null +++ b/src/ai/api/ai_subsidylist.hpp.sq @@ -0,0 +1,23 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_subsidylist.hpp" + +namespace SQConvert { + /* Allow AISubsidyList to be used as Squirrel parameter */ + template <> AISubsidyList *GetParam(ForceType<AISubsidyList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AISubsidyList *)instance; } + template <> AISubsidyList &GetParam(ForceType<AISubsidyList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AISubsidyList *)instance; } + template <> const AISubsidyList *GetParam(ForceType<const AISubsidyList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AISubsidyList *)instance; } + template <> const AISubsidyList &GetParam(ForceType<const AISubsidyList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AISubsidyList *)instance; } + template <> int Return<AISubsidyList *>(HSQUIRRELVM vm, AISubsidyList *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AISubsidyList", res, NULL, DefSQDestructorCallback<AISubsidyList>); return 1; } +}; // namespace SQConvert + +void SQAISubsidyList_Register(Squirrel *engine) { + DefSQClass <AISubsidyList> SQAISubsidyList("AISubsidyList"); + SQAISubsidyList.PreRegister(engine, "AIAbstractList"); + SQAISubsidyList.AddConstructor<void (AISubsidyList::*)(), 1>(engine, "x"); + + SQAISubsidyList.DefSQStaticMethod(engine, &AISubsidyList::GetClassName, "GetClassName", 1, "x"); + + SQAISubsidyList.PostRegister(engine); +} diff --git a/src/ai/api/ai_testmode.cpp b/src/ai/api/ai_testmode.cpp new file mode 100644 index 000000000..1b09fa699 --- /dev/null +++ b/src/ai/api/ai_testmode.cpp @@ -0,0 +1,26 @@ +/* $Id$ */ + +/** @file ai_testmode.cpp Implementation of AITestMode. */ + +#include "ai_testmode.hpp" +#include "../../command_type.h" + +bool AITestMode::ModeProc(TileIndex tile, uint32 p1, uint32 p2, uint procc, CommandCost costs) +{ + /* In test mode we only return 'false', telling the DoCommand it + * should stop after testing the command and return with that result. */ + return false; +} + +AITestMode::AITestMode() +{ + this->last_mode = this->GetDoCommandMode(); + this->last_instance = this->GetDoCommandModeInstance(); + this->SetDoCommandMode(&AITestMode::ModeProc, this); +} + +AITestMode::~AITestMode() +{ + assert(this->GetDoCommandModeInstance() == this); + this->SetDoCommandMode(this->last_mode, this->last_instance); +} diff --git a/src/ai/api/ai_testmode.hpp b/src/ai/api/ai_testmode.hpp new file mode 100644 index 000000000..c65c501d9 --- /dev/null +++ b/src/ai/api/ai_testmode.hpp @@ -0,0 +1,48 @@ +/* $Id$ */ + +/** @file ai_testmode.hpp Switch the AI to Test Mode. */ + +#ifndef AI_TESTMODE_HPP +#define AI_TESTMODE_HPP + +#include "ai_object.hpp" + +/** + * Class to switch current mode to Test Mode. + * If you create an instance of this class, the mode will be switched to + * Testing. The original mode is stored and recovered from when ever the + * instance is destroyed. + * In Test mode all the commands you execute aren't really executed. The + * system only checks if it would be able to execute your requests, and what + * the cost would be. + */ +class AITestMode : public AIObject { +public: + static const char *GetClassName() { return "AITestMode"; } + +private: + AIModeProc *last_mode; + AIObject *last_instance; + +protected: + /** + * The callback proc for Testing mode. + */ + static bool ModeProc(TileIndex tile, uint32 p1, uint32 p2, uint procc, CommandCost costs); + +public: + /** + * Creating instance of this class switches the build mode to Testing. + * @note When the instance is destroyed, he restores the mode that was + * current when the instance was created! + */ + AITestMode(); + + /** + * Destroying this instance reset the building mode to the mode it was + * in when the instance was created. + */ + ~AITestMode(); +}; + +#endif /* AI_TESTMODE_HPP */ diff --git a/src/ai/api/ai_testmode.hpp.sq b/src/ai/api/ai_testmode.hpp.sq new file mode 100644 index 000000000..5343d8cab --- /dev/null +++ b/src/ai/api/ai_testmode.hpp.sq @@ -0,0 +1,23 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_testmode.hpp" + +namespace SQConvert { + /* Allow AITestMode to be used as Squirrel parameter */ + template <> AITestMode *GetParam(ForceType<AITestMode *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AITestMode *)instance; } + template <> AITestMode &GetParam(ForceType<AITestMode &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AITestMode *)instance; } + template <> const AITestMode *GetParam(ForceType<const AITestMode *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AITestMode *)instance; } + template <> const AITestMode &GetParam(ForceType<const AITestMode &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AITestMode *)instance; } + template <> int Return<AITestMode *>(HSQUIRRELVM vm, AITestMode *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AITestMode", res, NULL, DefSQDestructorCallback<AITestMode>); return 1; } +}; // namespace SQConvert + +void SQAITestMode_Register(Squirrel *engine) { + DefSQClass <AITestMode> SQAITestMode("AITestMode"); + SQAITestMode.PreRegister(engine); + SQAITestMode.AddConstructor<void (AITestMode::*)(), 1>(engine, "x"); + + SQAITestMode.DefSQStaticMethod(engine, &AITestMode::GetClassName, "GetClassName", 1, "x"); + + SQAITestMode.PostRegister(engine); +} diff --git a/src/ai/api/ai_tile.cpp b/src/ai/api/ai_tile.cpp new file mode 100644 index 000000000..868cc679a --- /dev/null +++ b/src/ai/api/ai_tile.cpp @@ -0,0 +1,239 @@ +/* $Id$ */ + +/** @file ai_tile.cpp Implementation of AITile. */ + +#include "ai_tile.hpp" +#include "ai_map.hpp" +#include "ai_town.hpp" +#include "../../openttd.h" +#include "../../tile_map.h" +#include "../../tile_cmd.h" +#include "../../map_func.h" +#include "../../variables.h" +#include "../../station_func.h" +#include "../../command_type.h" +#include "../../settings_type.h" +#include "../../company_func.h" +#include "../../road_map.h" +#include "../../water_map.h" +#include "../../clear_map.h" +#include "../../town.h" + +/* static */ bool AITile::IsBuildable(TileIndex tile) +{ + if (!::IsValidTile(tile)) return false; + + switch (::GetTileType(tile)) { + default: return false; + case MP_CLEAR: return true; + case MP_TREES: return true; + case MP_WATER: return IsCoast(tile); + case MP_ROAD: + /* Tram bits aren't considered buildable */ + if (::GetRoadTypes(tile) != ROADTYPES_ROAD) return false; + /* Depots and crossings aren't considered buildable */ + if (::GetRoadTileType(tile) != ROAD_TILE_NORMAL) return false; + if (CountBits(::GetRoadBits(tile, ROADTYPE_ROAD)) != 1) return false; + if (::IsRoadOwner(tile, ROADTYPE_ROAD, OWNER_TOWN)) return true; + if (::IsRoadOwner(tile, ROADTYPE_ROAD, _current_company)) return true; + return false; + } +} + +/* static */ bool AITile::IsBuildableRectangle(TileIndex tile, uint width, uint height) +{ + uint tx, ty; + + tx = AIMap::GetTileX(tile); + ty = AIMap::GetTileY(tile); + + for (uint x = tx; x < width + tx; x++) { + for (uint y = ty; y < height + ty; y++) { + if (!IsBuildable(AIMap::GetTileIndex(x, y))) return false; + } + } + + return true; +} + +/* static */ bool AITile::IsWaterTile(TileIndex tile) +{ + if (!::IsValidTile(tile)) return false; + + return ::IsTileType(tile, MP_WATER) && !::IsCoast(tile); +} + +/* static */ bool AITile::IsCoastTile(TileIndex tile) +{ + if (!::IsValidTile(tile)) return false; + + return ::IsTileType(tile, MP_WATER) && ::IsCoast(tile); +} + +/* static */ bool AITile::IsStationTile(TileIndex tile) +{ + if (!::IsValidTile(tile)) return false; + + return ::IsTileType(tile, MP_STATION); +} + +/* static */ bool AITile::IsSteepSlope(Slope slope) +{ + if (slope == SLOPE_INVALID) return false; + + return ::IsSteepSlope((::Slope)slope); +} + +/* static */ bool AITile::IsHalftileSlope(Slope slope) +{ + if (slope == SLOPE_INVALID) return false; + + return ::IsHalftileSlope((::Slope)slope); +} + +/* static */ bool AITile::HasTreeOnTile(TileIndex tile) +{ + return ::IsTileType(tile, MP_TREES); +} + +/* static */ bool AITile::IsFarmTile(TileIndex tile) +{ + return (::IsTileType(tile, MP_CLEAR) && ::IsClearGround(tile, CLEAR_FIELDS)); +} + +/* static */ bool AITile::IsRockTile(TileIndex tile) +{ + return (::IsTileType(tile, MP_CLEAR) && ::IsClearGround(tile, CLEAR_ROCKS)); +} + +/* static */ bool AITile::IsRoughTile(TileIndex tile) +{ + return (::IsTileType(tile, MP_CLEAR) && ::IsClearGround(tile, CLEAR_ROUGH)); +} + +/* static */ bool AITile::IsSnowTile(TileIndex tile) +{ + return (::IsTileType(tile, MP_CLEAR) && ::IsClearGround(tile, CLEAR_SNOW)); +} + +/* static */ bool AITile::IsDesertTile(TileIndex tile) +{ + return (::IsTileType(tile, MP_CLEAR) && ::IsClearGround(tile, CLEAR_DESERT)); +} + +/* static */ AITile::Slope AITile::GetSlope(TileIndex tile) +{ + if (!::IsValidTile(tile)) return SLOPE_INVALID; + + return (Slope)::GetTileSlope(tile, NULL); +} + +/* static */ AITile::Slope AITile::GetComplementSlope(Slope slope) +{ + if (slope == SLOPE_INVALID) return SLOPE_INVALID; + if (IsSteepSlope(slope)) return SLOPE_INVALID; + if (IsHalftileSlope(slope)) return SLOPE_INVALID; + + return (Slope)::ComplementSlope((::Slope)slope); +} + +/* static */ int32 AITile::GetHeight(TileIndex tile) +{ + if (!::IsValidTile(tile)) return false; + + return ::TileHeight(tile); +} + +/* static */ AICompany::CompanyID AITile::GetOwner(TileIndex tile) +{ + if (!::IsValidTile(tile)) return AICompany::INVALID_COMPANY; + if (::IsTileType(tile, MP_HOUSE)) return AICompany::INVALID_COMPANY; + if (::IsTileType(tile, MP_INDUSTRY)) return AICompany::INVALID_COMPANY; + + return AICompany::ResolveCompanyID((AICompany::CompanyID)(byte)::GetTileOwner(tile)); +} + +/* static */ bool AITile::HasTransportType(TileIndex tile, TransportType transport_type) +{ + if (!::IsValidTile(tile)) return false; + + return GB(::GetTileTrackStatus(tile, (::TransportType)transport_type, UINT32_MAX), 0, 16) != 0; +} + +/* static */ int32 AITile::GetCargoAcceptance(TileIndex tile, CargoID cargo_type, uint width, uint height, uint radius) +{ + if (!::IsValidTile(tile)) return false; + + AcceptedCargo accepts; + ::GetAcceptanceAroundTiles(accepts, tile, width, height, _settings_game.station.modified_catchment ? radius : (uint)CA_UNMODIFIED); + return accepts[cargo_type]; +} + +/* static */ int32 AITile::GetCargoProduction(TileIndex tile, CargoID cargo_type, uint width, uint height, uint radius) +{ + if (!::IsValidTile(tile)) return false; + + AcceptedCargo produced; + ::GetProductionAroundTiles(produced, tile, width, height, _settings_game.station.modified_catchment ? radius : (uint)CA_UNMODIFIED); + return produced[cargo_type]; +} + +/* static */ int32 AITile::GetDistanceManhattanToTile(TileIndex tile_from, TileIndex tile_to) +{ + return AIMap::DistanceManhattan(tile_from, tile_to); +} + +/* static */ int32 AITile::GetDistanceSquareToTile(TileIndex tile_from, TileIndex tile_to) +{ + return AIMap::DistanceSquare(tile_from, tile_to); +} + +/* static */ bool AITile::RaiseTile(TileIndex tile, int32 slope) +{ + EnforcePrecondition(false, ::IsValidTile(tile)); + + return AIObject::DoCommand(tile, slope, 1, CMD_TERRAFORM_LAND); +} + +/* static */ bool AITile::LowerTile(TileIndex tile, int32 slope) +{ + EnforcePrecondition(false, ::IsValidTile(tile)); + + return AIObject::DoCommand(tile, slope, 0, CMD_TERRAFORM_LAND); +} + +/* static */ bool AITile::DemolishTile(TileIndex tile) +{ + EnforcePrecondition(false, ::IsValidTile(tile)); + + return AIObject::DoCommand(tile, 0, 0, CMD_LANDSCAPE_CLEAR); +} + +/* static */ bool AITile::PlantTree(TileIndex tile) +{ + EnforcePrecondition(false, ::IsValidTile(tile)); + + return AIObject::DoCommand(tile, -1, tile, CMD_PLANT_TREE); +} + +/* static */ bool AITile::PlantTreeRectangle(TileIndex tile, uint width, uint height) +{ + EnforcePrecondition(false, ::IsValidTile(tile)); + EnforcePrecondition(false, width >= 1 && width <= 20); + EnforcePrecondition(false, height >= 1 && height <= 20); + TileIndex end_tile = tile + ::TileDiffXY(width - 1, height - 1); + + return AIObject::DoCommand(tile, -1, end_tile, CMD_PLANT_TREE); +} + +/* static */ bool AITile::IsWithinTownInfluence(TileIndex tile, TownID town_id) +{ + return AITown::IsWithinTownInfluence(town_id, tile); +} + +/* static */ TownID AITile::GetClosestTown(TileIndex tile) +{ + if (!::IsValidTile(tile)) return INVALID_TOWN; + + return ::ClosestTownFromTile(tile, UINT_MAX)->index; +} diff --git a/src/ai/api/ai_tile.hpp b/src/ai/api/ai_tile.hpp new file mode 100644 index 000000000..298de75cb --- /dev/null +++ b/src/ai/api/ai_tile.hpp @@ -0,0 +1,362 @@ +/* $Id$ */ + +/** @file ai_tile.hpp Everything to query and manipulate tiles. */ + +#ifndef AI_TILE_HPP +#define AI_TILE_HPP + +#include "ai_abstractlist.hpp" +#include "ai_error.hpp" +#include "ai_company.hpp" + +/** + * Class that handles all tile related functions. + */ +class AITile : public AIObject { +public: + static const char *GetClassName() { return "AITile"; } + + /** + * Error messages related to modifying tiles. + */ + enum ErrorMessages { + + /** Base for tile related errors */ + ERR_TILE_BASE = AIError::ERR_CAT_TILE << AIError::ERR_CAT_BIT_SIZE, + + /** Tile can't be raised any higher */ + ERR_TILE_TOO_HIGH, // [STR_1003_ALREADY_AT_SEA_LEVEL] + + /** Tile can't be lowered any lower */ + ERR_TILE_TOO_LOW, // [STR_1003_ALREADY_AT_SEA_LEVEL] + }; + + /** + * Enumeration for the slope-type (from slopes.h). + * + * This enumeration use the chars N, E, S, W corresponding the + * direction North, East, South and West. The top corner of a tile + * is the north-part of the tile. + */ + enum Slope { + /* Values are important, as they represent the internal state of the game. */ + SLOPE_FLAT = 0x00, //!< A flat tile + SLOPE_W = 0x01, //!< The west corner of the tile is raised + SLOPE_S = 0x02, //!< The south corner of the tile is raised + SLOPE_E = 0x04, //!< The east corner of the tile is raised + SLOPE_N = 0x08, //!< The north corner of the tile is raised + SLOPE_STEEP = 0x10, //!< Indicates the slope is steep + SLOPE_NW = SLOPE_N | SLOPE_W, //!< North and west corner are raised + SLOPE_SW = SLOPE_S | SLOPE_W, //!< South and west corner are raised + SLOPE_SE = SLOPE_S | SLOPE_E, //!< South and east corner are raised + SLOPE_NE = SLOPE_N | SLOPE_E, //!< North and east corner are raised + SLOPE_EW = SLOPE_E | SLOPE_W, //!< East and west corner are raised + SLOPE_NS = SLOPE_N | SLOPE_S, //!< North and south corner are raised + SLOPE_ELEVATED = SLOPE_N | SLOPE_E | SLOPE_S | SLOPE_W, //!< All corner are raised, similar to SLOPE_FLAT + SLOPE_NWS = SLOPE_N | SLOPE_W | SLOPE_S, //!< North, west and south corner are raised + SLOPE_WSE = SLOPE_W | SLOPE_S | SLOPE_E, //!< West, south and east corner are raised + SLOPE_SEN = SLOPE_S | SLOPE_E | SLOPE_N, //!< South, east and north corner are raised + SLOPE_ENW = SLOPE_E | SLOPE_N | SLOPE_W, //!< East, north and west corner are raised + SLOPE_STEEP_W = SLOPE_STEEP | SLOPE_NWS, //!< A steep slope falling to east (from west) + SLOPE_STEEP_S = SLOPE_STEEP | SLOPE_WSE, //!< A steep slope falling to north (from south) + SLOPE_STEEP_E = SLOPE_STEEP | SLOPE_SEN, //!< A steep slope falling to west (from east) + SLOPE_STEEP_N = SLOPE_STEEP | SLOPE_ENW, //!< A steep slope falling to south (from north) + + SLOPE_INVALID = 0xFF, //!< An invalid slope + }; + + /** + * The different transport types a tile can have. + */ + enum TransportType { + /* Values are important, as they represent the internal state of the game. */ + TRANSPORT_RAIL = 0, //!< Tile with rail. + TRANSPORT_ROAD = 1, //!< Tile with road. + TRANSPORT_WATER = 2, //!< Tile with navigable waterways. + TRANSPORT_AIR = 3, //!< Tile with airport. + + INVALID_TRANSPORT = -1, //!< Tile without any transport type. + }; + + /** + * Check if this tile is buildable, i.e. no things on it that needs + * demolishing. + * @param tile The tile to check on. + * @pre AIMap::IsValidTile(tile). + * @return True if it is buildable, false if not. + * @note For trams you also might want to check for AIRoad::IsRoad(), + * as you can build tram-rails on road-tiles. + * @note For rail you also might want to check for AIRoad::IsRoad(), + * as in some cases you can build rails on road-tiles. + */ + static bool IsBuildable(TileIndex tile); + + /** + * Check if this tile is buildable in a rectangle around a tile, with the + * entry in the list as top-left. + * @param tile The tile to check on. + * @param width The width of the rectangle. + * @param height The height of the rectangle. + * @pre AIMap::IsValidTile(tile). + * @return True if it is buildable, false if not. + */ + static bool IsBuildableRectangle(TileIndex tile, uint width, uint height); + + /** + * Checks whether the given tile is actually a water tile. + * @param tile The tile to check on. + * @pre AIMap::IsValidTile(tile). + * @return True if and only if the tile is a water tile. + */ + static bool IsWaterTile(TileIndex tile); + + /** + * Checks whether the given tile is actually a coast tile. + * @param tile The tile to check. + * @pre AIMap::IsValidTile(tile). + * @return True if and only if the tile is a coast tile. + * @note Building on coast tiles in general is more expensive. + */ + static bool IsCoastTile(TileIndex tile); + + /** + * Checks whether the given tile is a station tile of any station. + * @param tile The tile to check. + * @pre AIMap::IsValidTile(tile). + * @return True if and only if the tile is a station tile. + */ + static bool IsStationTile(TileIndex tile); + + /** + * Check if a tile has a steep slope. + * @param slope The slope to check on. + * @pre slope != SLOPE_INVALID. + * @return True if the slope is a steep slope. + */ + static bool IsSteepSlope(Slope slope); + + /** + * Check if a tile has a halftile slope. + * @param slope The slope to check on. + * @pre slope != SLOPE_INVALID. + * @return True if the slope is a halftile slope. + */ + static bool IsHalftileSlope(Slope slope); + + /** + * Check if the tile has any tree on it. + * @param tile The tile to check on. + * @pre AIMap::IsValidTile(tile). + * @return True if and only if there is a tree on the tile. + */ + static bool HasTreeOnTile(TileIndex tile); + + /** + * Check if the tile is a farmland tile. + * @param tile The tile to check on. + * @pre AIMap::IsValidTile(tile). + * @return True if and only if the tile is farmland. + */ + static bool IsFarmTile(TileIndex tile); + + /** + * Check if the tile is a rock tile. + * @param tile The tile to check on. + * @pre AIMap::IsValidTile(tile). + * @return True if and only if the tile is rock tile. + */ + static bool IsRockTile(TileIndex tile); + + /** + * Check if the tile is a rough tile. + * @param tile The tile to check on. + * @pre AIMap::IsValidTile(tile). + * @return True if and only if the tile is rough tile. + */ + static bool IsRoughTile(TileIndex tile); + + /** + * Check if the tile is a snow tile. + * @param tile The tile to check on. + * @pre AIMap::IsValidTile(tile). + * @return True if and only if the tile is snow tile. + */ + static bool IsSnowTile(TileIndex tile); + + /** + * Check if the tile is a desert tile. + * @param tile The tile to check on. + * @pre AIMap::IsValidTile(tile). + * @return True if and only if the tile is desert tile. + */ + static bool IsDesertTile(TileIndex tile); + + /** + * Get the slope of a tile. + * @param tile The tile to check on. + * @pre AIMap::IsValidTile(tile). + * @return 0 means flat, others indicate internal state of slope. + */ + static Slope GetSlope(TileIndex tile); + + /** + * Get the complement of the slope. + * @param slope The slope to get the complement of. + * @pre slope != SLOPE_INVALID. + * @pre !IsSteepSlope(slope). + * @pre !IsHalftileSlope(slope). + * @return The complement of a slope. This means that all corners that + * weren't raised, are raised, and visa versa. + */ + static Slope GetComplementSlope(Slope slope); + + /** + * Get the height of the tile. + * @param tile The tile to check on. + * @pre AIMap::IsValidTile(tile). + * @return The height of the tile, ranging from 0 to 15. + */ + static int32 GetHeight(TileIndex tile); + + /** + * Get the owner of the tile. + * @param tile The tile to get the owner from. + * @pre AIMap::IsValidTile(tile). + * @return The CompanyID of the owner of the tile, or INVALID_COMPANY if + * there is no owner (grass/industry/water tiles, etc.). + */ + static AICompany::CompanyID GetOwner(TileIndex tile); + + /** + * Checks whether the given tile contains parts suitable for the given + * TransportType. + * @param tile The tile to check. + * @param transport_type The TransportType to check against. + * @pre AIMap::IsValidTile(tile). + * @note Returns false on tiles with roadworks and on road tiles with only + * a single piece of road as these tiles cannot be used to transport + * anything on. It furthermore returns true on some coast tile for + * TRANSPORT_WATER because ships can navigate over them. + * @return True if and only if the tile has the given TransportType. + */ + static bool HasTransportType(TileIndex tile, TransportType transport_type); + + /** + * Check how much cargo this tile accepts. + * It creates a radius around the tile, and adds up all acceptance of this + * cargo. + * @param tile The tile to check on. + * @param cargo_type The cargo to check the acceptance of. + * @param width The width of the station. + * @param height The height of the station. + * @param radius The radius of the station. + * @pre AIMap::IsValidTile(tile). + * @return Value below 8 means no acceptance; the more the better. + */ + static int32 GetCargoAcceptance(TileIndex tile, CargoID cargo_type, uint width, uint height, uint radius); + + /** + * Checks how many tiles in the radius produces this cargo. + * It creates a radius around the tile, and adds up all tiles that produce + * this cargo. + * @param tile The tile to check on. + * @param cargo_type The cargo to check the production of. + * @param width The width of the station. + * @param height The height of the station. + * @param radius The radius of the station. + * @pre AIMap::IsValidTile(tile). + * @return The tiles that produce this cargo within radius of the tile. + * @note Town(houses) are not included in the value. + */ + static int32 GetCargoProduction(TileIndex tile, CargoID cargo_type, uint width, uint height, uint radius); + + /** + * Get the manhattan distance from the tile to the tile. + * @param tile_from The tile to get the distance to. + * @param tile_to The tile to get the distance to. + * @return The distance between the two tiles. + */ + static int32 GetDistanceManhattanToTile(TileIndex tile_from, TileIndex tile_to); + + /** + * Get the square distance from the tile to the tile. + * @param tile_from The tile to get the distance to. + * @param tile_to The tile to get the distance to. + * @return The distance between the two tiles. + */ + static int32 GetDistanceSquareToTile(TileIndex tile_from, TileIndex tile_to); + + /** + * Raise the given corners of the tile. The corners can be combined, + * for example: SLOPE_N | SLOPE_W (= SLOPE_NW) + * @param tile The tile to raise. + * @param slope Corners to raise (SLOPE_xxx). + * @pre AIMap::IsValidTile(tile). + * @exception AIError::ERR_AREA_NOT_CLEAR + * @exception AIError::ERR_TOO_CLOSE_TO_EDGE + * @exception AITile::ERR_TILE_TOO_HIGH + * @return 0 means failed, 1 means success. + */ + static bool RaiseTile(TileIndex tile, int32 slope); + + /** + * Lower the given corners of the tile. The corners can be combined, + * for example: SLOPE_N | SLOPE_W (= SLOPE_NW) + * @param tile The tile to lower. + * @param slope Corners to lower (SLOPE_xxx). + * @pre AIMap::IsValidTile(tile). + * @exception AIError::ERR_AREA_NOT_CLEAR + * @exception AIError::ERR_TOO_CLOSE_TO_EDGE + * @exception AITile::ERR_TILE_TOO_LOW + * @return 0 means failed, 1 means success. + */ + static bool LowerTile(TileIndex tile, int32 slope); + + /** + * Destroy everything on the given tile. + * @param tile The tile to demolish. + * @pre AIMap::IsValidTile(tile). + * @exception AIError::ERR_AREA_NOT_CLEAR + * @return True if and only if the tile was demolished. + */ + static bool DemolishTile(TileIndex tile); + + /** + * Create a random tree on a tile. + * @param tile The tile to build a tree on. + * @pre AIMap::IsValidTile(tile). + * @return True if and only if a tree was added on the tile. + */ + static bool PlantTree(TileIndex tile); + + /** + * Create a random tree on a rectangle of tiles. + * @param tile The top left tile of the rectangle. + * @param width The width of the rectangle. + * @param height The height of the rectangle. + * @pre AIMap::IsValidTile(tile). + * @pre width >= 1 && width <= 20. + * @pre height >= 1 && height <= 20. + * @return True if and only if a tree was added on any of the tiles in the rectangle. + */ + static bool PlantTreeRectangle(TileIndex tile, uint width, uint height); + + /** + * Find out if this tile is within the rating influence of a town. + * Stations on this tile influence the rating of the town. + * @param tile The tile to check. + * @param town_id The town to check. + * @return True if the tile is within the rating influence of the town. + */ + static bool IsWithinTownInfluence(TileIndex tile, TownID town_id); + + /** + * Find the town that is closest to a tile. Stations you build at this tile + * will belong to this town. + * @param tile The tile to check. + * @return The TownID of the town closest to the tile. + */ + static TownID GetClosestTown(TileIndex tile); +}; + +#endif /* AI_TILE_HPP */ diff --git a/src/ai/api/ai_tile.hpp.sq b/src/ai/api/ai_tile.hpp.sq new file mode 100644 index 000000000..920dc6543 --- /dev/null +++ b/src/ai/api/ai_tile.hpp.sq @@ -0,0 +1,97 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_tile.hpp" + +namespace SQConvert { + /* Allow enums to be used as Squirrel parameters */ + template <> AITile::ErrorMessages GetParam(ForceType<AITile::ErrorMessages>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AITile::ErrorMessages)tmp; } + template <> int Return<AITile::ErrorMessages>(HSQUIRRELVM vm, AITile::ErrorMessages res) { sq_pushinteger(vm, (int32)res); return 1; } + template <> AITile::Slope GetParam(ForceType<AITile::Slope>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AITile::Slope)tmp; } + template <> int Return<AITile::Slope>(HSQUIRRELVM vm, AITile::Slope res) { sq_pushinteger(vm, (int32)res); return 1; } + template <> AITile::TransportType GetParam(ForceType<AITile::TransportType>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AITile::TransportType)tmp; } + template <> int Return<AITile::TransportType>(HSQUIRRELVM vm, AITile::TransportType res) { sq_pushinteger(vm, (int32)res); return 1; } + + /* Allow AITile to be used as Squirrel parameter */ + template <> AITile *GetParam(ForceType<AITile *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AITile *)instance; } + template <> AITile &GetParam(ForceType<AITile &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AITile *)instance; } + template <> const AITile *GetParam(ForceType<const AITile *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AITile *)instance; } + template <> const AITile &GetParam(ForceType<const AITile &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AITile *)instance; } + template <> int Return<AITile *>(HSQUIRRELVM vm, AITile *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AITile", res, NULL, DefSQDestructorCallback<AITile>); return 1; } +}; // namespace SQConvert + +void SQAITile_Register(Squirrel *engine) { + DefSQClass <AITile> SQAITile("AITile"); + SQAITile.PreRegister(engine); + SQAITile.AddConstructor<void (AITile::*)(), 1>(engine, "x"); + + SQAITile.DefSQConst(engine, AITile::ERR_TILE_BASE, "ERR_TILE_BASE"); + SQAITile.DefSQConst(engine, AITile::ERR_TILE_TOO_HIGH, "ERR_TILE_TOO_HIGH"); + SQAITile.DefSQConst(engine, AITile::ERR_TILE_TOO_LOW, "ERR_TILE_TOO_LOW"); + SQAITile.DefSQConst(engine, AITile::SLOPE_FLAT, "SLOPE_FLAT"); + SQAITile.DefSQConst(engine, AITile::SLOPE_W, "SLOPE_W"); + SQAITile.DefSQConst(engine, AITile::SLOPE_S, "SLOPE_S"); + SQAITile.DefSQConst(engine, AITile::SLOPE_E, "SLOPE_E"); + SQAITile.DefSQConst(engine, AITile::SLOPE_N, "SLOPE_N"); + SQAITile.DefSQConst(engine, AITile::SLOPE_STEEP, "SLOPE_STEEP"); + SQAITile.DefSQConst(engine, AITile::SLOPE_NW, "SLOPE_NW"); + SQAITile.DefSQConst(engine, AITile::SLOPE_SW, "SLOPE_SW"); + SQAITile.DefSQConst(engine, AITile::SLOPE_SE, "SLOPE_SE"); + SQAITile.DefSQConst(engine, AITile::SLOPE_NE, "SLOPE_NE"); + SQAITile.DefSQConst(engine, AITile::SLOPE_EW, "SLOPE_EW"); + SQAITile.DefSQConst(engine, AITile::SLOPE_NS, "SLOPE_NS"); + SQAITile.DefSQConst(engine, AITile::SLOPE_ELEVATED, "SLOPE_ELEVATED"); + SQAITile.DefSQConst(engine, AITile::SLOPE_NWS, "SLOPE_NWS"); + SQAITile.DefSQConst(engine, AITile::SLOPE_WSE, "SLOPE_WSE"); + SQAITile.DefSQConst(engine, AITile::SLOPE_SEN, "SLOPE_SEN"); + SQAITile.DefSQConst(engine, AITile::SLOPE_ENW, "SLOPE_ENW"); + SQAITile.DefSQConst(engine, AITile::SLOPE_STEEP_W, "SLOPE_STEEP_W"); + SQAITile.DefSQConst(engine, AITile::SLOPE_STEEP_S, "SLOPE_STEEP_S"); + SQAITile.DefSQConst(engine, AITile::SLOPE_STEEP_E, "SLOPE_STEEP_E"); + SQAITile.DefSQConst(engine, AITile::SLOPE_STEEP_N, "SLOPE_STEEP_N"); + SQAITile.DefSQConst(engine, AITile::SLOPE_INVALID, "SLOPE_INVALID"); + SQAITile.DefSQConst(engine, AITile::TRANSPORT_RAIL, "TRANSPORT_RAIL"); + SQAITile.DefSQConst(engine, AITile::TRANSPORT_ROAD, "TRANSPORT_ROAD"); + SQAITile.DefSQConst(engine, AITile::TRANSPORT_WATER, "TRANSPORT_WATER"); + SQAITile.DefSQConst(engine, AITile::TRANSPORT_AIR, "TRANSPORT_AIR"); + SQAITile.DefSQConst(engine, AITile::INVALID_TRANSPORT, "INVALID_TRANSPORT"); + + AIError::RegisterErrorMap(STR_1003_ALREADY_AT_SEA_LEVEL, AITile::ERR_TILE_TOO_HIGH); + AIError::RegisterErrorMap(STR_1003_ALREADY_AT_SEA_LEVEL, AITile::ERR_TILE_TOO_LOW); + + AIError::RegisterErrorMapString(AITile::ERR_TILE_TOO_HIGH, "ERR_TILE_TOO_HIGH"); + AIError::RegisterErrorMapString(AITile::ERR_TILE_TOO_LOW, "ERR_TILE_TOO_LOW"); + + SQAITile.DefSQStaticMethod(engine, &AITile::GetClassName, "GetClassName", 1, "x"); + SQAITile.DefSQStaticMethod(engine, &AITile::IsBuildable, "IsBuildable", 2, "xi"); + SQAITile.DefSQStaticMethod(engine, &AITile::IsBuildableRectangle, "IsBuildableRectangle", 4, "xiii"); + SQAITile.DefSQStaticMethod(engine, &AITile::IsWaterTile, "IsWaterTile", 2, "xi"); + SQAITile.DefSQStaticMethod(engine, &AITile::IsCoastTile, "IsCoastTile", 2, "xi"); + SQAITile.DefSQStaticMethod(engine, &AITile::IsStationTile, "IsStationTile", 2, "xi"); + SQAITile.DefSQStaticMethod(engine, &AITile::IsSteepSlope, "IsSteepSlope", 2, "xi"); + SQAITile.DefSQStaticMethod(engine, &AITile::IsHalftileSlope, "IsHalftileSlope", 2, "xi"); + SQAITile.DefSQStaticMethod(engine, &AITile::HasTreeOnTile, "HasTreeOnTile", 2, "xi"); + SQAITile.DefSQStaticMethod(engine, &AITile::IsFarmTile, "IsFarmTile", 2, "xi"); + SQAITile.DefSQStaticMethod(engine, &AITile::IsRockTile, "IsRockTile", 2, "xi"); + SQAITile.DefSQStaticMethod(engine, &AITile::IsRoughTile, "IsRoughTile", 2, "xi"); + SQAITile.DefSQStaticMethod(engine, &AITile::IsSnowTile, "IsSnowTile", 2, "xi"); + SQAITile.DefSQStaticMethod(engine, &AITile::IsDesertTile, "IsDesertTile", 2, "xi"); + SQAITile.DefSQStaticMethod(engine, &AITile::GetSlope, "GetSlope", 2, "xi"); + SQAITile.DefSQStaticMethod(engine, &AITile::GetComplementSlope, "GetComplementSlope", 2, "xi"); + SQAITile.DefSQStaticMethod(engine, &AITile::GetHeight, "GetHeight", 2, "xi"); + SQAITile.DefSQStaticMethod(engine, &AITile::GetOwner, "GetOwner", 2, "xi"); + SQAITile.DefSQStaticMethod(engine, &AITile::HasTransportType, "HasTransportType", 3, "xii"); + SQAITile.DefSQStaticMethod(engine, &AITile::GetCargoAcceptance, "GetCargoAcceptance", 6, "xiiiii"); + SQAITile.DefSQStaticMethod(engine, &AITile::GetCargoProduction, "GetCargoProduction", 6, "xiiiii"); + SQAITile.DefSQStaticMethod(engine, &AITile::GetDistanceManhattanToTile, "GetDistanceManhattanToTile", 3, "xii"); + SQAITile.DefSQStaticMethod(engine, &AITile::GetDistanceSquareToTile, "GetDistanceSquareToTile", 3, "xii"); + SQAITile.DefSQStaticMethod(engine, &AITile::RaiseTile, "RaiseTile", 3, "xii"); + SQAITile.DefSQStaticMethod(engine, &AITile::LowerTile, "LowerTile", 3, "xii"); + SQAITile.DefSQStaticMethod(engine, &AITile::DemolishTile, "DemolishTile", 2, "xi"); + SQAITile.DefSQStaticMethod(engine, &AITile::PlantTree, "PlantTree", 2, "xi"); + SQAITile.DefSQStaticMethod(engine, &AITile::PlantTreeRectangle, "PlantTreeRectangle", 4, "xiii"); + SQAITile.DefSQStaticMethod(engine, &AITile::IsWithinTownInfluence, "IsWithinTownInfluence", 3, "xii"); + SQAITile.DefSQStaticMethod(engine, &AITile::GetClosestTown, "GetClosestTown", 2, "xi"); + + SQAITile.PostRegister(engine); +} diff --git a/src/ai/api/ai_tilelist.cpp b/src/ai/api/ai_tilelist.cpp new file mode 100644 index 000000000..24575c697 --- /dev/null +++ b/src/ai/api/ai_tilelist.cpp @@ -0,0 +1,175 @@ +/* $Id$ */ + +/** @file ai_tilelist.cpp Implementation of AITileList and friends. */ + +#include "ai_tilelist.hpp" +#include "ai_industry.hpp" +#include "../../openttd.h" +#include "../../landscape.h" +#include "../../settings_type.h" +#include "../../station_func.h" +#include "../../map_func.h" +#include "../../tile_map.h" +#include "../../industry_map.h" +#include "../../station_base.h" +#include "../../station_map.h" + +void AITileList::FixRectangleSpan(TileIndex &t1, TileIndex &t2) +{ + uint x1 = ::TileX(t1); + uint x2 = ::TileX(t2); + + uint y1 = ::TileY(t1); + uint y2 = ::TileY(t2); + + if (x1 >= x2) ::Swap(x1, x2); + if (y1 >= y2) ::Swap(y1, y2); + + t1 = ::TileXY(x1, y1); + t2 = ::TileXY(x2, y2); +} + +void AITileList::AddRectangle(TileIndex t1, TileIndex t2) +{ + if (!::IsValidTile(t1)) return; + if (!::IsValidTile(t2)) return; + + this->FixRectangleSpan(t1, t2); + + uint w = TileX(t2) - TileX(t1) + 1; + uint h = TileY(t2) - TileY(t1) + 1; + + BEGIN_TILE_LOOP(t, w, h, t1) { + this->AddItem(t); + } END_TILE_LOOP(t, w, h, t1) +} + +void AITileList::AddTile(TileIndex tile) +{ + if (!::IsValidTile(tile)) return; + + this->AddItem(tile); +} + +void AITileList::RemoveRectangle(TileIndex t1, TileIndex t2) +{ + if (!::IsValidTile(t1)) return; + if (!::IsValidTile(t2)) return; + + this->FixRectangleSpan(t1, t2); + + uint w = TileX(t2) - TileX(t1) + 1; + uint h = TileY(t2) - TileY(t1) + 1; + + BEGIN_TILE_LOOP(t, w, h, t1) { + this->RemoveItem(t); + } END_TILE_LOOP(t, w, h, t1) +} + +void AITileList::RemoveTile(TileIndex tile) +{ + if (!::IsValidTile(tile)) return; + + this->RemoveItem(tile); +} + +AITileList_IndustryAccepting::AITileList_IndustryAccepting(IndustryID industry_id, uint radius) +{ + if (!AIIndustry::IsValidIndustry(industry_id)) return; + + const Industry *i = ::GetIndustry(industry_id); + const IndustrySpec *indsp = ::GetIndustrySpec(i->type); + + /* Check if this industry accepts anything */ + { + bool cargo_accepts = false; + for (byte j = 0; j < lengthof(indsp->accepts_cargo); j++) { + if (indsp->accepts_cargo[j] != CT_INVALID) cargo_accepts = true; + } + if (!cargo_accepts) return; + } + + if (!_settings_game.station.modified_catchment) radius = CA_UNMODIFIED; + + BEGIN_TILE_LOOP(cur_tile, i->width + radius * 2, i->height + radius * 2, i->xy - ::TileDiffXY(radius, radius)) { + if (!::IsValidTile(cur_tile)) continue; + /* Exclude all tiles that belong to this industry */ + if (::IsTileType(cur_tile, MP_INDUSTRY) && ::GetIndustryIndex(cur_tile) == industry_id) continue; + + /* Only add the tile if it accepts the cargo (sometimes just 1 tile of an + * industry triggers the acceptance). */ + AcceptedCargo accepts; + ::GetAcceptanceAroundTiles(accepts, cur_tile, 1, 1, radius); + { + bool cargo_accepts = false; + for (byte j = 0; j < lengthof(indsp->accepts_cargo); j++) { + if (indsp->accepts_cargo[j] != CT_INVALID && accepts[indsp->accepts_cargo[j]] != 0) cargo_accepts = true; + } + if (!cargo_accepts) continue; + } + + this->AddTile(cur_tile); + } END_TILE_LOOP(cur_tile, i->width + radius * 2, i->height + radius * 2, i->xy - ::TileDiffXY(radius, radius)) +} + +AITileList_IndustryProducing::AITileList_IndustryProducing(IndustryID industry_id, uint radius) +{ + if (!AIIndustry::IsValidIndustry(industry_id)) return; + + const Industry *i = ::GetIndustry(industry_id); + const IndustrySpec *indsp = ::GetIndustrySpec(i->type); + + /* Check if this industry produces anything */ + { + bool cargo_produces = false; + for (byte j = 0; j < lengthof(indsp->produced_cargo); j++) { + if (indsp->produced_cargo[j] != CT_INVALID) cargo_produces = true; + } + if (!cargo_produces) return; + } + + if (!_settings_game.station.modified_catchment) radius = CA_UNMODIFIED; + + BEGIN_TILE_LOOP(cur_tile, i->width + radius * 2, i->height + radius * 2, i->xy - ::TileDiffXY(radius, radius)) { + if (!::IsValidTile(cur_tile)) continue; + /* Exclude all tiles that belong to this industry */ + if (::IsTileType(cur_tile, MP_INDUSTRY) && ::GetIndustryIndex(cur_tile) == industry_id) continue; + + /* Only add the tile if it produces the cargo (a bug in OpenTTD makes this + * inconsitance). */ + AcceptedCargo produces; + ::GetProductionAroundTiles(produces, cur_tile, 1, 1, radius); + { + bool cargo_produces = false; + for (byte j = 0; j < lengthof(indsp->produced_cargo); j++) { + if (indsp->produced_cargo[j] != CT_INVALID && produces[indsp->produced_cargo[j]] != 0) cargo_produces = true; + } + if (!cargo_produces) continue; + } + + this->AddTile(cur_tile); + } END_TILE_LOOP(cur_tile, i->width + radius * 2, i->height + radius * 2, i->xy - ::TileDiffXY(radius, radius)) +} + +AITileList_StationType::AITileList_StationType(StationID station_id, AIStation::StationType station_type) +{ + if (!AIStation::IsValidStation(station_id)) return; + + const StationRect *rect = &::GetStation(station_id)->rect; + + uint station_type_value = 0; + /* Convert AIStation::StationType to ::StationType, but do it in a + * bitmask, so we can scan for multiple entries at the same time. */ + if ((station_type & AIStation::STATION_TRAIN) != 0) station_type_value |= (1 << ::STATION_RAIL); + if ((station_type & AIStation::STATION_TRUCK_STOP) != 0) station_type_value |= (1 << ::STATION_TRUCK); + if ((station_type & AIStation::STATION_BUS_STOP) != 0) station_type_value |= (1 << ::STATION_BUS); + if ((station_type & AIStation::STATION_AIRPORT) != 0) station_type_value |= (1 << ::STATION_AIRPORT) | (1 << ::STATION_OILRIG); + if ((station_type & AIStation::STATION_DOCK) != 0) station_type_value |= (1 << ::STATION_DOCK) | (1 << ::STATION_OILRIG); + + BEGIN_TILE_LOOP(cur_tile, rect->right - rect->left + 1, rect->bottom - rect->top + 1, ::TileXY(rect->left, rect->top)) { + if (!::IsTileType(cur_tile, MP_STATION)) continue; + if (::GetStationIndex(cur_tile) != station_id) continue; + if (!HasBit(station_type_value, ::GetStationType(cur_tile))) continue; + this->AddTile(cur_tile); + } END_TILE_LOOP(cur_tile, rect->right - rect->left + 1, rect->bottom - rect->top + 1, ::TileXY(rect->left, rect->top)) +} diff --git a/src/ai/api/ai_tilelist.hpp b/src/ai/api/ai_tilelist.hpp new file mode 100644 index 000000000..f3d2f658d --- /dev/null +++ b/src/ai/api/ai_tilelist.hpp @@ -0,0 +1,110 @@ +/* $Id$ */ + +/** @file ai_tilelist.hpp List tiles. */ + +#ifndef AI_TILELIST_HPP +#define AI_TILELIST_HPP + +#include "ai_abstractlist.hpp" +#include "ai_station.hpp" + +/** + * Creates an empty list, in which you can add tiles. + * @ingroup AIList + */ +class AITileList : public AIAbstractList { +public: + static const char *GetClassName() { return "AITileList"; } + +private: + /** + * Make sure t1.x is smaller than t2.x and t1.y is smaller than t2.y. + * They are swapped to ensure they are after calling this function. + * @param t1 one of the corners of the rectangle. + * @param t2 the other corner of the rectangle. + */ + void FixRectangleSpan(TileIndex &t1, TileIndex &t2); + +public: + /** + * Adds the rectangle between tile_from and tile_to to the to-be-evaluated tiles. + * @param tile_from One corner of the tiles to add. + * @param tile_to The other corner of the tiles to add. + * @pre AIMap::IsValidTile(tile_from). + * @pre AIMap::IsValidTile(tile_to). + */ + void AddRectangle(TileIndex tile_from, TileIndex tile_to); + + /** + * Add a tile to the to-be-evaluated tiles. + * @param tile The tile to add. + * @pre AIMap::IsValidTile(tile). + */ + void AddTile(TileIndex tile); + + /** + * Remove the tiles inside the rectangle between tile_from and tile_to form the list. + * @param tile_from One corner of the tiles to remove. + * @param tile_to The other corner of the files to remove. + * @pre AIMap::IsValidTile(tile_from). + * @pre AIMap::IsValidTile(tile_to). + */ + void RemoveRectangle(TileIndex tile_from, TileIndex tile_to); + + /** + * Remove a tile from the list. + * @param tile The tile to remove. + * @pre AIMap::IsValidTile(tile). + */ + void RemoveTile(TileIndex tile); +}; + +/** + * Creates a list of tiles that will accept cargo for the given industry. + * @note If a simular industry is close, it might happen that this industry receives the cargo. + * @ingroup AIList + */ +class AITileList_IndustryAccepting : public AITileList { +public: + static const char *GetClassName() { return "AITileList_IndustryAccepting"; } + + /** + * @param industry_id The industry to create the AITileList around. + * @param radius The radius of the station you will be using. + */ + AITileList_IndustryAccepting(IndustryID industry_id, uint radius); +}; + +/** + * Creates a list of tiles which the industry checks to see if a station is + * there to receive cargo produced by this industry. + * @ingroup AIList + */ +class AITileList_IndustryProducing : public AITileList { +public: + static const char *GetClassName() { return "AITileList_IndustryProducing"; } + + /** + * @param industry_id The industry to create the AITileList around. + * @param radius The radius of the station you will be using. + */ + AITileList_IndustryProducing(IndustryID industry_id, uint radius); +}; + +/** + * Creates a list of tiles which have the requested StationType of the + * StationID. + * @ingroup AIList + */ +class AITileList_StationType : public AITileList { +public: + static const char *GetClassName() { return "AITileList_StationType"; } + + /** + * @param station_id The station to create the AITileList for. + * @param station_type The StationType to create the AIList for. + */ + AITileList_StationType(StationID station_id, AIStation::StationType station_type); +}; + +#endif /* AI_TILELIST_HPP */ diff --git a/src/ai/api/ai_tilelist.hpp.sq b/src/ai/api/ai_tilelist.hpp.sq new file mode 100644 index 000000000..4e6bfdf30 --- /dev/null +++ b/src/ai/api/ai_tilelist.hpp.sq @@ -0,0 +1,85 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_tilelist.hpp" + +namespace SQConvert { + /* Allow AITileList to be used as Squirrel parameter */ + template <> AITileList *GetParam(ForceType<AITileList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AITileList *)instance; } + template <> AITileList &GetParam(ForceType<AITileList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AITileList *)instance; } + template <> const AITileList *GetParam(ForceType<const AITileList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AITileList *)instance; } + template <> const AITileList &GetParam(ForceType<const AITileList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AITileList *)instance; } + template <> int Return<AITileList *>(HSQUIRRELVM vm, AITileList *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AITileList", res, NULL, DefSQDestructorCallback<AITileList>); return 1; } +}; // namespace SQConvert + +void SQAITileList_Register(Squirrel *engine) { + DefSQClass <AITileList> SQAITileList("AITileList"); + SQAITileList.PreRegister(engine, "AIAbstractList"); + SQAITileList.AddConstructor<void (AITileList::*)(), 1>(engine, "x"); + + SQAITileList.DefSQStaticMethod(engine, &AITileList::GetClassName, "GetClassName", 1, "x"); + + SQAITileList.DefSQMethod(engine, &AITileList::AddRectangle, "AddRectangle", 3, "xii"); + SQAITileList.DefSQMethod(engine, &AITileList::AddTile, "AddTile", 2, "xi"); + SQAITileList.DefSQMethod(engine, &AITileList::RemoveRectangle, "RemoveRectangle", 3, "xii"); + SQAITileList.DefSQMethod(engine, &AITileList::RemoveTile, "RemoveTile", 2, "xi"); + + SQAITileList.PostRegister(engine); +} + +namespace SQConvert { + /* Allow AITileList_IndustryAccepting to be used as Squirrel parameter */ + template <> AITileList_IndustryAccepting *GetParam(ForceType<AITileList_IndustryAccepting *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AITileList_IndustryAccepting *)instance; } + template <> AITileList_IndustryAccepting &GetParam(ForceType<AITileList_IndustryAccepting &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AITileList_IndustryAccepting *)instance; } + template <> const AITileList_IndustryAccepting *GetParam(ForceType<const AITileList_IndustryAccepting *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AITileList_IndustryAccepting *)instance; } + template <> const AITileList_IndustryAccepting &GetParam(ForceType<const AITileList_IndustryAccepting &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AITileList_IndustryAccepting *)instance; } + template <> int Return<AITileList_IndustryAccepting *>(HSQUIRRELVM vm, AITileList_IndustryAccepting *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AITileList_IndustryAccepting", res, NULL, DefSQDestructorCallback<AITileList_IndustryAccepting>); return 1; } +}; // namespace SQConvert + +void SQAITileList_IndustryAccepting_Register(Squirrel *engine) { + DefSQClass <AITileList_IndustryAccepting> SQAITileList_IndustryAccepting("AITileList_IndustryAccepting"); + SQAITileList_IndustryAccepting.PreRegister(engine, "AITileList"); + SQAITileList_IndustryAccepting.AddConstructor<void (AITileList_IndustryAccepting::*)(IndustryID industry_id, uint radius), 3>(engine, "xii"); + + SQAITileList_IndustryAccepting.DefSQStaticMethod(engine, &AITileList_IndustryAccepting::GetClassName, "GetClassName", 1, "x"); + + SQAITileList_IndustryAccepting.PostRegister(engine); +} + +namespace SQConvert { + /* Allow AITileList_IndustryProducing to be used as Squirrel parameter */ + template <> AITileList_IndustryProducing *GetParam(ForceType<AITileList_IndustryProducing *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AITileList_IndustryProducing *)instance; } + template <> AITileList_IndustryProducing &GetParam(ForceType<AITileList_IndustryProducing &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AITileList_IndustryProducing *)instance; } + template <> const AITileList_IndustryProducing *GetParam(ForceType<const AITileList_IndustryProducing *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AITileList_IndustryProducing *)instance; } + template <> const AITileList_IndustryProducing &GetParam(ForceType<const AITileList_IndustryProducing &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AITileList_IndustryProducing *)instance; } + template <> int Return<AITileList_IndustryProducing *>(HSQUIRRELVM vm, AITileList_IndustryProducing *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AITileList_IndustryProducing", res, NULL, DefSQDestructorCallback<AITileList_IndustryProducing>); return 1; } +}; // namespace SQConvert + +void SQAITileList_IndustryProducing_Register(Squirrel *engine) { + DefSQClass <AITileList_IndustryProducing> SQAITileList_IndustryProducing("AITileList_IndustryProducing"); + SQAITileList_IndustryProducing.PreRegister(engine, "AITileList"); + SQAITileList_IndustryProducing.AddConstructor<void (AITileList_IndustryProducing::*)(IndustryID industry_id, uint radius), 3>(engine, "xii"); + + SQAITileList_IndustryProducing.DefSQStaticMethod(engine, &AITileList_IndustryProducing::GetClassName, "GetClassName", 1, "x"); + + SQAITileList_IndustryProducing.PostRegister(engine); +} + +namespace SQConvert { + /* Allow AITileList_StationType to be used as Squirrel parameter */ + template <> AITileList_StationType *GetParam(ForceType<AITileList_StationType *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AITileList_StationType *)instance; } + template <> AITileList_StationType &GetParam(ForceType<AITileList_StationType &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AITileList_StationType *)instance; } + template <> const AITileList_StationType *GetParam(ForceType<const AITileList_StationType *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AITileList_StationType *)instance; } + template <> const AITileList_StationType &GetParam(ForceType<const AITileList_StationType &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AITileList_StationType *)instance; } + template <> int Return<AITileList_StationType *>(HSQUIRRELVM vm, AITileList_StationType *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AITileList_StationType", res, NULL, DefSQDestructorCallback<AITileList_StationType>); return 1; } +}; // namespace SQConvert + +void SQAITileList_StationType_Register(Squirrel *engine) { + DefSQClass <AITileList_StationType> SQAITileList_StationType("AITileList_StationType"); + SQAITileList_StationType.PreRegister(engine, "AITileList"); + SQAITileList_StationType.AddConstructor<void (AITileList_StationType::*)(StationID station_id, AIStation::StationType station_type), 3>(engine, "xii"); + + SQAITileList_StationType.DefSQStaticMethod(engine, &AITileList_StationType::GetClassName, "GetClassName", 1, "x"); + + SQAITileList_StationType.PostRegister(engine); +} diff --git a/src/ai/api/ai_town.cpp b/src/ai/api/ai_town.cpp new file mode 100644 index 000000000..8c8ab9c02 --- /dev/null +++ b/src/ai/api/ai_town.cpp @@ -0,0 +1,197 @@ +/* $Id$ */ + +/** @file ai_town.cpp Implementation of AITown. */ + +#include "ai_town.hpp" +#include "ai_map.hpp" +#include "ai_cargo.hpp" +#include "ai_error.hpp" +#include "../../command_type.h" +#include "../../openttd.h" +#include "../../town.h" +#include "../../strings_func.h" +#include "../../core/alloc_func.hpp" +#include "../../company_func.h" +#include "../../station_base.h" +#include "table/strings.h" + +/* static */ TownID AITown::GetMaxTownID() +{ + return ::GetMaxTownIndex(); +} + +/* static */ int32 AITown::GetTownCount() +{ + return ::GetNumTowns(); +} + +/* static */ bool AITown::IsValidTown(TownID town_id) +{ + return ::IsValidTownID(town_id); +} + +/* static */ const char *AITown::GetName(TownID town_id) +{ + if (!IsValidTown(town_id)) return NULL; + static const int len = 64; + char *town_name = MallocT<char>(len); + + ::SetDParam(0, town_id); + ::GetString(town_name, STR_TOWN, &town_name[len - 1]); + + return town_name; +} + +/* static */ int32 AITown::GetPopulation(TownID town_id) +{ + if (!IsValidTown(town_id)) return -1; + const Town *t = ::GetTown(town_id); + return t->population; +} + +/* static */ int32 AITown::GetHouseCount(TownID town_id) +{ + if (!IsValidTown(town_id)) return -1; + const Town *t = ::GetTown(town_id); + return t->num_houses; +} + +/* static */ TileIndex AITown::GetLocation(TownID town_id) +{ + if (!IsValidTown(town_id)) return INVALID_TILE; + const Town *t = ::GetTown(town_id); + return t->xy; +} + +/* static */ int32 AITown::GetLastMonthProduction(TownID town_id, CargoID cargo_id) +{ + if (!IsValidTown(town_id)) return -1; + if (!AICargo::IsValidCargo(cargo_id)) return -1; + + const Town *t = ::GetTown(town_id); + + switch(AICargo::GetTownEffect(cargo_id)) { + case AICargo::TE_PASSENGERS: return t->act_pass; + case AICargo::TE_MAIL: return t->act_mail; + default: return -1; + } +} + +/* static */ int32 AITown::GetLastMonthTransported(TownID town_id, CargoID cargo_id) +{ + if (!IsValidTown(town_id)) return -1; + if (!AICargo::IsValidCargo(cargo_id)) return -1; + + const Town *t = ::GetTown(town_id); + + switch(AICargo::GetTownEffect(cargo_id)) { + case AICargo::TE_PASSENGERS: return t->pct_pass_transported; + case AICargo::TE_MAIL: return t->pct_mail_transported; + default: return -1; + } +} + +/* static */ int32 AITown::GetMaxProduction(TownID town_id, CargoID cargo_id) +{ + if (!IsValidTown(town_id)) return -1; + if (!AICargo::IsValidCargo(cargo_id)) return -1; + + const Town *t = ::GetTown(town_id); + + switch(AICargo::GetTownEffect(cargo_id)) { + case AICargo::TE_PASSENGERS: return t->max_pass; + case AICargo::TE_MAIL: return t->max_mail; + default: return -1; + } +} + +/* static */ int32 AITown::GetDistanceManhattanToTile(TownID town_id, TileIndex tile) +{ + return AIMap::DistanceManhattan(tile, GetLocation(town_id)); +} + +/* static */ int32 AITown::GetDistanceSquareToTile(TownID town_id, TileIndex tile) +{ + return AIMap::DistanceSquare(tile, GetLocation(town_id)); +} + +/* static */ bool AITown::IsWithinTownInfluence(TownID town_id, TileIndex tile) +{ + if (!IsValidTown(town_id)) return false; + + const Town *t = ::GetTown(town_id); + return ((uint32)GetDistanceSquareToTile(town_id, tile) <= t->squared_town_zone_radius[0]); +} + +/* static */ bool AITown::HasStatue(TownID town_id) +{ + if (!IsValidTown(town_id)) return false; + + return ::HasBit(::GetTown(town_id)->statues, _current_company); +} + +/* static */ int AITown::GetRoadReworkDuration(TownID town_id) +{ + if (!IsValidTown(town_id)) return -1; + + return ::GetTown(town_id)->road_build_months; +} + +/* static */ AICompany::CompanyID AITown::GetExclusiveRightsCompany(TownID town_id) +{ + if (!IsValidTown(town_id)) return AICompany::INVALID_COMPANY; + + return (AICompany::CompanyID)(int8)::GetTown(town_id)->exclusivity; +} + +/* static */ int32 AITown::GetExclusiveRightsDuration(TownID town_id) +{ + if (!IsValidTown(town_id)) return -1; + + return ::GetTown(town_id)->exclusive_counter; +} + +extern uint GetMaskOfTownActions(int *nump, CompanyID cid, const Town *t); + +/* static */ bool AITown::IsActionAvailable(TownID town_id, TownAction town_action) +{ + if (!IsValidTown(town_id)) return false; + + return HasBit(::GetMaskOfTownActions(NULL, _current_company, ::GetTown(town_id)), town_action); +} + +/* static */ bool AITown::PerformTownAction(TownID town_id, TownAction town_action) +{ + EnforcePrecondition(false, IsValidTown(town_id)); + EnforcePrecondition(false, IsActionAvailable(town_id, town_action)); + + return AIObject::DoCommand(::GetTown(town_id)->xy, town_id, town_action, CMD_DO_TOWN_ACTION); +} + +/* static */ AITown::TownRating AITown::GetRating(TownID town_id, AICompany::CompanyID company_id) +{ + if (!IsValidTown(town_id)) return INVALID_TOWN_RATING; + AICompany::CompanyID company = AICompany::ResolveCompanyID(company_id); + if (company == AICompany::INVALID_COMPANY) return INVALID_TOWN_RATING; + + const Town *t = ::GetTown(town_id); + if (!HasBit(t->have_ratings, company)) return TOWN_RATING_NONE; + return max(TOWN_RATING_APPALLING, (TownRating)((t->ratings[company] / 200) + 3)); +} + +/* static */ int AITown::GetAllowedNoise(TownID town_id) +{ + if (!IsValidTown(town_id)) return -1; + + const Town *t = ::GetTown(town_id); + if (_settings_game.economy.station_noise_level) { + return t->MaxTownNoise() - t->noise_reached; + } + + int num = 0; + const Station *st; + FOR_ALL_STATIONS(st) { + if (st->town == t && st->facilities & FACIL_AIRPORT && st->airport_type != AT_OILRIG) num++; + } + return max(0, 2 - num); +} diff --git a/src/ai/api/ai_town.hpp b/src/ai/api/ai_town.hpp new file mode 100644 index 000000000..94ba91e17 --- /dev/null +++ b/src/ai/api/ai_town.hpp @@ -0,0 +1,284 @@ +/* $Id$ */ + +/** @file ai_town.hpp Everything to query towns. */ + +#ifndef AI_TOWN_HPP +#define AI_TOWN_HPP + +#include "ai_object.hpp" +#include "ai_company.hpp" + +/** + * Class that handles all town related functions. + */ +class AITown : public AIObject { +public: + static const char *GetClassName() { return "AITown"; } + + /** + * Actions that one can perform on a town. + */ + enum TownAction { + /* Values are important, as they represent the internal state of the game. */ + + /** + * The cargo ratings temporary gains 25% of rating (in + * absolute percentage, so 10% becomes 35%, with a max of 99%) + * for all stations within 10 tiles. + */ + TOWN_ACTION_ADVERTISE_SMALL = 0, + + /** + * The cargo ratings temporary gains 44% of rating (in + * absolute percentage, so 10% becomes 54%, with a max of 99%) + * for all stations within 15 tiles. + */ + TOWN_ACTION_ADVERTISE_MEDIUM = 1, + + /** + * The cargo ratings temporary gains 63% of rating (in + * absolute percentage, so 10% becomes 73%, with a max of 99%) + * for all stations within 20 tiles. + */ + TOWN_ACTION_ADVERTISE_LARGE = 2, + + /** + * Rebuild the roads of this town for 6 months. + */ + TOWN_ACTION_ROAD_REBUILD = 3, + + /** + * Build a statue in this town. + */ + TOWN_ACTION_BUILD_STATUE = 4, + + /** + * Fund the creation of extra buildings for 3 months. + */ + TOWN_ACTION_FUND_BUILDINGS = 5, + + /** + * Buy exclusive rights for this town for 12 months. + */ + TOWN_ACTION_BUY_RIGHTS = 6, + + /** + * Bribe the town in order to get a higher rating. + */ + TOWN_ACTION_BRIBE = 7, + }; + + /** + * Different ratings one could have in a town. + */ + enum TownRating { + TOWN_RATING_NONE, ///< The company got no rating in the town. + TOWN_RATING_APPALLING, ///< The company got an appalling rating in the town . + TOWN_RATING_VERY_POOR, ///< The company got an very poor rating in the town. + TOWN_RATING_POOR, ///< The company got an poor rating in the town. + TOWN_RATING_MEDIOCRE, ///< The company got an mediocre rating in the town. + TOWN_RATING_GOOD, ///< The company got an good rating in the town. + TOWN_RATING_VERY_GOOD, ///< The company got an very good rating in the town. + TOWN_RATING_EXCELLENT, ///< The company got an excellent rating in the town. + TOWN_RATING_OUTSTANDING, ///< The company got an outstanding rating in the town. + INVALID_TOWN_RATING = -1, ///< The town rating for invalid towns/companies. + }; + + /** + * Gets the maximum town index; there are no valid towns with a higher index. + * @return The maximum town index. + * @post Return value is always non-negative. + */ + static TownID GetMaxTownID(); + + /** + * Gets the number of towns. This is different than GetMaxTownID() + * because of the way OpenTTD works internally. + * @return The number of towns. + * @post Return value is always non-negative. + */ + static int32 GetTownCount(); + + /** + * Checks whether the given town index is valid. + * @param town_id The index to check. + * @return True if and only if the town is valid. + */ + static bool IsValidTown(TownID town_id); + + /** + * Get the name of the town. + * @param town_id The town to get the name of. + * @pre IsValidTown(town_id). + * @return The name of the town. + */ + static const char *GetName(TownID town_id); + + /** + * Gets the number of inhabitants in the town. + * @param town_id The town to get the population of. + * @pre IsValidTown(town_id). + * @return The number of inhabitants. + * @post Return value is always non-negative. + */ + static int32 GetPopulation(TownID town_id); + + /** + * Gets the number of houses in the town. + * @param town_id The town to get the number of houses of. + * @pre IsValidTown(town_id). + * @return The number of houses. + * @post Return value is always non-negative. + */ + static int32 GetHouseCount(TownID town_id); + + /** + * Gets the location of the town. + * @param town_id The town to get the location of. + * @pre IsValidTown(town_id). + * @return The location of the town. + */ + static TileIndex GetLocation(TownID town_id); + + /** + * Get the total last month's production of the given cargo at a town. + * @param town_id The index of the town. + * @param cargo_id The index of the cargo. + * @pre IsValidTown(town_id). + * @pre AICargo::IsValidCargo(cargo_id). + * @pre AICargo::GetTownEffect(cargo_id) == TE_PASSENGERS || AICargo::GetTownEffect(cargo_id) == TE_MAIL. + * @return The last month's production of the given cargo for this town. + * @post Return value is always non-negative. + */ + static int32 GetLastMonthProduction(TownID town_id, CargoID cargo_id); + + /** + * Get the total amount of cargo transported from a town last month. + * @param town_id The index of the industry. + * @param cargo_id The index of the cargo. + * @pre IsValidTown(town_id). + * @pre AICargo::IsValidCargo(cargo_id). + * @pre AICargo::GetTownEffect(cargo_id) == TE_PASSENGERS || AICargo::GetTownEffect(cargo_id) == TE_MAIL. + * @return The amount of given cargo transported from this town last month. + * @post Return value is always non-negative. + */ + static int32 GetLastMonthTransported(TownID town_id, CargoID cargo_id); + + /** + * Get the maximum production of the given cargo at a town. + * @param town_id The index of the town. + * @param cargo_id The index of the cargo. + * @pre IsValidTown(town_id). + * @pre AICargo::IsValidCargo(cargo_id). + * @pre AICargo::GetTownEffect(cargo_id) == TE_PASSENGERS || AICargo::GetTownEffect(cargo_id) == TE_MAIL. + * @return The maximum production of the given cargo for this town. + * @post Return value is always non-negative. + */ + static int32 GetMaxProduction(TownID town_id, CargoID cargo_id); + + /** + * Get the manhattan distance from the tile to the AITown::GetLocation() + * of the town. + * @param town_id The town to get the distance to. + * @param tile The tile to get the distance to. + * @pre IsValidTown(town_id). + * @return The distance between town and tile. + */ + static int32 GetDistanceManhattanToTile(TownID town_id, TileIndex tile); + + /** + * Get the square distance from the tile to the AITown::GetLocation() + * of the town. + * @param town_id The town to get the distance to. + * @param tile The tile to get the distance to. + * @pre IsValidTown(town_id). + * @return The distance between town and tile. + */ + static int32 GetDistanceSquareToTile(TownID town_id, TileIndex tile); + + /** + * Find out if this tile is within the rating influence of a town. + * Stations on this tile influence the rating of the town. + * @param town_id The town to check. + * @param tile The tile to check. + * @pre IsValidTown(town_id). + * @return True if the tile is within the rating influence of the town. + */ + static bool IsWithinTownInfluence(TownID town_id, TileIndex tile); + + /** + * Find out if this town has a statue for the current company. + * @param town_id The town to check. + * @pre IsValidTown(town_id). + * @return True if the town has a statue. + */ + static bool HasStatue(TownID town_id); + + /** + * Find out how long the town is undergoing road reconstructions. + * @param town_id The town to check. + * @pre IsValidTown(town_id). + * @return The number of months the road reworks are still going to take. + * The value 0 means that there are currently no road reworks. + */ + static int GetRoadReworkDuration(TownID town_id); + + /** + * Find out which company currently has the exclusive rights of this town. + * @param town_id The town to check. + * @pre IsValidTown(town_id). + * @return The company that has the exclusive rights. The value + * AICompany::INVALID_COMPANY means that there are currently no + * exclusive rights given out to anyone. + */ + static AICompany::CompanyID GetExclusiveRightsCompany(TownID town_id); + + /** + * Find out how long the town is under influence of the exclusive rights. + * @param town_id The town to check. + * @pre IsValidTown(town_id). + * @return The number of months the exclusive rights hold. + * The value 0 means that there are currently no exclusive rights + * given out to anyone. + */ + static int32 GetExclusiveRightsDuration(TownID town_id); + + /** + * Find out if an action can currently be performed on the town. + * @param town_id The town to perform the action on. + * @param town_action The action to perform on the town. + * @pre IsValidTown(town_id). + * @return True if and only if the action can performed. + */ + static bool IsActionAvailable(TownID town_id, TownAction town_action); + + /** + * Perform a town action on this town. + * @param town_id The town to perform the action on. + * @param town_action The action to perform on the town. + * @pre IsValidTown(town_id). + * @pre IsActionAvailable(town_id, town_action). + * @return True if the action succeeded. + */ + static bool PerformTownAction(TownID town_id, TownAction town_action); + + /** + * Get the rating of a company within a town. + * @param town_id The town to get the rating for. + * @param company_id The company to get the rating for. + * @pre IsValidTown(town_id). + * @pre AICompany.ResolveCompanyID(company) != AICompany::INVALID_COMPANY. + * @return The rating as shown to humans. + */ + static TownRating GetRating(TownID town_id, AICompany::CompanyID company_id); + + /** + * Get the maximum level of noise that still can be added by airports + * before the town start to refuse building a new airport. + * @param town_id The town to get the allowed noise from. + * @return The noise that still can be added. + */ + static int GetAllowedNoise(TownID town_id); +}; + +#endif /* AI_TOWN_HPP */ diff --git a/src/ai/api/ai_town.hpp.sq b/src/ai/api/ai_town.hpp.sq new file mode 100644 index 000000000..664503923 --- /dev/null +++ b/src/ai/api/ai_town.hpp.sq @@ -0,0 +1,69 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_town.hpp" + +namespace SQConvert { + /* Allow enums to be used as Squirrel parameters */ + template <> AITown::TownAction GetParam(ForceType<AITown::TownAction>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AITown::TownAction)tmp; } + template <> int Return<AITown::TownAction>(HSQUIRRELVM vm, AITown::TownAction res) { sq_pushinteger(vm, (int32)res); return 1; } + template <> AITown::TownRating GetParam(ForceType<AITown::TownRating>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AITown::TownRating)tmp; } + template <> int Return<AITown::TownRating>(HSQUIRRELVM vm, AITown::TownRating res) { sq_pushinteger(vm, (int32)res); return 1; } + + /* Allow AITown to be used as Squirrel parameter */ + template <> AITown *GetParam(ForceType<AITown *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AITown *)instance; } + template <> AITown &GetParam(ForceType<AITown &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AITown *)instance; } + template <> const AITown *GetParam(ForceType<const AITown *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AITown *)instance; } + template <> const AITown &GetParam(ForceType<const AITown &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AITown *)instance; } + template <> int Return<AITown *>(HSQUIRRELVM vm, AITown *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AITown", res, NULL, DefSQDestructorCallback<AITown>); return 1; } +}; // namespace SQConvert + +void SQAITown_Register(Squirrel *engine) { + DefSQClass <AITown> SQAITown("AITown"); + SQAITown.PreRegister(engine); + SQAITown.AddConstructor<void (AITown::*)(), 1>(engine, "x"); + + SQAITown.DefSQConst(engine, AITown::TOWN_ACTION_ADVERTISE_SMALL, "TOWN_ACTION_ADVERTISE_SMALL"); + SQAITown.DefSQConst(engine, AITown::TOWN_ACTION_ADVERTISE_MEDIUM, "TOWN_ACTION_ADVERTISE_MEDIUM"); + SQAITown.DefSQConst(engine, AITown::TOWN_ACTION_ADVERTISE_LARGE, "TOWN_ACTION_ADVERTISE_LARGE"); + SQAITown.DefSQConst(engine, AITown::TOWN_ACTION_ROAD_REBUILD, "TOWN_ACTION_ROAD_REBUILD"); + SQAITown.DefSQConst(engine, AITown::TOWN_ACTION_BUILD_STATUE, "TOWN_ACTION_BUILD_STATUE"); + SQAITown.DefSQConst(engine, AITown::TOWN_ACTION_FUND_BUILDINGS, "TOWN_ACTION_FUND_BUILDINGS"); + SQAITown.DefSQConst(engine, AITown::TOWN_ACTION_BUY_RIGHTS, "TOWN_ACTION_BUY_RIGHTS"); + SQAITown.DefSQConst(engine, AITown::TOWN_ACTION_BRIBE, "TOWN_ACTION_BRIBE"); + SQAITown.DefSQConst(engine, AITown::TOWN_RATING_NONE, "TOWN_RATING_NONE"); + SQAITown.DefSQConst(engine, AITown::TOWN_RATING_APPALLING, "TOWN_RATING_APPALLING"); + SQAITown.DefSQConst(engine, AITown::TOWN_RATING_VERY_POOR, "TOWN_RATING_VERY_POOR"); + SQAITown.DefSQConst(engine, AITown::TOWN_RATING_POOR, "TOWN_RATING_POOR"); + SQAITown.DefSQConst(engine, AITown::TOWN_RATING_MEDIOCRE, "TOWN_RATING_MEDIOCRE"); + SQAITown.DefSQConst(engine, AITown::TOWN_RATING_GOOD, "TOWN_RATING_GOOD"); + SQAITown.DefSQConst(engine, AITown::TOWN_RATING_VERY_GOOD, "TOWN_RATING_VERY_GOOD"); + SQAITown.DefSQConst(engine, AITown::TOWN_RATING_EXCELLENT, "TOWN_RATING_EXCELLENT"); + SQAITown.DefSQConst(engine, AITown::TOWN_RATING_OUTSTANDING, "TOWN_RATING_OUTSTANDING"); + SQAITown.DefSQConst(engine, AITown::INVALID_TOWN_RATING, "INVALID_TOWN_RATING"); + + SQAITown.DefSQStaticMethod(engine, &AITown::GetClassName, "GetClassName", 1, "x"); + SQAITown.DefSQStaticMethod(engine, &AITown::GetMaxTownID, "GetMaxTownID", 1, "x"); + SQAITown.DefSQStaticMethod(engine, &AITown::GetTownCount, "GetTownCount", 1, "x"); + SQAITown.DefSQStaticMethod(engine, &AITown::IsValidTown, "IsValidTown", 2, "xi"); + SQAITown.DefSQStaticMethod(engine, &AITown::GetName, "GetName", 2, "xi"); + SQAITown.DefSQStaticMethod(engine, &AITown::GetPopulation, "GetPopulation", 2, "xi"); + SQAITown.DefSQStaticMethod(engine, &AITown::GetHouseCount, "GetHouseCount", 2, "xi"); + SQAITown.DefSQStaticMethod(engine, &AITown::GetLocation, "GetLocation", 2, "xi"); + SQAITown.DefSQStaticMethod(engine, &AITown::GetLastMonthProduction, "GetLastMonthProduction", 3, "xii"); + SQAITown.DefSQStaticMethod(engine, &AITown::GetLastMonthTransported, "GetLastMonthTransported", 3, "xii"); + SQAITown.DefSQStaticMethod(engine, &AITown::GetMaxProduction, "GetMaxProduction", 3, "xii"); + SQAITown.DefSQStaticMethod(engine, &AITown::GetDistanceManhattanToTile, "GetDistanceManhattanToTile", 3, "xii"); + SQAITown.DefSQStaticMethod(engine, &AITown::GetDistanceSquareToTile, "GetDistanceSquareToTile", 3, "xii"); + SQAITown.DefSQStaticMethod(engine, &AITown::IsWithinTownInfluence, "IsWithinTownInfluence", 3, "xii"); + SQAITown.DefSQStaticMethod(engine, &AITown::HasStatue, "HasStatue", 2, "xi"); + SQAITown.DefSQStaticMethod(engine, &AITown::GetRoadReworkDuration, "GetRoadReworkDuration", 2, "xi"); + SQAITown.DefSQStaticMethod(engine, &AITown::GetExclusiveRightsCompany, "GetExclusiveRightsCompany", 2, "xi"); + SQAITown.DefSQStaticMethod(engine, &AITown::GetExclusiveRightsDuration, "GetExclusiveRightsDuration", 2, "xi"); + SQAITown.DefSQStaticMethod(engine, &AITown::IsActionAvailable, "IsActionAvailable", 3, "xii"); + SQAITown.DefSQStaticMethod(engine, &AITown::PerformTownAction, "PerformTownAction", 3, "xii"); + SQAITown.DefSQStaticMethod(engine, &AITown::GetRating, "GetRating", 3, "xii"); + SQAITown.DefSQStaticMethod(engine, &AITown::GetAllowedNoise, "GetAllowedNoise", 2, "xi"); + + SQAITown.PostRegister(engine); +} diff --git a/src/ai/api/ai_townlist.cpp b/src/ai/api/ai_townlist.cpp new file mode 100644 index 000000000..34612d857 --- /dev/null +++ b/src/ai/api/ai_townlist.cpp @@ -0,0 +1,15 @@ +/* $Id$ */ + +/** @file ai_townlist.cpp Implementation of AITownList and friends. */ + +#include "ai_townlist.hpp" +#include "../../openttd.h" +#include "../../town.h" + +AITownList::AITownList() +{ + Town *t; + FOR_ALL_TOWNS(t) { + this->AddItem(t->index); + } +} diff --git a/src/ai/api/ai_townlist.hpp b/src/ai/api/ai_townlist.hpp new file mode 100644 index 000000000..0e2eb1530 --- /dev/null +++ b/src/ai/api/ai_townlist.hpp @@ -0,0 +1,20 @@ +/* $Id$ */ + +/** @file ai_townlist.hpp List all the towns. */ + +#ifndef AI_TOWNLIST_HPP +#define AI_TOWNLIST_HPP + +#include "ai_abstractlist.hpp" + +/** + * Creates a list of towns that are currently on the map. + * @ingroup AIList + */ +class AITownList : public AIAbstractList { +public: + static const char *GetClassName() { return "AITownList"; } + AITownList(); +}; + +#endif /* AI_TOWNLIST_HPP */ diff --git a/src/ai/api/ai_townlist.hpp.sq b/src/ai/api/ai_townlist.hpp.sq new file mode 100644 index 000000000..31be65144 --- /dev/null +++ b/src/ai/api/ai_townlist.hpp.sq @@ -0,0 +1,23 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_townlist.hpp" + +namespace SQConvert { + /* Allow AITownList to be used as Squirrel parameter */ + template <> AITownList *GetParam(ForceType<AITownList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AITownList *)instance; } + template <> AITownList &GetParam(ForceType<AITownList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AITownList *)instance; } + template <> const AITownList *GetParam(ForceType<const AITownList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AITownList *)instance; } + template <> const AITownList &GetParam(ForceType<const AITownList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AITownList *)instance; } + template <> int Return<AITownList *>(HSQUIRRELVM vm, AITownList *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AITownList", res, NULL, DefSQDestructorCallback<AITownList>); return 1; } +}; // namespace SQConvert + +void SQAITownList_Register(Squirrel *engine) { + DefSQClass <AITownList> SQAITownList("AITownList"); + SQAITownList.PreRegister(engine, "AIAbstractList"); + SQAITownList.AddConstructor<void (AITownList::*)(), 1>(engine, "x"); + + SQAITownList.DefSQStaticMethod(engine, &AITownList::GetClassName, "GetClassName", 1, "x"); + + SQAITownList.PostRegister(engine); +} diff --git a/src/ai/api/ai_tunnel.cpp b/src/ai/api/ai_tunnel.cpp new file mode 100644 index 000000000..1991ef89b --- /dev/null +++ b/src/ai/api/ai_tunnel.cpp @@ -0,0 +1,119 @@ +/* $Id$ */ + +/** @file ai_tunnel.cpp Implementation of AITunnel. */ + +#include "ai_tunnel.hpp" +#include "ai_map.hpp" +#include "ai_rail.hpp" +#include "../ai_instance.hpp" +#include "../../openttd.h" +#include "../../landscape.h" +#include "../../tunnel_map.h" +#include "../../road_type.h" +#include "../../command_func.h" +#include "../../tunnelbridge.h" +#include "../../road_func.h" + +/* static */ bool AITunnel::IsTunnelTile(TileIndex tile) +{ + if (!::IsValidTile(tile)) return false; + return ::IsTunnelTile(tile); +} + +/* static */ TileIndex AITunnel::GetOtherTunnelEnd(TileIndex tile) +{ + if (!::IsValidTile(tile)) return INVALID_TILE; + + /* If it's a tunnel alread, take the easy way out! */ + if (IsTunnelTile(tile)) return ::GetOtherTunnelEnd(tile); + + ::DoCommand(tile, 0, 0, DC_AUTO, CMD_BUILD_TUNNEL); + return _build_tunnel_endtile == 0 ? INVALID_TILE : _build_tunnel_endtile; +} + +static void _DoCommandReturnBuildTunnel2(class AIInstance *instance) +{ + if (!AITunnel::_BuildTunnelRoad2()) { + AIObject::SetLastCommandRes(false); + AIInstance::DoCommandReturn(instance); + return; + } + + /* This can never happen, as in test-mode this callback is never executed, + * and in execute-mode, the other callback is called. */ + NOT_REACHED(); +} + +static void _DoCommandReturnBuildTunnel1(class AIInstance *instance) +{ + if (!AITunnel::_BuildTunnelRoad1()) { + AIObject::SetLastCommandRes(false); + AIInstance::DoCommandReturn(instance); + return; + } + + /* This can never happen, as in test-mode this callback is never executed, + * and in execute-mode, the other callback is called. */ + NOT_REACHED(); +} + +/* static */ bool AITunnel::BuildTunnel(AIVehicle::VehicleType vehicle_type, TileIndex start) +{ + EnforcePrecondition(false, ::IsValidTile(start)); + EnforcePrecondition(false, vehicle_type == AIVehicle::VEHICLE_RAIL || vehicle_type == AIVehicle::VEHICLE_ROAD); + EnforcePrecondition(false, vehicle_type != AIVehicle::VEHICLE_RAIL || AIRail::IsRailTypeAvailable(AIRail::GetCurrentRailType())); + + uint type = 0; + if (vehicle_type == AIVehicle::VEHICLE_ROAD) { + type |= (TRANSPORT_ROAD << 9); + type |= RoadTypeToRoadTypes((::RoadType)AIObject::GetRoadType()); + } else { + type |= (TRANSPORT_RAIL << 9); + type |= AIRail::GetCurrentRailType(); + } + + /* For rail we do nothing special */ + if (vehicle_type == AIVehicle::VEHICLE_RAIL) { + return AIObject::DoCommand(start, type, 0, CMD_BUILD_TUNNEL); + } + + AIObject::SetCallbackVariable(0, start); + if (!AIObject::DoCommand(start, type, 0, CMD_BUILD_TUNNEL, NULL, &_DoCommandReturnBuildTunnel1)) return false; + + /* In case of test-mode, test if we can build both road pieces */ + return _BuildTunnelRoad1(); +} + +/* static */ bool AITunnel::_BuildTunnelRoad1() +{ + /* Build the piece of road on the 'start' side of the tunnel */ + TileIndex end = AIObject::GetCallbackVariable(0); + TileIndex start = AITunnel::GetOtherTunnelEnd(end); + + DiagDirection dir_1 = (DiagDirection)((::TileX(start) == ::TileX(end)) ? (::TileY(start) < ::TileY(end) ? DIAGDIR_NW : DIAGDIR_SE) : (::TileX(start) < ::TileX(end) ? DIAGDIR_NE : DIAGDIR_SW)); + DiagDirection dir_2 = ::ReverseDiagDir(dir_1); + + if (!AIObject::DoCommand(start + ::TileOffsByDiagDir(dir_1), ::DiagDirToRoadBits(dir_2) | (AIObject::GetRoadType() << 4), 0, CMD_BUILD_ROAD, NULL, &_DoCommandReturnBuildTunnel2)) return false; + + /* In case of test-mode, test the other road piece too */ + return _BuildTunnelRoad2(); +} + +/* static */ bool AITunnel::_BuildTunnelRoad2() +{ + /* Build the piece of road on the 'end' side of the tunnel */ + TileIndex end = AIObject::GetCallbackVariable(0); + TileIndex start = AITunnel::GetOtherTunnelEnd(end); + + DiagDirection dir_1 = (DiagDirection)((::TileX(start) == ::TileX(end)) ? (::TileY(start) < ::TileY(end) ? DIAGDIR_NW : DIAGDIR_SE) : (::TileX(start) < ::TileX(end) ? DIAGDIR_NE : DIAGDIR_SW)); + DiagDirection dir_2 = ::ReverseDiagDir(dir_1); + + return AIObject::DoCommand(end + ::TileOffsByDiagDir(dir_2), ::DiagDirToRoadBits(dir_1) | (AIObject::GetRoadType() << 4), 0, CMD_BUILD_ROAD); +} + +/* static */ bool AITunnel::RemoveTunnel(TileIndex tile) +{ + EnforcePrecondition(false, IsTunnelTile(tile)); + + return AIObject::DoCommand(tile, 0, 0, CMD_LANDSCAPE_CLEAR); +} diff --git a/src/ai/api/ai_tunnel.hpp b/src/ai/api/ai_tunnel.hpp new file mode 100644 index 000000000..625237905 --- /dev/null +++ b/src/ai/api/ai_tunnel.hpp @@ -0,0 +1,103 @@ +/* $Id$ */ + +/** @file ai_tunnel.hpp Everything to query and build tunnels. */ + +#ifndef AI_TUNNEL_HPP +#define AI_TUNNEL_HPP + +#include "ai_object.hpp" +#include "ai_vehicle.hpp" + +/** + * Class that handles all tunnel related functions. + */ +class AITunnel : public AIObject { +public: + static const char *GetClassName() { return "AITunnel"; } + + /** + * All tunnel related errors. + */ + enum ErrorMessages { + + /** Base for bridge related errors */ + ERR_TUNNEL_BASE = AIError::ERR_CAT_TUNNEL << AIError::ERR_CAT_BIT_SIZE, + + /** Can't build tunnels on water */ + ERR_TUNNEL_CANNOT_BUILD_ON_WATER, // [STR_3807_CAN_T_BUILD_ON_WATER] + + /** The start tile must slope either North, South, West or East */ + ERR_TUNNEL_START_SITE_UNSUITABLE, // [STR_500B_SITE_UNSUITABLE_FOR_TUNNEL] + + /** An other tunnel is in the way */ + ERR_TUNNEL_ANOTHER_TUNNEL_IN_THE_WAY, // [STR_5003_ANOTHER_TUNNEL_IN_THE_WAY] + + /** Unable to excavate land at the end to create the tunnel's exit */ + ERR_TUNNEL_END_SITE_UNSUITABLE, // [STR_5005_UNABLE_TO_EXCAVATE_LAND] + }; + + /** + * Check whether the tile is an entrance to a tunnel. + * @param tile The tile to check. + * @pre AIMap::IsValidTile(tile). + * @return True if and only if the tile is the beginning or end of a tunnel. + */ + static bool IsTunnelTile(TileIndex tile); + + /** + * Get the tile that exits on the other end of a (would be) tunnel starting + * at tile. + * @param tile The tile that is an entrance to a tunnel or the tile where you may want to build a tunnel. + * @pre AIMap::IsValidTile(tile). + * @return The TileIndex that is the other end of the (would be) tunnel, or + * INVALID_TILE if no other end was found (can't build tunnel). + */ + static TileIndex GetOtherTunnelEnd(TileIndex tile); + +#ifndef DOXYGEN_SKIP + /** + * Internal function to help BuildTunnel in case of road. + */ + static bool _BuildTunnelRoad1(); + + /** + * Internal function to help BuildTunnel in case of road. + */ + static bool _BuildTunnelRoad2(); +#endif + + /** + * Builds a tunnel starting at start. The direction of the tunnel depends + * on the slope of the start tile. Tunnels can be created for either + * rails or roads; use the appropriate AIVehicle::VehicleType. + * As an extra for road, this functions builds two half-pieces of road on + * each end of the tunnel, making it easier for you to connect it to your + * network. + * @param start Where to start the tunnel. + * @param vehicle_type The vehicle-type of tunnel to build. + * @pre AIMap::IsValidTile(start). + * @pre vehicle_type == AIVehicle::VEHICLE_ROAD || (vehicle_type == AIVehicle::VEHICLE_RAIL && + * AIRail::IsRailTypeAvailable(AIRail::GetCurrentRailType())). + * @exception AIError::ERR_AREA_NOT_CLEAR + * @exception AITunnel::ERR_TUNNEL_CANNOT_BUILD_ON_WATER + * @exception AITunnel::ERR_TUNNEL_START_SITE_UNSUITABLE + * @exception AITunnel::ERR_TUNNEL_ANOTHER_TUNNEL_IN_THE_WAY + * @exception AITunnel::ERR_TUNNEL_END_SITE_UNSUITABLE + * @return Whether the tunnel has been/can be build or not. + * @note The slope of a tile can be determined by AITile::GetSlope(TileIndex). + * @note No matter if the road pieces were build or not, if building the + * tunnel succeeded, this function returns true. + */ + static bool BuildTunnel(AIVehicle::VehicleType vehicle_type, TileIndex start); + + /** + * Remove the tunnel whose entrance is located at tile. + * @param tile The tile that is an entrance to a tunnel. + * @pre AIMap::IsValidTile(tile) && IsTunnelTile(tile). + * @exception AIError::ERR_OWNED_BY_ANOTHER_COMPANY + * @return Whether the tunnel has been/can be removed or not. + */ + static bool RemoveTunnel(TileIndex tile); +}; + +#endif /* AI_TUNNEL_HPP */ diff --git a/src/ai/api/ai_tunnel.hpp.sq b/src/ai/api/ai_tunnel.hpp.sq new file mode 100644 index 000000000..fd2f6959c --- /dev/null +++ b/src/ai/api/ai_tunnel.hpp.sq @@ -0,0 +1,47 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_tunnel.hpp" + +namespace SQConvert { + /* Allow enums to be used as Squirrel parameters */ + template <> AITunnel::ErrorMessages GetParam(ForceType<AITunnel::ErrorMessages>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AITunnel::ErrorMessages)tmp; } + template <> int Return<AITunnel::ErrorMessages>(HSQUIRRELVM vm, AITunnel::ErrorMessages res) { sq_pushinteger(vm, (int32)res); return 1; } + + /* Allow AITunnel to be used as Squirrel parameter */ + template <> AITunnel *GetParam(ForceType<AITunnel *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AITunnel *)instance; } + template <> AITunnel &GetParam(ForceType<AITunnel &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AITunnel *)instance; } + template <> const AITunnel *GetParam(ForceType<const AITunnel *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AITunnel *)instance; } + template <> const AITunnel &GetParam(ForceType<const AITunnel &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AITunnel *)instance; } + template <> int Return<AITunnel *>(HSQUIRRELVM vm, AITunnel *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AITunnel", res, NULL, DefSQDestructorCallback<AITunnel>); return 1; } +}; // namespace SQConvert + +void SQAITunnel_Register(Squirrel *engine) { + DefSQClass <AITunnel> SQAITunnel("AITunnel"); + SQAITunnel.PreRegister(engine); + SQAITunnel.AddConstructor<void (AITunnel::*)(), 1>(engine, "x"); + + SQAITunnel.DefSQConst(engine, AITunnel::ERR_TUNNEL_BASE, "ERR_TUNNEL_BASE"); + SQAITunnel.DefSQConst(engine, AITunnel::ERR_TUNNEL_CANNOT_BUILD_ON_WATER, "ERR_TUNNEL_CANNOT_BUILD_ON_WATER"); + SQAITunnel.DefSQConst(engine, AITunnel::ERR_TUNNEL_START_SITE_UNSUITABLE, "ERR_TUNNEL_START_SITE_UNSUITABLE"); + SQAITunnel.DefSQConst(engine, AITunnel::ERR_TUNNEL_ANOTHER_TUNNEL_IN_THE_WAY, "ERR_TUNNEL_ANOTHER_TUNNEL_IN_THE_WAY"); + SQAITunnel.DefSQConst(engine, AITunnel::ERR_TUNNEL_END_SITE_UNSUITABLE, "ERR_TUNNEL_END_SITE_UNSUITABLE"); + + AIError::RegisterErrorMap(STR_3807_CAN_T_BUILD_ON_WATER, AITunnel::ERR_TUNNEL_CANNOT_BUILD_ON_WATER); + AIError::RegisterErrorMap(STR_500B_SITE_UNSUITABLE_FOR_TUNNEL, AITunnel::ERR_TUNNEL_START_SITE_UNSUITABLE); + AIError::RegisterErrorMap(STR_5003_ANOTHER_TUNNEL_IN_THE_WAY, AITunnel::ERR_TUNNEL_ANOTHER_TUNNEL_IN_THE_WAY); + AIError::RegisterErrorMap(STR_5005_UNABLE_TO_EXCAVATE_LAND, AITunnel::ERR_TUNNEL_END_SITE_UNSUITABLE); + + AIError::RegisterErrorMapString(AITunnel::ERR_TUNNEL_CANNOT_BUILD_ON_WATER, "ERR_TUNNEL_CANNOT_BUILD_ON_WATER"); + AIError::RegisterErrorMapString(AITunnel::ERR_TUNNEL_START_SITE_UNSUITABLE, "ERR_TUNNEL_START_SITE_UNSUITABLE"); + AIError::RegisterErrorMapString(AITunnel::ERR_TUNNEL_ANOTHER_TUNNEL_IN_THE_WAY, "ERR_TUNNEL_ANOTHER_TUNNEL_IN_THE_WAY"); + AIError::RegisterErrorMapString(AITunnel::ERR_TUNNEL_END_SITE_UNSUITABLE, "ERR_TUNNEL_END_SITE_UNSUITABLE"); + + SQAITunnel.DefSQStaticMethod(engine, &AITunnel::GetClassName, "GetClassName", 1, "x"); + SQAITunnel.DefSQStaticMethod(engine, &AITunnel::IsTunnelTile, "IsTunnelTile", 2, "xi"); + SQAITunnel.DefSQStaticMethod(engine, &AITunnel::GetOtherTunnelEnd, "GetOtherTunnelEnd", 2, "xi"); + SQAITunnel.DefSQStaticMethod(engine, &AITunnel::BuildTunnel, "BuildTunnel", 3, "xii"); + SQAITunnel.DefSQStaticMethod(engine, &AITunnel::RemoveTunnel, "RemoveTunnel", 2, "xi"); + + SQAITunnel.PostRegister(engine); +} diff --git a/src/ai/api/ai_types.hpp b/src/ai/api/ai_types.hpp new file mode 100644 index 000000000..086cf75e0 --- /dev/null +++ b/src/ai/api/ai_types.hpp @@ -0,0 +1,40 @@ +/* $Id$ */ + +/** @file ai_types.hpp Defines all the types of the game, like VehicleID, .... */ + +#ifndef AI_TYPES_HPP +#define AI_TYPES_HPP + +#include "../../core/overflowsafe_type.hpp" +#include "../../company_type.h" + +/* Define all types here, so we don't have to include the whole _type.h maze */ +typedef uint BridgeType; //!< Internal name, not of any use for you. +typedef byte CargoID; //!< The ID of a cargo. +class CommandCost; //!< The cost of a command. +typedef uint16 EngineID; //!< The ID of an engine. +typedef uint16 GroupID; //!< The ID of a group. +typedef uint16 IndustryID; //!< The ID of an industry. +typedef uint8 IndustryType; //!< The ID of an industry-type. +typedef OverflowSafeInt64 Money; //!< Money, stored in a 32bit/64bit safe way. For AIs money is always in pounds. +typedef uint16 SignID; //!< The ID of a sign. +typedef uint16 StationID; //!< The ID of a station. +typedef uint16 StringID; //!< The ID of a string. +typedef uint32 TileIndex; //!< The ID of a tile (just named differently). +typedef uint16 TownID; //!< The ID of a town. +typedef uint16 VehicleID; //!< The ID of a vehicle. + +/* Types we defined ourself, as the OpenTTD core doesn't have them (yet) */ +typedef uint AIErrorType; //!< The types of errors inside the NoAI framework. +typedef BridgeType BridgeID; //!< The ID of a bridge. +typedef uint16 SubsidyID; //!< The ID of a subsidy. + +#ifndef _SQUIRREL_H_ +/* Life becomes easier when we can tell about a function it needs the VM, but + * without really including 'squirrel.h'. */ +typedef struct SQVM *HSQUIRRELVM; //!< Pointer to Squirrel Virtual Machine. +typedef int SQInteger; //!< Squirrel Integer. +typedef struct SQObject HSQOBJECT; //!< Squirrel Object (fake declare) +#endif + +#endif /* AI_TYPES_HPP */ diff --git a/src/ai/api/ai_vehicle.cpp b/src/ai/api/ai_vehicle.cpp new file mode 100644 index 000000000..9532e3caf --- /dev/null +++ b/src/ai/api/ai_vehicle.cpp @@ -0,0 +1,389 @@ +/* $Id$ */ + +/** @file ai_vehicle.cpp Implementation of AIVehicle. */ + +#include "ai_vehicle.hpp" +#include "ai_engine.hpp" +#include "ai_cargo.hpp" +#include "ai_order.hpp" +#include "ai_gamesettings.hpp" +#include "../ai_instance.hpp" +#include "../../openttd.h" +#include "../../company_func.h" +#include "../../aircraft.h" +#include "../../string_func.h" +#include "../../strings_func.h" +#include "../../core/alloc_func.hpp" +#include "../../command_func.h" +#include "../../roadveh.h" +#include "../../train.h" +#include "../../vehicle_func.h" +#include "table/strings.h" + +/* static */ bool AIVehicle::IsValidVehicle(VehicleID vehicle_id) +{ + if (!::IsValidVehicleID(vehicle_id)) return false; + const Vehicle *v = ::GetVehicle(vehicle_id); + return v->owner == _current_company && (v->IsPrimaryVehicle() || (v->type == VEH_TRAIN && ::IsFreeWagon(v))); +} + +/* static */ int32 AIVehicle::GetNumWagons(VehicleID vehicle_id) +{ + if (!IsValidVehicle(vehicle_id)) return -1; + + int num = 1; + if (::GetVehicle(vehicle_id)->type == VEH_TRAIN) { + const Vehicle *v = ::GetVehicle(vehicle_id); + while ((v = GetNextUnit(v)) != NULL) num++; + } + + return num; +} + +/* static */ int AIVehicle::GetLength(VehicleID vehicle_id) +{ + if (!IsValidVehicle(vehicle_id)) return -1; + + const Vehicle *v = ::GetVehicle(vehicle_id); + switch (v->type) { + case VEH_ROAD: return v->u.road.cached_veh_length; + case VEH_TRAIN: return v->u.rail.cached_total_length; + default: return -1; + } +} + +/* static */ VehicleID AIVehicle::BuildVehicle(TileIndex depot, EngineID engine_id) +{ + EnforcePrecondition(INVALID_VEHICLE, AIEngine::IsValidEngine(engine_id)); + + ::VehicleType type = ::GetEngine(engine_id)->type; + + EnforcePreconditionCustomError(INVALID_VEHICLE, !AIGameSettings::IsDisabledVehicleType((AIVehicle::VehicleType)type), AIVehicle::ERR_VEHICLE_BUILD_DISABLED); + + if (!AIObject::DoCommand(depot, engine_id, 0, ::GetCmdBuildVeh(type), NULL, &AIInstance::DoCommandReturnVehicleID)) return INVALID_VEHICLE; + + /* In case of test-mode, we return VehicleID 0 */ + return 0; +} + +/* static */ VehicleID AIVehicle::CloneVehicle(TileIndex depot, VehicleID vehicle_id, bool share_orders) +{ + EnforcePrecondition(false, IsValidVehicle(vehicle_id)); + + if (!AIObject::DoCommand(depot, vehicle_id, share_orders, CMD_CLONE_VEHICLE, NULL, &AIInstance::DoCommandReturnVehicleID)) return INVALID_VEHICLE; + + /* In case of test-mode, we return VehicleID 0 */ + return 0; +} + +/* static */ bool AIVehicle::MoveWagon(VehicleID source_vehicle_id, int source_wagon, bool move_attached_wagons, int dest_vehicle_id, int dest_wagon) +{ + EnforcePrecondition(false, IsValidVehicle(source_vehicle_id) && source_wagon < GetNumWagons(source_vehicle_id)); + EnforcePrecondition(false, dest_vehicle_id == -1 || (IsValidVehicle(dest_vehicle_id) && dest_wagon < GetNumWagons(dest_vehicle_id))); + EnforcePrecondition(false, ::GetVehicle(source_vehicle_id)->type == VEH_TRAIN); + EnforcePrecondition(false, dest_vehicle_id == -1 || ::GetVehicle(dest_vehicle_id)->type == VEH_TRAIN); + + const Vehicle *v = ::GetVehicle(source_vehicle_id); + while (source_wagon-- > 0) v = GetNextUnit(v); + const Vehicle *w = NULL; + if (dest_vehicle_id != -1) { + w = ::GetVehicle(dest_vehicle_id); + while (dest_wagon-- > 0) w = GetNextUnit(w); + } + + return AIObject::DoCommand(0, v->index | ((w == NULL ? INVALID_VEHICLE : w->index) << 16), move_attached_wagons ? 1 : 0, CMD_MOVE_RAIL_VEHICLE); +} + +/* static */ int AIVehicle::GetRefitCapacity(VehicleID vehicle_id, CargoID cargo) +{ + if (!IsValidVehicle(vehicle_id)) return -1; + if (!AICargo::IsValidCargo(cargo)) return -1; + + CommandCost res = ::DoCommand(0, vehicle_id, cargo, DC_QUERY_COST, GetCmdRefitVeh(::GetVehicle(vehicle_id))); + return CmdSucceeded(res) ? _returned_refit_capacity : -1; +} + +/* static */ bool AIVehicle::RefitVehicle(VehicleID vehicle_id, CargoID cargo) +{ + EnforcePrecondition(false, IsValidVehicle(vehicle_id) && AICargo::IsValidCargo(cargo)); + + return AIObject::DoCommand(0, vehicle_id, cargo, GetCmdRefitVeh(::GetVehicle(vehicle_id))); +} + + +/* static */ bool AIVehicle::SellVehicle(VehicleID vehicle_id) +{ + EnforcePrecondition(false, IsValidVehicle(vehicle_id)); + + const Vehicle *v = ::GetVehicle(vehicle_id); + return AIObject::DoCommand(0, vehicle_id, v->type == VEH_TRAIN ? 1 : 0, GetCmdSellVeh(v)); +} + +/* static */ bool AIVehicle::SellWagon(VehicleID vehicle_id, int wagon, bool sell_attached_wagons) +{ + EnforcePrecondition(false, IsValidVehicle(vehicle_id) && wagon < GetNumWagons(vehicle_id)); + EnforcePrecondition(false, ::GetVehicle(vehicle_id)->type == VEH_TRAIN); + + const Vehicle *v = ::GetVehicle(vehicle_id); + while (wagon-- > 0) v = GetNextUnit(v); + + return AIObject::DoCommand(0, v->index, sell_attached_wagons ? 1 : 0, CMD_SELL_RAIL_WAGON); +} + +/* static */ bool AIVehicle::SendVehicleToDepot(VehicleID vehicle_id) +{ + EnforcePrecondition(false, IsValidVehicle(vehicle_id)); + + return AIObject::DoCommand(0, vehicle_id, 0, GetCmdSendToDepot(::GetVehicle(vehicle_id))); +} + +/* static */ bool AIVehicle::IsInDepot(VehicleID vehicle_id) +{ + if (!IsValidVehicle(vehicle_id)) return false; + return ::GetVehicle(vehicle_id)->IsInDepot(); +} + +/* static */ bool AIVehicle::IsStoppedInDepot(VehicleID vehicle_id) +{ + if (!IsValidVehicle(vehicle_id)) return false; + return ::GetVehicle(vehicle_id)->IsStoppedInDepot(); +} + +/* static */ bool AIVehicle::StartStopVehicle(VehicleID vehicle_id) +{ + EnforcePrecondition(false, IsValidVehicle(vehicle_id)); + + return AIObject::DoCommand(0, vehicle_id, 0, CMD_START_STOP_VEHICLE); +} + +/* static */ bool AIVehicle::SkipToVehicleOrder(VehicleID vehicle_id, AIOrder::OrderPosition order_position) +{ + order_position = AIOrder::ResolveOrderPosition(vehicle_id, order_position); + + EnforcePrecondition(false, AIOrder::IsValidVehicleOrder(vehicle_id, order_position)); + + return AIObject::DoCommand(0, vehicle_id, order_position, CMD_SKIP_TO_ORDER); +} + +/* static */ bool AIVehicle::ReverseVehicle(VehicleID vehicle_id) +{ + EnforcePrecondition(false, IsValidVehicle(vehicle_id)); + EnforcePrecondition(false, ::GetVehicle(vehicle_id)->type == VEH_ROAD || ::GetVehicle(vehicle_id)->type == VEH_TRAIN); + + switch (::GetVehicle(vehicle_id)->type) { + case VEH_ROAD: return AIObject::DoCommand(0, vehicle_id, 0, CMD_TURN_ROADVEH); + case VEH_TRAIN: return AIObject::DoCommand(0, vehicle_id, 0, CMD_REVERSE_TRAIN_DIRECTION); + default: NOT_REACHED(); + } +} + +/* static */ bool AIVehicle::SetName(VehicleID vehicle_id, const char *name) +{ + EnforcePrecondition(false, IsValidVehicle(vehicle_id)); + EnforcePrecondition(false, !::StrEmpty(name)); + EnforcePreconditionCustomError(false, ::strlen(name) < MAX_LENGTH_VEHICLE_NAME_BYTES, AIError::ERR_PRECONDITION_STRING_TOO_LONG); + + return AIObject::DoCommand(0, vehicle_id, 0, CMD_RENAME_VEHICLE, name); +} + +/* static */ TileIndex AIVehicle::GetLocation(VehicleID vehicle_id) +{ + if (!IsValidVehicle(vehicle_id)) return INVALID_TILE; + + const Vehicle *v = ::GetVehicle(vehicle_id); + if (v->type == VEH_AIRCRAFT) { + uint x = Clamp(v->x_pos / TILE_SIZE, 0, ::MapSizeX() - 2); + uint y = Clamp(v->y_pos / TILE_SIZE, 0, ::MapSizeY() - 2); + return ::TileXY(x, y); + } + + return v->tile; +} + +/* static */ EngineID AIVehicle::GetEngineType(VehicleID vehicle_id) +{ + if (!IsValidVehicle(vehicle_id)) return INVALID_ENGINE; + + return ::GetVehicle(vehicle_id)->engine_type; +} + +/* static */ EngineID AIVehicle::GetWagonEngineType(VehicleID vehicle_id, int wagon) +{ + if (!IsValidVehicle(vehicle_id)) return INVALID_ENGINE; + if (wagon >= GetNumWagons(vehicle_id)) return INVALID_ENGINE; + + const Vehicle *v = ::GetVehicle(vehicle_id); + if (v->type == VEH_TRAIN) { + while (wagon-- > 0) v = GetNextUnit(v); + } + return v->engine_type; +} + +/* static */ int32 AIVehicle::GetUnitNumber(VehicleID vehicle_id) +{ + if (!IsValidVehicle(vehicle_id)) return -1; + + return ::GetVehicle(vehicle_id)->unitnumber; +} + +/* static */ const char *AIVehicle::GetName(VehicleID vehicle_id) +{ + if (!IsValidVehicle(vehicle_id)) return NULL; + + static const int len = 64; + char *vehicle_name = MallocT<char>(len); + + ::SetDParam(0, vehicle_id); + ::GetString(vehicle_name, STR_VEHICLE_NAME, &vehicle_name[len - 1]); + return vehicle_name; +} + +/* static */ int32 AIVehicle::GetAge(VehicleID vehicle_id) +{ + if (!IsValidVehicle(vehicle_id)) return -1; + + return ::GetVehicle(vehicle_id)->age; +} + +/* static */ int32 AIVehicle::GetWagonAge(VehicleID vehicle_id, int wagon) +{ + if (!IsValidVehicle(vehicle_id)) return -1; + if (wagon >= GetNumWagons(vehicle_id)) return -1; + + const Vehicle *v = ::GetVehicle(vehicle_id); + if (v->type == VEH_TRAIN) { + while (wagon-- > 0) v = GetNextUnit(v); + } + return v->age; +} + +/* static */ int32 AIVehicle::GetMaxAge(VehicleID vehicle_id) +{ + if (!IsValidVehicle(vehicle_id)) return -1; + + return ::GetVehicle(vehicle_id)->max_age; +} + +/* static */ int32 AIVehicle::GetAgeLeft(VehicleID vehicle_id) +{ + if (!IsValidVehicle(vehicle_id)) return -1; + + return ::GetVehicle(vehicle_id)->max_age - ::GetVehicle(vehicle_id)->age; +} + +/* static */ int32 AIVehicle::GetCurrentSpeed(VehicleID vehicle_id) +{ + if (!IsValidVehicle(vehicle_id)) return -1; + + return ::GetVehicle(vehicle_id)->GetDisplaySpeed(); +} + +/* static */ AIVehicle::VehicleState AIVehicle::GetState(VehicleID vehicle_id) +{ + if (!IsValidVehicle(vehicle_id)) return AIVehicle::VS_INVALID; + + const Vehicle *v = ::GetVehicle(vehicle_id); + byte vehstatus = v->vehstatus; + + if (vehstatus & ::VS_CRASHED) return AIVehicle::VS_CRASHED; + if (v->breakdown_ctr != 0) return AIVehicle::VS_BROKEN; + if (v->IsStoppedInDepot()) return AIVehicle::VS_IN_DEPOT; + if (vehstatus & ::VS_STOPPED) return AIVehicle::VS_STOPPED; + if (v->current_order.IsType(OT_LOADING)) return AIVehicle::VS_AT_STATION; + return AIVehicle::VS_RUNNING; +} + +/* static */ Money AIVehicle::GetRunningCost(VehicleID vehicle_id) +{ + if (!IsValidVehicle(vehicle_id)) return -1; + + return ::GetVehicle(vehicle_id)->GetRunningCost() >> 8; +} + +/* static */ Money AIVehicle::GetProfitThisYear(VehicleID vehicle_id) +{ + if (!IsValidVehicle(vehicle_id)) return -1; + + return ::GetVehicle(vehicle_id)->GetDisplayProfitThisYear(); +} + +/* static */ Money AIVehicle::GetProfitLastYear(VehicleID vehicle_id) +{ + if (!IsValidVehicle(vehicle_id)) return -1; + + return ::GetVehicle(vehicle_id)->GetDisplayProfitLastYear(); +} + +/* static */ Money AIVehicle::GetCurrentValue(VehicleID vehicle_id) +{ + if (!IsValidVehicle(vehicle_id)) return -1; + + return ::GetVehicle(vehicle_id)->value; +} + +/* static */ AIVehicle::VehicleType AIVehicle::GetVehicleType(VehicleID vehicle_id) +{ + if (!IsValidVehicle(vehicle_id)) return VEHICLE_INVALID; + + switch (::GetVehicle(vehicle_id)->type) { + case VEH_ROAD: return VEHICLE_ROAD; + case VEH_TRAIN: return VEHICLE_RAIL; + case VEH_SHIP: return VEHICLE_WATER; + case VEH_AIRCRAFT: return VEHICLE_AIR; + default: return VEHICLE_INVALID; + } +} + +/* static */ AIRoad::RoadType AIVehicle::GetRoadType(VehicleID vehicle_id) +{ + if (!IsValidVehicle(vehicle_id)) return AIRoad::ROADTYPE_INVALID; + if (GetVehicleType(vehicle_id) != VEHICLE_ROAD) return AIRoad::ROADTYPE_INVALID; + + return (AIRoad::RoadType)::GetVehicle(vehicle_id)->u.road.roadtype; +} + +/* static */ int32 AIVehicle::GetCapacity(VehicleID vehicle_id, CargoID cargo) +{ + if (!IsValidVehicle(vehicle_id)) return -1; + if (!AICargo::IsValidCargo(cargo)) return -1; + + uint32 amount = 0; + for (const Vehicle *v = ::GetVehicle(vehicle_id); v != NULL; v = v->Next()) { + if (v->cargo_type == cargo) amount += v->cargo_cap; + } + + return amount; +} + +/* static */ int32 AIVehicle::GetCargoLoad(VehicleID vehicle_id, CargoID cargo) +{ + if (!IsValidVehicle(vehicle_id)) return -1; + if (!AICargo::IsValidCargo(cargo)) return -1; + + uint32 amount = 0; + for (const Vehicle *v = ::GetVehicle(vehicle_id); v != NULL; v = v->Next()) { + if (v->cargo_type == cargo) amount += v->cargo.Count(); + } + + return amount; +} + +/* static */ GroupID AIVehicle::GetGroupID(VehicleID vehicle_id) +{ + if (!IsValidVehicle(vehicle_id)) return ::INVALID_GROUP; + + return ::GetVehicle(vehicle_id)->group_id; +} + +/* static */ bool AIVehicle::IsArticulated(VehicleID vehicle_id) +{ + if (!IsValidVehicle(vehicle_id)) return false; + if (GetVehicleType(vehicle_id) != VEHICLE_ROAD && GetVehicleType(vehicle_id) != VEHICLE_RAIL) return false; + + const Vehicle *v = ::GetVehicle(vehicle_id); + switch (v->type) { + case VEH_ROAD: return RoadVehHasArticPart(v); + case VEH_TRAIN: return EngineHasArticPart(v); + default: NOT_REACHED(); + } +} diff --git a/src/ai/api/ai_vehicle.hpp b/src/ai/api/ai_vehicle.hpp new file mode 100644 index 000000000..8120fe947 --- /dev/null +++ b/src/ai/api/ai_vehicle.hpp @@ -0,0 +1,477 @@ +/* $Id$ */ + +/** @file ai_vehicle.hpp Everything to query and build vehicles. */ + +#ifndef AI_VEHICLE_HPP +#define AI_VEHICLE_HPP + +#include "ai_object.hpp" +#include "ai_error.hpp" +#include "ai_road.hpp" +#include "ai_order.hpp" + +/** + * Class that handles all vehicle related functions. + */ +class AIVehicle : public AIObject { +public: + static const char *GetClassName() { return "AIVehicle"; } + + /** + * All vehicle related error messages. + */ + enum ErrorMessages { + /** Base for vehicle related errors */ + ERR_VEHICLE_BASE = AIError::ERR_CAT_VEHICLE << AIError::ERR_CAT_BIT_SIZE, + + /** Too many vehicles in the game, can't build any more. */ + ERR_VEHICLE_TOO_MANY, // [STR_00E1_TOO_MANY_VEHICLES_IN_GAME] + + /** Vehicle is not available */ + ERR_VEHICLE_NOT_AVAILABLE, // [STR_AIRCRAFT_NOT_AVAILABLE, STR_ROAD_VEHICLE_NOT_AVAILABLE, STR_SHIP_NOT_AVAILABLE, STR_RAIL_VEHICLE_NOT_AVAILABLE] + + /** Vehicle can't be build due to game settigns */ + ERR_VEHICLE_BUILD_DISABLED, // [STR_A008_CAN_T_BUILD_AIRCRAFT, STR_980D_CAN_T_BUILD_SHIP, STR_9009_CAN_T_BUILD_ROAD_VEHICLE, STR_882B_CAN_T_BUILD_RAILROAD_VEHICLE] + + /** Vehicle can't be build in the selected depot */ + ERR_VEHICLE_WRONG_DEPOT, // [STR_DEPOT_WRONG_DEPOT_TYPE] + + /** Vehicle can't return to the depot */ + ERR_VEHICLE_CANNOT_SEND_TO_DEPOT, // [STR_8830_CAN_T_SEND_TRAIN_TO_DEPOT, STR_9018_CAN_T_SEND_VEHICLE_TO_DEPOT, STR_9819_CAN_T_SEND_SHIP_TO_DEPOT, STR_A012_CAN_T_SEND_AIRCRAFT_TO] + + /** Vehicle can't start / stop */ + ERR_VEHICLE_CANNOT_START_STOP, // [STR_883B_CAN_T_STOP_START_TRAIN, STR_9015_CAN_T_STOP_START_ROAD_VEHICLE, STR_9818_CAN_T_STOP_START_SHIP, STR_A016_CAN_T_STOP_START_AIRCRAFT] + + /** Vehicle can't turn */ + ERR_VEHICLE_CANNOT_TURN, // [STR_8869_CAN_T_REVERSE_DIRECTION, STR_9033_CAN_T_MAKE_VEHICLE_TURN] + + /** Vehicle can't be refit */ + ERR_VEHICLE_CANNOT_REFIT, // [STR_RAIL_CAN_T_REFIT_VEHICLE, STR_REFIT_ROAD_VEHICLE_CAN_T, STR_9841_CAN_T_REFIT_SHIP, STR_A042_CAN_T_REFIT_AIRCRAFT] + + /** Vehicle is destroyed */ + ERR_VEHICLE_IS_DESTROYED, // [STR_CAN_T_REFIT_DESTROYED_VEHICLE, STR_CAN_T_SELL_DESTROYED_VEHICLE] + + /** Vehicle is not in a depot */ + ERR_VEHICLE_NOT_IN_DEPOT, // [STR_A01B_AIRCRAFT_MUST_BE_STOPPED, STR_9013_MUST_BE_STOPPED_INSIDE, STR_TRAIN_MUST_BE_STOPPED, STR_980B_SHIP_MUST_BE_STOPPED_IN] + + /** Vehicle is flying */ + ERR_VEHICLE_IN_FLIGHT, // [STR_A017_AIRCRAFT_IS_IN_FLIGHT] + + /** Vehicle is without power */ + ERR_VEHCILE_NO_POWER, // [STR_TRAIN_START_NO_CATENARY] + + }; + + /** + * The type of a vehicle available in the game. Trams for example are + * road vehicles, as maglev is a rail vehicle. + */ + enum VehicleType { + /* Order IS important, as it now matches the internal state of the game for vehicle type */ + VEHICLE_RAIL, //!< Rail type vehicle. + VEHICLE_ROAD, //!< Road type vehicle (bus / truck). + VEHICLE_WATER, //!< Water type vehicle. + VEHICLE_AIR, //!< Air type vehicle. + VEHICLE_INVALID = 0xFF, //!< Invalid vehicle type. + }; + + /** + * The different states a vehicle can be in. + */ + enum VehicleState { + VS_RUNNING, //!< The vehicle is currently running. + VS_STOPPED, //!< The vehicle is stopped manually. + VS_IN_DEPOT, //!< The vehicle is stopped in the depot. + VS_AT_STATION, //!< The vehicle is stopped at a station and is currently loading or unloading. + VS_BROKEN, //!< The vehicle has broken down and will start running again in a while. + VS_CRASHED, //!< The vehicle is crashed (and will never run again). + + VS_INVALID = 0xFF, //!< An invalid vehicle state. + }; + + /** + * Checks whether the given vehicle is valid and owned by you. + * @param vehicle_id The vehicle to check. + * @return True if and only if the vehicle is valid. + */ + static bool IsValidVehicle(VehicleID vehicle_id); + + /** + * Get the number of wagons a vehicle has. + * @param vehicle_id The vehicle to get the number of wagons from. + * @pre IsValidVehicle(vehicle_id). + * @return The number of wagons the vehicle has. + */ + static int32 GetNumWagons(VehicleID vehicle_id); + + /** + * Set the name of a vehicle. + * @param vehicle_id The vehicle to set the name for. + * @param name The name for the vehicle. + * @pre IsValidVehicle(vehicle_id). + * @pre 'name' must have at least one character. + * @pre 'name' must have at most 30 characters. + * @exception AIError::ERR_NAME_IS_NOT_UNIQUE + * @return True if and only if the name was changed. + */ + static bool SetName(VehicleID vehicle_id, const char *name); + + /** + * Get the name of a vehicle. + * @param vehicle_id The vehicle to get the name of. + * @pre IsValidVehicle(vehicle_id). + * @return The name the vehicle has. + */ + static const char *GetName(VehicleID vehicle_id); + + /** + * Get the current location of a vehicle. + * @param vehicle_id The vehicle to get the location of. + * @pre IsValidVehicle(vehicle_id). + * @return The tile the vehicle is currently on. + */ + static TileIndex GetLocation(VehicleID vehicle_id); + + /** + * Get the engine-type of a vehicle. + * @param vehicle_id The vehicle to get the engine-type of. + * @pre IsValidVehicle(vehicle_id). + * @return The engine type the vehicle has. + */ + static EngineID GetEngineType(VehicleID vehicle_id); + + /** + * Get the engine-type of a wagon. + * @param vehicle_id The vehicle to get the engine-type of. + * @param wagon The wagon in the vehicle to get the engine-type of. + * @pre IsValidVehicle(vehicle_id). + * @pre wagon < GetNumWagons(vehicle_id). + * @return The engine type the vehicle has. + */ + static EngineID GetWagonEngineType(VehicleID vehicle_id, int wagon); + + /** + * Get the unitnumber of a vehicle. + * @param vehicle_id The vehicle to get the unitnumber of. + * @pre IsValidVehicle(vehicle_id). + * @return The unitnumber the vehicle has. + */ + static int32 GetUnitNumber(VehicleID vehicle_id); + + /** + * Get the current age of a vehicle. + * @param vehicle_id The vehicle to get the age of. + * @pre IsValidVehicle(vehicle_id). + * @return The current age the vehicle has. + * @note The age is in days. + */ + static int32 GetAge(VehicleID vehicle_id); + + /** + * Get the current age of a second (or third, etc.) engine in a train vehicle. + * @param vehicle_id The vehicle to get the age of. + * @param wagon The wagon in the vehicle to get the age of. + * @pre IsValidVehicle(vehicle_id). + * @pre wagon < GetNumWagons(vehicle_id). + * @return The current age the vehicle has. + * @note The age is in days. + */ + static int32 GetWagonAge(VehicleID vehicle_id, int wagon); + + /** + * Get the maximum age of a vehicle. + * @param vehicle_id The vehicle to get the age of. + * @pre IsValidVehicle(vehicle_id). + * @return The maximum age the vehicle has. + * @note The age is in days. + */ + static int32 GetMaxAge(VehicleID vehicle_id); + + /** + * Get the age a vehicle has left (maximum - current). + * @param vehicle_id The vehicle to get the age of. + * @pre IsValidVehicle(vehicle_id). + * @return The age the vehicle has left. + * @note The age is in days. + */ + static int32 GetAgeLeft(VehicleID vehicle_id); + + /** + * Get the current speed of a vehicle. + * @param vehicle_id The vehicle to get the age of. + * @pre IsValidVehicle(vehicle_id). + * @return The current speed of the vehicle. + * @note Speed is in km/h. + */ + static int32 GetCurrentSpeed(VehicleID vehicle_id); + + /** + * Get the current state of a vehicle. + * @param vehicle_id The vehicle to get the state of. + * @pre IsValidVehicle(vehicle_id). + * @return The current state of the vehicle. + */ + static VehicleState GetState(VehicleID vehicle_id); + + /** + * Get the running cost of this vehicle. + * @param vehicle_id The vehicle to get the age of. + * @pre IsValidVehicle(vehicle_id). + * @return The running cost of the vehicle per year. + * @note Cost is per year; divide by 364 to get per day. + * @note This is not equal to AIEngine::GetRunningCost for Trains, because + * wagons and second engines can add up in the calculation too. + */ + static Money GetRunningCost(VehicleID vehicle_id); + + /** + * Get the current profit of a vehicle. + * @param vehicle_id The vehicle to get the profit of. + * @pre IsValidVehicle(vehicle_id). + * @return The current profit the vehicle has. + */ + static Money GetProfitThisYear(VehicleID vehicle_id); + + /** + * Get the profit of last year of a vehicle. + * @param vehicle_id The vehicle to get the profit of. + * @pre IsValidVehicle(vehicle_id). + * @return The profit the vehicle had last year. + */ + static Money GetProfitLastYear(VehicleID vehicle_id); + + + /** + * Get the current value of a vehicle. + * @param vehicle_id The vehicle to get the value of. + * @pre IsValidVehicle(vehicle_id). + * @return The value the vehicle currently has (the amount you should get + * when you would sell the vehicle right now). + */ + static Money GetCurrentValue(VehicleID vehicle_id); + + /** + * Get the type of vehicle. + * @param vehicle_id The vehicle to get the type of. + * @pre IsValidVehicle(vehicle_id). + * @return The vehicle type. + */ + static AIVehicle::VehicleType GetVehicleType(VehicleID vehicle_id); + + /** + * Get the RoadType of the vehicle. + * @param vehicle_id The vehicle to get the RoadType of. + * @pre IsValidVehicle(vehicle_id). + * @pre GetVehicleType(vehicle_id) == VEHICLE_ROAD. + * @return The RoadType the vehicle has. + */ + static AIRoad::RoadType GetRoadType(VehicleID vehicle_id); + + /** + * Check if a vehicle is in a depot. + * @param vehicle_id The vehicle to check. + * @pre IsValidVehicle(vehicle_id). + * @return True if and only if the vehicle is in a depot. + */ + static bool IsInDepot(VehicleID vehicle_id); + + /** + * Check if a vehicle is in a depot and stopped. + * @param vehicle_id The vehicle to check. + * @pre IsValidVehicle(vehicle_id). + * @return True if and only if the vehicle is in a depot and stopped. + */ + static bool IsStoppedInDepot(VehicleID vehicle_id); + + /** + * Builds a vehicle with the given engine at the given depot. + * @param depot The depot where the vehicle will be build. + * @param engine_id The engine to use for this vehicle. + * @pre The tile at depot has a depot that can build the engine and + * is owned by you. + * @pre IsValidEngine(engine_id). + * @exception AIVehicle::ERR_VEHICLE_TOO_MANY + * @exception AIVehicle::ERR_VEHICLE_BUILD_DISABLED + * @exception AIVehicle::ERR_VEHICLE_WRONG_DEPOT + * @return The VehicleID of the new vehicle, or an invalid VehicleID when + * it failed. Check the return value using IsValidVehicle. In test-mode + * 0 is returned if it was successful; any other value indicates failure. + * @note In Test Mode it means you can't assign orders yet to this vehicle, + * as the vehicle isn't really built yet. Build it for real first before + * assigning orders. + */ + static VehicleID BuildVehicle(TileIndex depot, EngineID engine_id); + + /** + * Clones a vehicle at the given depot, copying or cloning it's orders. + * @param depot The depot where the vehicle will be build. + * @param vehicle_id The vehicle to use as example for the new vehicle. + * @param share_orders Should the orders be copied or shared? + * @pre The tile 'depot' has a depot on it, allowing 'vehicle_id'-type vehicles. + * @pre IsValidVehicle(vehicle_id). + * @exception AIVehicle::ERR_VEHICLE_TOO_MANY + * @exception AIVehicle::ERR_VEHICLE_BUILD_DISABLED + * @exception AIVehicle::ERR_VEHICLE_WRONG_DEPOT + * @return The VehicleID of the new vehicle, or an invalid VehicleID when + * it failed. Check the return value using IsValidVehicle. In test-mode + * 0 is returned if it was successful; any other value indicates failure. + */ + static VehicleID CloneVehicle(TileIndex depot, VehicleID vehicle_id, bool share_orders); + + /** + * Move a wagon after another wagon. + * @param source_vehicle_id The vehicle to move a wagon away from. + * @param source_wagon The wagon in source_vehicle to move. + * @param move_attached_wagons Also move all wagons attached to the wagon to move. + * @param dest_vehicle_id The vehicle to move the wagon to, or -1 to create a new vehicle. + * @param dest_wagon The wagon in dest_vehicle to place source_wagon after. + * @pre IsValidVehicle(source_vehicle_id). + * @pre source_wagon < GetNumWagons(source_vehicle_id). + * @pre dest_vehicle_id == -1 || (IsValidVehicle(dest_vehicle_id) && dest_wagon < GetNumWagons(dest_vehicle_id)). + * @pre GetVehicleType(source_vehicle_id) == VEHICLE_RAIL. + * @pre dest_vehicle_id == -1 || GetVehicleType(dest_vehicle_id) == VEHICLE_RAIL. + * @return Whether or not moving the wagon(s) succeeded. + */ + static bool MoveWagon(VehicleID source_vehicle_id, int source_wagon, bool move_attached_wagons, int dest_vehicle_id, int dest_wagon); + + /** + * Gets the capacity of the given vehicle when refited to the given cargo type. + * @param vehicle_id The vehicle to refit. + * @param cargo The cargo to refit to. + * @pre IsValidVehicle(vehicle_id). + * @pre AICargo::IsValidCargo(cargo). + * @pre You must own the vehicle. + * @pre The vehicle must be stopped in the depot. + * @return The capacity the vehicle will have when refited. + */ + static int GetRefitCapacity(VehicleID vehicle_id, CargoID cargo); + + /** + * Refits a vehicle to the given cargo type. + * @param vehicle_id The vehicle to refit. + * @param cargo The cargo to refit to. + * @pre IsValidVehicle(vehicle_id). + * @pre AICargo::IsValidCargo(cargo). + * @pre You must own the vehicle. + * @pre The vehicle must be stopped in the depot. + * @exception AIVehicle::ERR_VEHICLE_CANNOT_REFIT + * @exception AIVehicle::ERR_VEHICLE_IS_DESTROYED + * @exception AIVehicle::ERR_VEHICLE_NOT_IN_DEPOT + * @return True if and only if the refit succeeded. + */ + static bool RefitVehicle(VehicleID vehicle_id, CargoID cargo); + + /** + * Sells the given vehicle. + * @param vehicle_id The vehicle to sell. + * @pre IsValidVehicle(vehicle_id). + * @pre You must own the vehicle. + * @pre The vehicle must be stopped in the depot. + * @exception AIVehicle::ERR_VEHICLE_IS_DESTROYED + * @exception AIVehicle::ERR_VEHICLE_NOT_IN_DEPOT + * @return True if and only if the vehicle has been sold. + */ + static bool SellVehicle(VehicleID vehicle_id); + + /** + * Sells the given wagon from the vehicle. + * @param vehicle_id The vehicle to sell a wagon from. + * @param wagon The wagon to sell. + * @param sell_attached_wagons Sell all wagons attached to the one we want to sell. + * @pre IsValidVehicle(vehicle_id). + * @pre wagon < GetNumWagons(vehicle_id). + * @pre You must own the vehicle. + * @pre The vehicle must be stopped in the depot. + * @exception AIVehicle::ERR_VEHICLE_IS_DESTROYED + * @exception AIVehicle::ERR_VEHICLE_NOT_IN_DEPOT + * @return True if and only if the wagon(s) has been sold. + */ + static bool SellWagon(VehicleID vehicle_id, int wagon, bool sell_attached_wagons); + + /** + * Sends the given vehicle to a depot. + * @param vehicle_id The vehicle to send to a depot. + * @pre IsValidVehicle(vehicle_id). + * @exception AIVehicle::ERR_VEHICLE_CANNOT_SEND_TO_DEPOT + * @return True if and only if the vehicle has been sent to a depot. + */ + static bool SendVehicleToDepot(VehicleID vehicle_id); + + /** + * Starts or stops the given vehicle depending on the current state. + * @param vehicle_id The vehicle to start/stop. + * @pre IsValidVehicle(vehicle_id). + * @exception AIVehicle::ERR_VEHICLE_CANNOT_START_STOP + * @exception (For aircraft only): AIVehicle::ERR_VEHICLE_IN_FLIGHT + * @exception (For trains only): AIVehicle::ERR_VEHICLE_NO_POWER + * @return True if and only if the vehicle has been started or stopped. + */ + static bool StartStopVehicle(VehicleID vehicle_id); + + /** + * Skips the current order of the given vehicle. + * @param vehicle_id The vehicle to skip the order for. + * @param order_position The selected order to which we want to skip. + * @pre IsValidVehicleOrder(vehicle_id, order_position). + * @return True if and only if the order has been skipped. + */ + static bool SkipToVehicleOrder(VehicleID vehicle_id, AIOrder::OrderPosition order_position); + + /** + * Turn the given vehicle so it'll drive the other way. + * @param vehicle_id The vehicle to turn. + * @pre IsValidVehicle(vehicle_id). + * @pre GetVehicleType(vehicle_id) == VEHICLE_ROAD || GetVehicleType(vehicle_id) == VEHICLE_RAIL. + * @return True if and only if the vehicle has started to turn. + * @note Vehicles cannot always be reversed. For example busses and trucks need to be running + * and not be inside a depot. + */ + static bool ReverseVehicle(VehicleID vehicle_id); + + /** + * Get the maximum amount of a specific cargo the given vehicle can transport. + * @param vehicle_id The vehicle to get the capacity of. + * @param cargo The cargo to get the capacity for. + * @pre IsValidVehicle(vehicle_id). + * @pre AICargo::IsValidCargo(cargo). + * @return The maximum amount of the given cargo the vehicle can transport. + */ + static int32 GetCapacity(VehicleID vehicle_id, CargoID cargo); + + /** + * Get the length of a the total vehicle in 1/16's of a tile. + * @param vehicle_id The vehicle to get the length of. + * @pre IsValidVehicle(vehicle_id). + * @pre GetVehicleType(vehicle_id) == AIVehicle.VEHICLE_ROAD || GetVehicleType(vehicle_id) == AIVehicle.VEHICLE_RAIL. + * @return The length of the engine. + */ + static int GetLength(VehicleID vehicle_id); + + /** + * Get the amount of a specific cargo the given vehicle transports. + * @param vehicle_id The vehicle to get the load amount of. + * @param cargo The cargo to get the load amount for. + * @pre IsValidVehicle(vehicle_id). + * @pre AICargo::IsValidCargo(cargo). + * @return The amount of the given cargo the vehicle currently transports. + */ + static int32 GetCargoLoad(VehicleID vehicle_id, CargoID cargo); + + /** + * Get the group of a given vehicle. + * @param vehicle_id The vehicle to get the group from. + * @return The group of the given vehicle. + */ + static GroupID GetGroupID(VehicleID vehicle_id); + + /** + * Check if the vehicle is articulated. + * @param vehicle_id The vehicle to check. + * @pre IsValidVehicle(vehicle_id). + * @pre GetVehicleType(vehicle_id) == VEHICLE_ROAD || GetVehicleType(vehicle_id) == VEHICLE_RAIL. + * @return True if the vehicle is articulated. + */ + static bool IsArticulated(VehicleID vehicle_id); +}; + +#endif /* AI_VEHICLE_HPP */ diff --git a/src/ai/api/ai_vehicle.hpp.sq b/src/ai/api/ai_vehicle.hpp.sq new file mode 100644 index 000000000..87dce4893 --- /dev/null +++ b/src/ai/api/ai_vehicle.hpp.sq @@ -0,0 +1,141 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_vehicle.hpp" + +namespace SQConvert { + /* Allow enums to be used as Squirrel parameters */ + template <> AIVehicle::ErrorMessages GetParam(ForceType<AIVehicle::ErrorMessages>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AIVehicle::ErrorMessages)tmp; } + template <> int Return<AIVehicle::ErrorMessages>(HSQUIRRELVM vm, AIVehicle::ErrorMessages res) { sq_pushinteger(vm, (int32)res); return 1; } + template <> AIVehicle::VehicleType GetParam(ForceType<AIVehicle::VehicleType>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AIVehicle::VehicleType)tmp; } + template <> int Return<AIVehicle::VehicleType>(HSQUIRRELVM vm, AIVehicle::VehicleType res) { sq_pushinteger(vm, (int32)res); return 1; } + template <> AIVehicle::VehicleState GetParam(ForceType<AIVehicle::VehicleState>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AIVehicle::VehicleState)tmp; } + template <> int Return<AIVehicle::VehicleState>(HSQUIRRELVM vm, AIVehicle::VehicleState res) { sq_pushinteger(vm, (int32)res); return 1; } + + /* Allow AIVehicle to be used as Squirrel parameter */ + template <> AIVehicle *GetParam(ForceType<AIVehicle *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIVehicle *)instance; } + template <> AIVehicle &GetParam(ForceType<AIVehicle &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIVehicle *)instance; } + template <> const AIVehicle *GetParam(ForceType<const AIVehicle *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIVehicle *)instance; } + template <> const AIVehicle &GetParam(ForceType<const AIVehicle &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIVehicle *)instance; } + template <> int Return<AIVehicle *>(HSQUIRRELVM vm, AIVehicle *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIVehicle", res, NULL, DefSQDestructorCallback<AIVehicle>); return 1; } +}; // namespace SQConvert + +void SQAIVehicle_Register(Squirrel *engine) { + DefSQClass <AIVehicle> SQAIVehicle("AIVehicle"); + SQAIVehicle.PreRegister(engine); + SQAIVehicle.AddConstructor<void (AIVehicle::*)(), 1>(engine, "x"); + + SQAIVehicle.DefSQConst(engine, AIVehicle::ERR_VEHICLE_BASE, "ERR_VEHICLE_BASE"); + SQAIVehicle.DefSQConst(engine, AIVehicle::ERR_VEHICLE_TOO_MANY, "ERR_VEHICLE_TOO_MANY"); + SQAIVehicle.DefSQConst(engine, AIVehicle::ERR_VEHICLE_NOT_AVAILABLE, "ERR_VEHICLE_NOT_AVAILABLE"); + SQAIVehicle.DefSQConst(engine, AIVehicle::ERR_VEHICLE_BUILD_DISABLED, "ERR_VEHICLE_BUILD_DISABLED"); + SQAIVehicle.DefSQConst(engine, AIVehicle::ERR_VEHICLE_WRONG_DEPOT, "ERR_VEHICLE_WRONG_DEPOT"); + SQAIVehicle.DefSQConst(engine, AIVehicle::ERR_VEHICLE_CANNOT_SEND_TO_DEPOT, "ERR_VEHICLE_CANNOT_SEND_TO_DEPOT"); + SQAIVehicle.DefSQConst(engine, AIVehicle::ERR_VEHICLE_CANNOT_START_STOP, "ERR_VEHICLE_CANNOT_START_STOP"); + SQAIVehicle.DefSQConst(engine, AIVehicle::ERR_VEHICLE_CANNOT_TURN, "ERR_VEHICLE_CANNOT_TURN"); + SQAIVehicle.DefSQConst(engine, AIVehicle::ERR_VEHICLE_CANNOT_REFIT, "ERR_VEHICLE_CANNOT_REFIT"); + SQAIVehicle.DefSQConst(engine, AIVehicle::ERR_VEHICLE_IS_DESTROYED, "ERR_VEHICLE_IS_DESTROYED"); + SQAIVehicle.DefSQConst(engine, AIVehicle::ERR_VEHICLE_NOT_IN_DEPOT, "ERR_VEHICLE_NOT_IN_DEPOT"); + SQAIVehicle.DefSQConst(engine, AIVehicle::ERR_VEHICLE_IN_FLIGHT, "ERR_VEHICLE_IN_FLIGHT"); + SQAIVehicle.DefSQConst(engine, AIVehicle::ERR_VEHCILE_NO_POWER, "ERR_VEHCILE_NO_POWER"); + SQAIVehicle.DefSQConst(engine, AIVehicle::VEHICLE_RAIL, "VEHICLE_RAIL"); + SQAIVehicle.DefSQConst(engine, AIVehicle::VEHICLE_ROAD, "VEHICLE_ROAD"); + SQAIVehicle.DefSQConst(engine, AIVehicle::VEHICLE_WATER, "VEHICLE_WATER"); + SQAIVehicle.DefSQConst(engine, AIVehicle::VEHICLE_AIR, "VEHICLE_AIR"); + SQAIVehicle.DefSQConst(engine, AIVehicle::VEHICLE_INVALID, "VEHICLE_INVALID"); + SQAIVehicle.DefSQConst(engine, AIVehicle::VS_RUNNING, "VS_RUNNING"); + SQAIVehicle.DefSQConst(engine, AIVehicle::VS_STOPPED, "VS_STOPPED"); + SQAIVehicle.DefSQConst(engine, AIVehicle::VS_IN_DEPOT, "VS_IN_DEPOT"); + SQAIVehicle.DefSQConst(engine, AIVehicle::VS_AT_STATION, "VS_AT_STATION"); + SQAIVehicle.DefSQConst(engine, AIVehicle::VS_BROKEN, "VS_BROKEN"); + SQAIVehicle.DefSQConst(engine, AIVehicle::VS_CRASHED, "VS_CRASHED"); + SQAIVehicle.DefSQConst(engine, AIVehicle::VS_INVALID, "VS_INVALID"); + + AIError::RegisterErrorMap(STR_00E1_TOO_MANY_VEHICLES_IN_GAME, AIVehicle::ERR_VEHICLE_TOO_MANY); + AIError::RegisterErrorMap(STR_AIRCRAFT_NOT_AVAILABLE, AIVehicle::ERR_VEHICLE_NOT_AVAILABLE); + AIError::RegisterErrorMap(STR_ROAD_VEHICLE_NOT_AVAILABLE, AIVehicle::ERR_VEHICLE_NOT_AVAILABLE); + AIError::RegisterErrorMap(STR_SHIP_NOT_AVAILABLE, AIVehicle::ERR_VEHICLE_NOT_AVAILABLE); + AIError::RegisterErrorMap(STR_RAIL_VEHICLE_NOT_AVAILABLE, AIVehicle::ERR_VEHICLE_NOT_AVAILABLE); + AIError::RegisterErrorMap(STR_A008_CAN_T_BUILD_AIRCRAFT, AIVehicle::ERR_VEHICLE_BUILD_DISABLED); + AIError::RegisterErrorMap(STR_980D_CAN_T_BUILD_SHIP, AIVehicle::ERR_VEHICLE_BUILD_DISABLED); + AIError::RegisterErrorMap(STR_9009_CAN_T_BUILD_ROAD_VEHICLE, AIVehicle::ERR_VEHICLE_BUILD_DISABLED); + AIError::RegisterErrorMap(STR_882B_CAN_T_BUILD_RAILROAD_VEHICLE, AIVehicle::ERR_VEHICLE_BUILD_DISABLED); + AIError::RegisterErrorMap(STR_DEPOT_WRONG_DEPOT_TYPE, AIVehicle::ERR_VEHICLE_WRONG_DEPOT); + AIError::RegisterErrorMap(STR_8830_CAN_T_SEND_TRAIN_TO_DEPOT, AIVehicle::ERR_VEHICLE_CANNOT_SEND_TO_DEPOT); + AIError::RegisterErrorMap(STR_9018_CAN_T_SEND_VEHICLE_TO_DEPOT, AIVehicle::ERR_VEHICLE_CANNOT_SEND_TO_DEPOT); + AIError::RegisterErrorMap(STR_9819_CAN_T_SEND_SHIP_TO_DEPOT, AIVehicle::ERR_VEHICLE_CANNOT_SEND_TO_DEPOT); + AIError::RegisterErrorMap(STR_A012_CAN_T_SEND_AIRCRAFT_TO, AIVehicle::ERR_VEHICLE_CANNOT_SEND_TO_DEPOT); + AIError::RegisterErrorMap(STR_883B_CAN_T_STOP_START_TRAIN, AIVehicle::ERR_VEHICLE_CANNOT_START_STOP); + AIError::RegisterErrorMap(STR_9015_CAN_T_STOP_START_ROAD_VEHICLE, AIVehicle::ERR_VEHICLE_CANNOT_START_STOP); + AIError::RegisterErrorMap(STR_9818_CAN_T_STOP_START_SHIP, AIVehicle::ERR_VEHICLE_CANNOT_START_STOP); + AIError::RegisterErrorMap(STR_A016_CAN_T_STOP_START_AIRCRAFT, AIVehicle::ERR_VEHICLE_CANNOT_START_STOP); + AIError::RegisterErrorMap(STR_8869_CAN_T_REVERSE_DIRECTION, AIVehicle::ERR_VEHICLE_CANNOT_TURN); + AIError::RegisterErrorMap(STR_9033_CAN_T_MAKE_VEHICLE_TURN, AIVehicle::ERR_VEHICLE_CANNOT_TURN); + AIError::RegisterErrorMap(STR_RAIL_CAN_T_REFIT_VEHICLE, AIVehicle::ERR_VEHICLE_CANNOT_REFIT); + AIError::RegisterErrorMap(STR_REFIT_ROAD_VEHICLE_CAN_T, AIVehicle::ERR_VEHICLE_CANNOT_REFIT); + AIError::RegisterErrorMap(STR_9841_CAN_T_REFIT_SHIP, AIVehicle::ERR_VEHICLE_CANNOT_REFIT); + AIError::RegisterErrorMap(STR_A042_CAN_T_REFIT_AIRCRAFT, AIVehicle::ERR_VEHICLE_CANNOT_REFIT); + AIError::RegisterErrorMap(STR_CAN_T_REFIT_DESTROYED_VEHICLE, AIVehicle::ERR_VEHICLE_IS_DESTROYED); + AIError::RegisterErrorMap(STR_CAN_T_SELL_DESTROYED_VEHICLE, AIVehicle::ERR_VEHICLE_IS_DESTROYED); + AIError::RegisterErrorMap(STR_A01B_AIRCRAFT_MUST_BE_STOPPED, AIVehicle::ERR_VEHICLE_NOT_IN_DEPOT); + AIError::RegisterErrorMap(STR_9013_MUST_BE_STOPPED_INSIDE, AIVehicle::ERR_VEHICLE_NOT_IN_DEPOT); + AIError::RegisterErrorMap(STR_TRAIN_MUST_BE_STOPPED, AIVehicle::ERR_VEHICLE_NOT_IN_DEPOT); + AIError::RegisterErrorMap(STR_980B_SHIP_MUST_BE_STOPPED_IN, AIVehicle::ERR_VEHICLE_NOT_IN_DEPOT); + AIError::RegisterErrorMap(STR_A017_AIRCRAFT_IS_IN_FLIGHT, AIVehicle::ERR_VEHICLE_IN_FLIGHT); + AIError::RegisterErrorMap(STR_TRAIN_START_NO_CATENARY, AIVehicle::ERR_VEHCILE_NO_POWER); + + AIError::RegisterErrorMapString(AIVehicle::ERR_VEHICLE_TOO_MANY, "ERR_VEHICLE_TOO_MANY"); + AIError::RegisterErrorMapString(AIVehicle::ERR_VEHICLE_NOT_AVAILABLE, "ERR_VEHICLE_NOT_AVAILABLE"); + AIError::RegisterErrorMapString(AIVehicle::ERR_VEHICLE_BUILD_DISABLED, "ERR_VEHICLE_BUILD_DISABLED"); + AIError::RegisterErrorMapString(AIVehicle::ERR_VEHICLE_WRONG_DEPOT, "ERR_VEHICLE_WRONG_DEPOT"); + AIError::RegisterErrorMapString(AIVehicle::ERR_VEHICLE_CANNOT_SEND_TO_DEPOT, "ERR_VEHICLE_CANNOT_SEND_TO_DEPOT"); + AIError::RegisterErrorMapString(AIVehicle::ERR_VEHICLE_CANNOT_START_STOP, "ERR_VEHICLE_CANNOT_START_STOP"); + AIError::RegisterErrorMapString(AIVehicle::ERR_VEHICLE_CANNOT_TURN, "ERR_VEHICLE_CANNOT_TURN"); + AIError::RegisterErrorMapString(AIVehicle::ERR_VEHICLE_CANNOT_REFIT, "ERR_VEHICLE_CANNOT_REFIT"); + AIError::RegisterErrorMapString(AIVehicle::ERR_VEHICLE_IS_DESTROYED, "ERR_VEHICLE_IS_DESTROYED"); + AIError::RegisterErrorMapString(AIVehicle::ERR_VEHICLE_NOT_IN_DEPOT, "ERR_VEHICLE_NOT_IN_DEPOT"); + AIError::RegisterErrorMapString(AIVehicle::ERR_VEHICLE_IN_FLIGHT, "ERR_VEHICLE_IN_FLIGHT"); + AIError::RegisterErrorMapString(AIVehicle::ERR_VEHCILE_NO_POWER, "ERR_VEHCILE_NO_POWER"); + + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::GetClassName, "GetClassName", 1, "x"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::IsValidVehicle, "IsValidVehicle", 2, "xi"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::GetNumWagons, "GetNumWagons", 2, "xi"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::SetName, "SetName", 3, "xis"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::GetName, "GetName", 2, "xi"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::GetLocation, "GetLocation", 2, "xi"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::GetEngineType, "GetEngineType", 2, "xi"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::GetWagonEngineType, "GetWagonEngineType", 3, "xii"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::GetUnitNumber, "GetUnitNumber", 2, "xi"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::GetAge, "GetAge", 2, "xi"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::GetWagonAge, "GetWagonAge", 3, "xii"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::GetMaxAge, "GetMaxAge", 2, "xi"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::GetAgeLeft, "GetAgeLeft", 2, "xi"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::GetCurrentSpeed, "GetCurrentSpeed", 2, "xi"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::GetState, "GetState", 2, "xi"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::GetRunningCost, "GetRunningCost", 2, "xi"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::GetProfitThisYear, "GetProfitThisYear", 2, "xi"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::GetProfitLastYear, "GetProfitLastYear", 2, "xi"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::GetCurrentValue, "GetCurrentValue", 2, "xi"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::GetVehicleType, "GetVehicleType", 2, "xi"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::GetRoadType, "GetRoadType", 2, "xi"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::IsInDepot, "IsInDepot", 2, "xi"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::IsStoppedInDepot, "IsStoppedInDepot", 2, "xi"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::BuildVehicle, "BuildVehicle", 3, "xii"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::CloneVehicle, "CloneVehicle", 4, "xiib"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::MoveWagon, "MoveWagon", 6, "xiibii"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::GetRefitCapacity, "GetRefitCapacity", 3, "xii"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::RefitVehicle, "RefitVehicle", 3, "xii"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::SellVehicle, "SellVehicle", 2, "xi"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::SellWagon, "SellWagon", 4, "xiib"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::SendVehicleToDepot, "SendVehicleToDepot", 2, "xi"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::StartStopVehicle, "StartStopVehicle", 2, "xi"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::SkipToVehicleOrder, "SkipToVehicleOrder", 3, "xii"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::ReverseVehicle, "ReverseVehicle", 2, "xi"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::GetCapacity, "GetCapacity", 3, "xii"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::GetLength, "GetLength", 2, "xi"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::GetCargoLoad, "GetCargoLoad", 3, "xii"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::GetGroupID, "GetGroupID", 2, "xi"); + SQAIVehicle.DefSQStaticMethod(engine, &AIVehicle::IsArticulated, "IsArticulated", 2, "xi"); + + SQAIVehicle.PostRegister(engine); +} diff --git a/src/ai/api/ai_vehiclelist.cpp b/src/ai/api/ai_vehiclelist.cpp new file mode 100644 index 000000000..127ac77e9 --- /dev/null +++ b/src/ai/api/ai_vehiclelist.cpp @@ -0,0 +1,37 @@ +/* $Id$ */ + +/** @file ai_vehiclelist.cpp Implementation of AIVehicleList and friends. */ + +#include "ai_vehiclelist.hpp" +#include "ai_station.hpp" +#include "../../openttd.h" +#include "../../company_func.h" +#include "../../vehicle_base.h" + +AIVehicleList::AIVehicleList() +{ + Vehicle *v; + FOR_ALL_VEHICLES(v) { + if (v->owner == _current_company && v->IsPrimaryVehicle()) this->AddItem(v->index); + } +} + +AIVehicleList_Station::AIVehicleList_Station(StationID station_id) +{ + if (!AIStation::IsValidStation(station_id)) return; + + Vehicle *v; + + FOR_ALL_VEHICLES(v) { + if (v->owner == _current_company && v->IsPrimaryVehicle()) { + const Order *order; + + FOR_VEHICLE_ORDERS(v, order) { + if (order->IsType(OT_GOTO_STATION) && order->GetDestination() == station_id) { + this->AddItem(v->index); + break; + } + } + } + } +} diff --git a/src/ai/api/ai_vehiclelist.hpp b/src/ai/api/ai_vehiclelist.hpp new file mode 100644 index 000000000..0e91d75bc --- /dev/null +++ b/src/ai/api/ai_vehiclelist.hpp @@ -0,0 +1,34 @@ +/* $Id$ */ + +/** @file ai_vehiclelist.hpp List all the vehicles (you own). */ + +#ifndef AI_VEHICLELIST_HPP +#define AI_VEHICLELIST_HPP + +#include "ai_abstractlist.hpp" + +/** + * Creates a list of vehicles of which you are the owner. + * @ingroup AIList + */ +class AIVehicleList : public AIAbstractList { +public: + static const char *GetClassName() { return "AIVehicleList"; } + AIVehicleList(); +}; + +/** + * Creates a list of vehicles that have orders to a given station. + * @ingroup AIList + */ +class AIVehicleList_Station : public AIAbstractList { +public: + static const char *GetClassName() { return "AIVehicleList_Station"; } + + /** + * @param station_id The station to get the list of vehicles that have orders to him from. + */ + AIVehicleList_Station(StationID station_id); +}; + +#endif /* AI_VEHICLELIST_HPP */ diff --git a/src/ai/api/ai_vehiclelist.hpp.sq b/src/ai/api/ai_vehiclelist.hpp.sq new file mode 100644 index 000000000..5a5f930cb --- /dev/null +++ b/src/ai/api/ai_vehiclelist.hpp.sq @@ -0,0 +1,42 @@ +/* $Id$ */ +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "ai_vehiclelist.hpp" + +namespace SQConvert { + /* Allow AIVehicleList to be used as Squirrel parameter */ + template <> AIVehicleList *GetParam(ForceType<AIVehicleList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIVehicleList *)instance; } + template <> AIVehicleList &GetParam(ForceType<AIVehicleList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIVehicleList *)instance; } + template <> const AIVehicleList *GetParam(ForceType<const AIVehicleList *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIVehicleList *)instance; } + template <> const AIVehicleList &GetParam(ForceType<const AIVehicleList &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIVehicleList *)instance; } + template <> int Return<AIVehicleList *>(HSQUIRRELVM vm, AIVehicleList *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIVehicleList", res, NULL, DefSQDestructorCallback<AIVehicleList>); return 1; } +}; // namespace SQConvert + +void SQAIVehicleList_Register(Squirrel *engine) { + DefSQClass <AIVehicleList> SQAIVehicleList("AIVehicleList"); + SQAIVehicleList.PreRegister(engine, "AIAbstractList"); + SQAIVehicleList.AddConstructor<void (AIVehicleList::*)(), 1>(engine, "x"); + + SQAIVehicleList.DefSQStaticMethod(engine, &AIVehicleList::GetClassName, "GetClassName", 1, "x"); + + SQAIVehicleList.PostRegister(engine); +} + +namespace SQConvert { + /* Allow AIVehicleList_Station to be used as Squirrel parameter */ + template <> AIVehicleList_Station *GetParam(ForceType<AIVehicleList_Station *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIVehicleList_Station *)instance; } + template <> AIVehicleList_Station &GetParam(ForceType<AIVehicleList_Station &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIVehicleList_Station *)instance; } + template <> const AIVehicleList_Station *GetParam(ForceType<const AIVehicleList_Station *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (AIVehicleList_Station *)instance; } + template <> const AIVehicleList_Station &GetParam(ForceType<const AIVehicleList_Station &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AIVehicleList_Station *)instance; } + template <> int Return<AIVehicleList_Station *>(HSQUIRRELVM vm, AIVehicleList_Station *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AIVehicleList_Station", res, NULL, DefSQDestructorCallback<AIVehicleList_Station>); return 1; } +}; // namespace SQConvert + +void SQAIVehicleList_Station_Register(Squirrel *engine) { + DefSQClass <AIVehicleList_Station> SQAIVehicleList_Station("AIVehicleList_Station"); + SQAIVehicleList_Station.PreRegister(engine, "AIAbstractList"); + SQAIVehicleList_Station.AddConstructor<void (AIVehicleList_Station::*)(StationID station_id), 2>(engine, "xi"); + + SQAIVehicleList_Station.DefSQStaticMethod(engine, &AIVehicleList_Station::GetClassName, "GetClassName", 1, "x"); + + SQAIVehicleList_Station.PostRegister(engine); +} diff --git a/src/ai/api/squirrel_export.awk b/src/ai/api/squirrel_export.awk new file mode 100644 index 000000000..877586722 --- /dev/null +++ b/src/ai/api/squirrel_export.awk @@ -0,0 +1,385 @@ +# $Id$ +# +# Awk script to automatically generate the code needed +# to export the AI API to Squirrel. +# +# Note that arrays are 1 based... +# + +# Simple insertion sort. +function array_sort(ARRAY, ELEMENTS, temp, i, j) { + for (i = 2; i <= ELEMENTS; i++) + for (j = i; ARRAY[j - 1] > ARRAY[j]; --j) { + temp = ARRAY[j] + ARRAY[j] = ARRAY[j - 1] + ARRAY[j - 1] = temp + } + return +} + +function dump_class_templates(name) { + print " template <> " name " *GetParam(ForceType<" name " *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (" name " *)instance; }" + print " template <> " name " &GetParam(ForceType<" name " &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(" name " *)instance; }" + print " template <> const " name " *GetParam(ForceType<const " name " *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (" name " *)instance; }" + print " template <> const " name " &GetParam(ForceType<const " name " &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(" name " *)instance; }" + if (name == "AIEvent") { + print " template <> int Return<" name " *>(HSQUIRRELVM vm, " name " *res) { if (res == NULL) { sq_pushnull(vm); return 1; } Squirrel::CreateClassInstanceVM(vm, \"" name "\", res, NULL, DefSQDestructorCallback<" name ">); return 1; }" + } else { + print " template <> int Return<" name " *>(HSQUIRRELVM vm, " name " *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, \"" name "\", res, NULL, DefSQDestructorCallback<" name ">); return 1; }" + } +} + +BEGIN { + enum_size = 0 + enum_value_size = 0 + enum_string_to_error_size = 0 + enum_error_to_string_size = 0 + struct_size = 0 + method_size = 0 + static_method_size = 0 + virtual_class = "false" + super_cls = "" + cls = "" + start_squirrel_define_on_next_line = "false" + cls_level = 0 + RS = "\r|\n" +} + +/@file/ { + # Break it in two lines, so SVN doesn't replace it + printf "/* $I" + print "d$ */" + print "/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */" + print "" + print "#include \"" $3 "\"" +} + +# Remove the old squirrel stuff +/#ifdef DEFINE_SQUIRREL_CLASS/ { squirrel_stuff = "true"; next; } +/^#endif \/\* DEFINE_SQUIRREL_CLASS \*\// { if (squirrel_stuff == "true") { squirrel_stuff = "false"; next; } } +{ if (squirrel_stuff == "true") next; } + +# Ignore forward declarations of classes +/^( *)class(.*);/ { next; } +# We only want to have public functions exported for now +/^( *)class/ { + if (cls_level == 0) { + public = "false" + cls_param[0] = "" + cls_param[1] = 1 + cls_param[2] = "x" + cls = $2 + if (match($4, "public") || match($4, "protected") || match($4, "private")) { + super_cls = $5 + } else { + super_cls = $4 + } + } else if (cls_level == 1) { + struct_size++ + structs[struct_size] = cls "::" $2 + } + cls_level++ + next +} +/^( *)public/ { if (cls_level == 1) public = "true"; next; } +/^( *)protected/ { if (cls_level == 1) public = "false"; next; } +/^( *)private/ { if (cls_level == 1) public = "false"; next; } + +# Ignore special doxygen blocks +/^#ifndef DOXYGEN_SKIP/ { doxygen_skip = "next"; next; } +/^#ifdef DOXYGEN_SKIP/ { doxygen_skip = "true"; next; } +/^#endif/ { doxygen_skip = "false"; next; } +/^#else/ { + if (doxygen_skip == "next") { + doxygen_skip = "true"; + } else { + doxygen_skip = "false"; + } + next; +} +{ if (doxygen_skip == "true") next } + +# Ignore the comments +/^#/ { next; } +/\/\*.*\*\// { comment = "false"; next; } +/\/\*/ { comment = "true"; next; } +/\*\// { comment = "false"; next; } +{ if (comment == "true") next } + +# We need to make specialized conversions for structs +/^( *)struct/ { + cls_level++ + if (public == "false") next + if (cls_level != 1) next + struct_size++ + structs[struct_size] = cls "::" $2 + next +} + +# We need to make specialized conversions for enums +/^( *)enum/ { + cls_level++ + if (public == "false") next; + in_enum = "true" + enum_size++ + enums[enum_size] = cls "::" $2 + next +} + +# Maybe the end of the class, if so we can start with the Squirrel export pretty soon +/};/ { + cls_level-- + if (cls_level != 0) { + in_enum = "false"; + next; + } + if (cls == "") { + next; + } + start_squirrel_define_on_next_line = "true" + next; +} + +# Empty/white lines. When we may do the Squirrel export, do that export. +/^([ ]*)$/ { + if (start_squirrel_define_on_next_line == "false") next + spaces = " "; + public = "false" + namespace_opened = "false" + + print "" + + # First check whether we have enums to print + if (enum_size != 0) { + if (namespace_opened == "false") { + print "namespace SQConvert {" + namespace_opened = "true" + } + print " /* Allow enums to be used as Squirrel parameters */" + for (i = 1; i <= enum_size; i++) { + print " template <> " enums[i] " GetParam(ForceType<" enums[i] ">, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (" enums[i] ")tmp; }" + print " template <> int Return<" enums[i] ">(HSQUIRRELVM vm, " enums[i] " res) { sq_pushinteger(vm, (int32)res); return 1; }" + delete enums[i] + } + } + + # Then check whether we have structs/classes to print + if (struct_size != 0) { + if (namespace_opened == "false") { + print "namespace SQConvert {" + namespace_opened = "true" + } + print " /* Allow inner classes/structs to be used as Squirrel parameters */" + for (i = 1; i <= struct_size; i++) { + dump_class_templates(structs[i]) + delete structs[i] + } + } + + if (namespace_opened == "false") { + print "namespace SQConvert {" + namespace_opened = "true" + } else { + print "" + } + print " /* Allow " cls " to be used as Squirrel parameter */" + dump_class_templates(cls) + + print "}; // namespace SQConvert" + + print ""; + # Then do the registration functions of the class. */ + print "void SQ" cls "_Register(Squirrel *engine) {" + print " DefSQClass <" cls "> SQ" cls "(\"" cls "\");" + if (super_cls == "AIObject" || super_cls == "AIAbstractList::Valuator") { + print " SQ" cls ".PreRegister(engine);" + } else { + print " SQ" cls ".PreRegister(engine, \"" super_cls "\");" + } + if (virtual_class == "false" && super_cls != "AIEvent") { + print " SQ" cls ".AddConstructor<void (" cls "::*)(" cls_param[0] "), " cls_param[1]">(engine, \"" cls_param[2] "\");" + } + print "" + + # Enum values + mlen = 0 + for (i = 1; i <= enum_value_size; i++) { + if (mlen <= length(enum_value[i])) mlen = length(enum_value[i]) + } + for (i = 1; i <= enum_value_size; i++) { + print " SQ" cls ".DefSQConst(engine, " cls "::" enum_value[i] ", " substr(spaces, 1, mlen - length(enum_value[i])) "\"" enum_value[i] "\");" + delete enum_value[i] + } + if (enum_value_size != 0) print "" + + # Mapping of OTTD strings to errors + mlen = 0 + for (i = 1; i <= enum_string_to_error_size; i++) { + if (mlen <= length(enum_string_to_error_mapping_string[i])) mlen = length(enum_string_to_error_mapping_string[i]) + } + for (i = 1; i <= enum_string_to_error_size; i++) { + print " AIError::RegisterErrorMap(" enum_string_to_error_mapping_string[i] ", " substr(spaces, 1, mlen - length(enum_string_to_error_mapping_string[i])) cls "::" enum_string_to_error_mapping_error[i] ");" + + delete enum_string_to_error_mapping_string[i] + } + if (enum_string_to_error_size != 0) print "" + + # Mapping of errors to human 'readable' strings. + mlen = 0 + for (i = 1; i <= enum_error_to_string_size; i++) { + if (mlen <= length(enum_error_to_string_mapping[i])) mlen = length(enum_error_to_string_mapping[i]) + } + for (i = 1; i <= enum_error_to_string_size; i++) { + print " AIError::RegisterErrorMapString(" cls "::" enum_error_to_string_mapping[i] ", " substr(spaces, 1, mlen - length(enum_error_to_string_mapping[i])) "\"" enum_error_to_string_mapping[i] "\");" + delete enum_error_to_string_mapping[i] + } + if (enum_error_to_string_size != 0) print "" + + # Static methods + mlen = 0 + for (i = 1; i <= static_method_size; i++) { + if (mlen <= length(static_methods[i, 0])) mlen = length(static_methods[i, 0]) + } + for (i = 1; i <= static_method_size; i++) { + print " SQ" cls ".DefSQStaticMethod(engine, &" cls "::" static_methods[i, 0] ", " substr(spaces, 1, mlen - length(static_methods[i, 0])) "\"" static_methods[i, 0] "\", " substr(spaces, 1, mlen - length(static_methods[i, 0])) "" static_methods[i, 1] ", \"" static_methods[i, 2] "\");" + delete static_methods[i] + } + if (static_method_size != 0) print "" + + if (virtual_class == "false") { + # Non-static methods + mlen = 0 + for (i = 1; i <= method_size; i++) { + if (mlen <= length(methods[i, 0])) mlen = length(methods[i, 0]) + } + for (i = 1; i <= method_size; i++) { + if (methods[i, 2] == "v") { + print " SQ" cls ".DefSQAdvancedMethod(engine, &" cls "::" methods[i, 0] ", " substr(spaces, 1, mlen - length(methods[i, 0]) - 8) "\"" methods[i, 0] "\");" + } else { + print " SQ" cls ".DefSQMethod(engine, &" cls "::" methods[i, 0] ", " substr(spaces, 1, mlen - length(methods[i, 0])) "\"" methods[i, 0] "\", " substr(spaces, 1, mlen - length(methods[i, 0])) "" methods[i, 1] ", \"" methods[i, 2] "\");" + } + delete methods[i] + } + if (method_size != 0) print "" + } + print " SQ" cls ".PostRegister(engine);" + print "}" + + enum_size = 0 + enum_value_size = 0 + enum_string_to_error_size = 0 + enum_error_to_string_size = 0 + struct_size = 0 + method_size = 0 + static_method_size = 0 + virtual_class = "false" + cls = "" + start_squirrel_define_on_next_line = "false" + cls_level = 0 +} + +# Skip non-public functions +{ if (public == "false") next } + +# Add enums +{ + if (in_enum == "true") { + enum_value_size++ + sub(",", "", $1) + enum_value[enum_value_size] = $1 + + # Check if this a special error enum + if (match(enums[enum_size], ".*::ErrorMessages") != 0) { + # syntax: + # enum ErrorMessages { + # ERR_SOME_ERROR, // [STR_ITEM1, STR_ITEM2, ...] + # } + + # Set the mappings + if (match($0, "\\[.*\\]") != 0) { + mappings = substr($0, RSTART, RLENGTH); + gsub("([\\[[:space:]\\]])", "", mappings); + + split(mappings, mapitems, ","); + for (i = 1; i <= length(mapitems); i++) { + enum_string_to_error_size++ + enum_string_to_error_mapping_string[enum_string_to_error_size] = mapitems[i] + enum_string_to_error_mapping_error[enum_string_to_error_size] = $1 + } + + enum_error_to_string_size++ + enum_error_to_string_mapping[enum_error_to_string_size] = $1 + } + } + next + } +} + +# Add a method to the list +/^.*\(.*\).*$/ { + if (cls_level != 1) next + if (match($0, "~")) next + + is_static = match($0, "static") + if (match($0, "virtual")) { + virtual_class = "true" + } + gsub("virtual", "", $0) + gsub("static", "", $0) + gsub("const", "", $0) + gsub("{.*", "", $0) + param_s = $0 + gsub("\\*", "", $0) + gsub("\\(.*", "", $0) + + sub(".*\\(", "", param_s) + sub("\\).*", "", param_s) + + funcname = $2 + if ($1 == cls && funcname == "") { + cls_param[0] = param_s + if (param_s == "") next + } else if (funcname == "") next + + split(param_s, params, ",") + types = "x" + for (len = 1; params[len] != ""; len++) { + sub("^[ ]*", "", params[len]) + if (match(params[len], "\\*") || match(params[len], "&")) { + if (match(params[len], "^char")) { + types = types "s" + } else if (match(params[len], "^void")) { + types = types "p" + } else if (match(params[len], "^Array")) { + types = types "a" + } else if (match(params[len], "^struct Array")) { + types = types "a" + } else { + types = types "x" + } + } else if (match(params[len], "^bool")) { + types = types "b" + } else if (match(params[len], "^HSQUIRRELVM")) { + types = "v" + } else { + types = types "i" + } + } + + if ($1 == cls && funcname == "") { + cls_param[1] = len; + cls_param[2] = types; + } else if (substr(funcname, 0, 1) == "_" && types != "v") { + } else if (is_static) { + static_method_size++ + static_methods[static_method_size, 0] = funcname + static_methods[static_method_size, 1] = len + static_methods[static_method_size, 2] = types + } else { + method_size++ + methods[method_size, 0] = funcname + methods[method_size, 1] = len + methods[method_size, 2] = types + } + next +} diff --git a/src/ai/api/squirrel_export.sh b/src/ai/api/squirrel_export.sh new file mode 100644 index 000000000..ac324390f --- /dev/null +++ b/src/ai/api/squirrel_export.sh @@ -0,0 +1,93 @@ +#!/bin/bash + +# This must be called from within the src/ai/api directory. + +if [ -z "$1" ]; then + for f in `ls *.hpp`; do + case "${f}" in + # these files should not be changed by this script + "ai_controller.hpp" | "ai_object.hpp" | "ai_types.hpp" ) continue; + esac + awk -f squirrel_export.awk ${f} > ${f}.tmp + if ! [ -f "${f}.sq" ] || [ -n "`diff -I '$Id' -b ${f}.tmp ${f}.sq 2> /dev/null || echo boo`" ]; then + mv ${f}.tmp ${f}.sq + echo "Updated: ${f}.sq" + svn add ${f}.sq > /dev/null 2>&1 + svn propset svn:eol-style native ${f}.sq > /dev/null 2>&1 + svn propset svn:keywords Id ${f}.sq > /dev/null 2>&1 + else + rm -f ${f}.tmp + fi + done +else + awk -f squirrel_export.awk $1 > $1.tmp + if ! [ -f "${f}.sq" ] || [ -n "`diff -I '$Id' -b $1.sq $1.tmp 2> /dev/null || echo boo`" ]; then + mv $1.tmp $1.sq + echo "Updated: $1.sq" + svn add $1.sq > /dev/null 2>&1 + svn propset svn:eol-style native $1.sq > /dev/null 2>&1 + svn propset svn:keywords Id $1.sq > /dev/null 2>&1 + else + rm -f $1.tmp + fi +fi + +# Remove .hpp.sq if .hpp doesn't exist anymore +for f in `ls *.hpp.sq`; do + f=`echo ${f} | sed "s/.hpp.sq$/.hpp/"` + if [ ! -f ${f} ];then + echo "Deleted: ${f}.sq" + svn del --force ${f}.sq > /dev/null 2>&1 + fi +done + +# Add stuff to ai_instance.cpp +f='../ai_instance.cpp' + +functions=`` + +echo " +{ } +/.hpp.sq/ { next } +/squirrel_register_std/ { next } +/SQAIController_Register/ { print \$0; next } +/SQAI.*_Register/ { next } + +/Note: this line a marker in squirrel_export.sh. Do not change!/ { + print \$0 + gsub(\"^.*/\", \"\") + split(\"`grep '^void SQAI.*_Register(Squirrel \*engine) {$' *.hpp.sq | sed 's/:.*$//' | sort | uniq | tr -d '\r' | tr '\n' ' '`\", files, \" \") + + for (i = 1; files[i] != \"\"; i++) { + print \"#include \\\"api/\" files[i] \"\\\"\" \$0 + } + + next; +} + +/\/\* Register all classes \*\// { + print \$0 + gsub(\"^.*/\", \"\") + print \" squirrel_register_std(this->engine);\" \$0 + split(\"`grep '^void SQAI.*_Register(Squirrel \*engine) {$' *.hpp.sq | sed 's/^.*void //;s/Squirrel \*/this->/;s/ {/;/;s/_Register/0000Register/g;' | sort | sed 's/0000Register/_Register/g' | tr -d '\r' | tr '\n' ' '`\", regs, \" \") + + for (i = 1; regs[i] != \"\"; i++) { + if (regs[i] == \"SQAIController_Register(this->engine);\") continue + print \" \" regs[i] \$0 + } + + next +} + +{ print \$0; } +" > ${f}.awk + +awk -f ${f}.awk ${f} > ${f}.tmp + +if ! [ -f "${f}" ] || [ -n "`diff -I '$Id' -b ${f} ${f}.tmp 2> /dev/null || echo boo`" ]; then + mv ${f}.tmp ${f} + echo "Updated: ${f}" +else + rm -f ${f}.tmp +fi +rm -f ${f}.awk diff --git a/src/ai/default/default.cpp b/src/ai/default/default.cpp deleted file mode 100644 index 74d9b7ac5..000000000 --- a/src/ai/default/default.cpp +++ /dev/null @@ -1,4028 +0,0 @@ -/* $Id$ */ - -/** @file default.cpp The original AI. */ - -#include "../../stdafx.h" -#include "../../openttd.h" -#include "../../aircraft.h" -#include "../../bridge_map.h" -#include "../../tile_cmd.h" -#include "../../landscape.h" -#include "../../rail_map.h" -#include "../../road_map.h" -#include "../../roadveh.h" -#include "../../station_map.h" -#include "../../tunnel_map.h" -#include "../../command_func.h" -#include "../../town.h" -#include "../../industry.h" -#include "../../pathfind.h" -#include "../../airport.h" -#include "../../variables.h" -#include "../../bridge.h" -#include "../../date_func.h" -#include "../../tunnelbridge_map.h" -#include "../../window_func.h" -#include "../../vehicle_func.h" -#include "../../functions.h" -#include "../../company_func.h" -#include "../../company_base.h" -#include "../../settings_type.h" -#include "default.h" -#include "../../tunnelbridge.h" -#include "../../order_func.h" - -#include "../../table/ai_rail.h" - -// remove some day perhaps? -static uint _ai_service_interval; -CompanyAI _companies_ai[MAX_COMPANIES]; - -typedef void AiStateAction(Company *c); - -enum { - AIS_0 = 0, - AIS_1 = 1, - AIS_VEH_LOOP = 2, - AIS_VEH_CHECK_REPLACE_VEHICLE = 3, - AIS_VEH_DO_REPLACE_VEHICLE = 4, - AIS_WANT_NEW_ROUTE = 5, - AIS_BUILD_DEFAULT_RAIL_BLOCKS = 6, - AIS_BUILD_RAIL = 7, - AIS_BUILD_RAIL_VEH = 8, - AIS_DELETE_RAIL_BLOCKS = 9, - AIS_BUILD_DEFAULT_ROAD_BLOCKS = 10, - AIS_BUILD_ROAD = 11, - AIS_BUILD_ROAD_VEHICLES = 12, - AIS_DELETE_ROAD_BLOCKS = 13, - AIS_AIRPORT_STUFF = 14, - AIS_BUILD_DEFAULT_AIRPORT_BLOCKS = 15, - AIS_BUILD_AIRCRAFT_VEHICLES = 16, - AIS_CHECK_SHIP_STUFF = 17, - AIS_BUILD_DEFAULT_SHIP_BLOCKS = 18, - AIS_DO_SHIP_STUFF = 19, - AIS_SELL_VEHICLE = 20, - AIS_REMOVE_STATION = 21, - AIS_REMOVE_TRACK = 22, - AIS_REMOVE_SINGLE_RAIL_TILE = 23 -}; - - -static inline TrackBits GetRailTrackStatus(TileIndex tile) -{ - return TrackStatusToTrackBits(GetTileTrackStatus(tile, TRANSPORT_RAIL, 0)); -} - - -static void AiCase0(Company *c) -{ - _companies_ai[c->index].state = AIS_REMOVE_TRACK; - _companies_ai[c->index].state_counter = 0; -} - -static void AiCase1(Company *c) -{ - _companies_ai[c->index].cur_veh = NULL; - _companies_ai[c->index].state = AIS_VEH_LOOP; -} - -static void AiStateVehLoop(Company *c) -{ - Vehicle *v; - uint index; - - index = (_companies_ai[c->index].cur_veh == NULL) ? 0 : _companies_ai[c->index].cur_veh->index + 1; - - FOR_ALL_VEHICLES_FROM(v, index) { - if (v->owner != _current_company) continue; - - if ((v->type == VEH_TRAIN && v->subtype == 0) || - v->type == VEH_ROAD || - (v->type == VEH_AIRCRAFT && IsNormalAircraft(v)) || - v->type == VEH_SHIP) { - /* replace engine? */ - if (v->type == VEH_TRAIN && v->engine_type < 3 && - (_price.build_railvehicle >> 3) < c->money) { - _companies_ai[c->index].state = AIS_VEH_CHECK_REPLACE_VEHICLE; - _companies_ai[c->index].cur_veh = v; - return; - } - - /* not profitable? */ - if (v->age >= 730 && - v->profit_last_year < _price.station_value * 5 * 256 && - v->profit_this_year < _price.station_value * 5 * 256) { - _companies_ai[c->index].state_counter = 0; - _companies_ai[c->index].state = AIS_SELL_VEHICLE; - _companies_ai[c->index].cur_veh = v; - return; - } - - /* not reliable? */ - if (v->age >= v->max_age || ( - v->age != 0 && - GetEngine(v->engine_type)->reliability < 35389 - )) { - _companies_ai[c->index].state = AIS_VEH_CHECK_REPLACE_VEHICLE; - _companies_ai[c->index].cur_veh = v; - return; - } - } - } - - _companies_ai[c->index].state = AIS_WANT_NEW_ROUTE; - _companies_ai[c->index].state_counter = 0; -} - -static EngineID AiChooseTrainToBuild(RailType railtype, Money money, byte flag, TileIndex tile) -{ - EngineID best_veh_index = INVALID_ENGINE; - byte best_veh_score = 0; - const Engine *e; - - FOR_ALL_ENGINES_OF_TYPE(e, VEH_TRAIN) { - EngineID i = e->index; - const RailVehicleInfo *rvi = &e->u.rail; - - if (!IsCompatibleRail(rvi->railtype, railtype) || - rvi->railveh_type == RAILVEH_WAGON || - (rvi->railveh_type == RAILVEH_MULTIHEAD && flag & 1) || - !HasBit(e->company_avail, _current_company) || - e->reliability < 0x8A3D) { - continue; - } - - /* Don't choose an engine designated for passenger use for freight. */ - if (rvi->ai_passenger_only != 0 && flag == 1) continue; - - CommandCost ret = DoCommand(tile, i, 0, 0, CMD_BUILD_RAIL_VEHICLE); - if (CmdSucceeded(ret) && ret.GetCost() <= money && rvi->ai_rank >= best_veh_score) { - best_veh_score = rvi->ai_rank; - best_veh_index = i; - } - } - - return best_veh_index; -} - -static EngineID AiChooseRoadVehToBuild(CargoID cargo, Money money, TileIndex tile) -{ - EngineID best_veh_index = INVALID_ENGINE; - int32 best_veh_rating = 0; - const Engine *e; - - FOR_ALL_ENGINES_OF_TYPE(e, VEH_ROAD) { - EngineID i = e->index; - const RoadVehicleInfo *rvi = &e->u.road; - - if (!HasBit(e->company_avail, _current_company) || e->reliability < 0x8A3D) { - continue; - } - - /* Skip vehicles which can't take our cargo type */ - if (rvi->cargo_type != cargo && !CanRefitTo(i, cargo)) continue; - - /* Rate and compare the engine by speed & capacity */ - int rating = rvi->max_speed * rvi->capacity; - if (rating <= best_veh_rating) continue; - - CommandCost ret = DoCommand(tile, i, 0, 0, CMD_BUILD_ROAD_VEH); - if (CmdFailed(ret)) continue; - - /* Add the cost of refitting */ - if (rvi->cargo_type != cargo) ret.AddCost(GetRefitCost(i)); - if (ret.GetCost() > money) continue; - - best_veh_rating = rating; - best_veh_index = i; - } - - return best_veh_index; -} - -/** - * Choose aircraft to build. - * @param money current AI money - * @param forbidden forbidden flags - AIR_HELI = 0 (always allowed), AIR_CTOL = 1 (bit 0), AIR_FAST = 2 (bit 1) - * @return EngineID of aircraft to build - */ -static EngineID AiChooseAircraftToBuild(Money money, byte forbidden) -{ - EngineID best_veh_index = INVALID_ENGINE; - Money best_veh_cost = 0; - const Engine *e; - - FOR_ALL_ENGINES_OF_TYPE(e, VEH_AIRCRAFT) { - EngineID i = e->index; - const AircraftVehicleInfo *avi = &e->u.air; - - if (!HasBit(e->company_avail, _current_company) || e->reliability < 0x8A3D) { - continue; - } - - if ((avi->subtype & forbidden) != 0) continue; - - CommandCost ret = DoCommand(0, i, 0, DC_QUERY_COST, CMD_BUILD_AIRCRAFT); - if (CmdSucceeded(ret) && ret.GetCost() <= money && ret.GetCost() >= best_veh_cost) { - best_veh_cost = ret.GetCost(); - best_veh_index = i; - } - } - - return best_veh_index; -} - -static Money AiGetBasePrice(const Company *c) -{ - Money base = _price.station_value; - - // adjust base price when more expensive vehicles are available - switch (_companies_ai[c->index].railtype_to_use) { - default: NOT_REACHED(); - case RAILTYPE_RAIL: break; - case RAILTYPE_ELECTRIC: break; - case RAILTYPE_MONO: base = (base * 3) >> 1; break; - case RAILTYPE_MAGLEV: base *= 2; break; - } - - return base; -} - -static EngineID AiChooseRoadVehToReplaceWith(const Company *c, const Vehicle *v) -{ - Money avail_money = c->money + v->value; - return AiChooseRoadVehToBuild(v->cargo_type, avail_money, v->tile); -} - -static EngineID AiChooseAircraftToReplaceWith(const Company *c, const Vehicle *v) -{ - Money avail_money = c->money + v->value; - - /* determine forbidden aircraft bits */ - byte forbidden = 0; - const Order *o; - - FOR_VEHICLE_ORDERS(v, o) { - if (!o->IsValid()) continue; - if (!IsValidStationID(o->GetDestination())) continue; - const Station *st = GetStation(o->GetDestination()); - if (!(st->facilities & FACIL_AIRPORT)) continue; - - AirportFTAClass::Flags flags = st->Airport()->flags; - if (!(flags & AirportFTAClass::AIRPLANES)) forbidden |= AIR_CTOL | AIR_FAST; // no planes for heliports / oil rigs - if (flags & AirportFTAClass::SHORT_STRIP) forbidden |= AIR_FAST; // no fast planes for small airports - } - - return AiChooseAircraftToBuild( - avail_money, forbidden - ); -} - -static EngineID AiChooseTrainToReplaceWith(const Company *c, const Vehicle *v) -{ - Money avail_money = c->money + v->value; - const Vehicle *u = v; - int num = 0; - - while (++num, u->Next() != NULL) { - u = u->Next(); - } - - // XXX: check if a wagon - return AiChooseTrainToBuild(v->u.rail.railtype, avail_money, 0, v->tile); -} - -static EngineID AiChooseShipToReplaceWith(const Company *c, const Vehicle *v) -{ - /* Ships are not implemented in this (broken) AI */ - return INVALID_ENGINE; -} - -static void AiHandleGotoDepot(Company *c, int cmd) -{ - if (!_companies_ai[c->index].cur_veh->current_order.IsType(OT_GOTO_DEPOT)) - DoCommand(0, _companies_ai[c->index].cur_veh->index, 0, DC_EXEC, cmd); - - if (++_companies_ai[c->index].state_counter <= 1387) { - _companies_ai[c->index].state = AIS_VEH_DO_REPLACE_VEHICLE; - return; - } - - if (_companies_ai[c->index].cur_veh->current_order.IsType(OT_GOTO_DEPOT)) { - _companies_ai[c->index].cur_veh->current_order.MakeDummy(); - InvalidateWindow(WC_VEHICLE_VIEW, _companies_ai[c->index].cur_veh->index); - } -} - -static void AiRestoreVehicleOrders(Vehicle *v, BackuppedOrders *bak) -{ - if (bak->order == NULL) return; - - for (uint i = 0; !bak->order[i].IsType(OT_NOTHING); i++) { - if (!DoCommandP(0, v->index + (i << 16), bak->order[i].Pack(), CMD_INSERT_ORDER | CMD_NO_TEST_IF_IN_NETWORK)) - break; - } -} - -static void AiHandleReplaceTrain(Company *c) -{ - const Vehicle *v = _companies_ai[c->index].cur_veh; - BackuppedOrders orderbak; - EngineID veh; - - // wait until the vehicle reaches the depot. - if (!IsRailDepotTile(v->tile) || v->u.rail.track != TRACK_BIT_DEPOT || !(v->vehstatus & VS_STOPPED)) { - AiHandleGotoDepot(c, CMD_SEND_TRAIN_TO_DEPOT); - return; - } - - veh = AiChooseTrainToReplaceWith(c, v); - if (veh != INVALID_ENGINE) { - TileIndex tile; - - BackupVehicleOrders(v, &orderbak); - tile = v->tile; - - if (CmdSucceeded(DoCommand(0, v->index, 2, DC_EXEC, CMD_SELL_RAIL_WAGON)) && - CmdSucceeded(DoCommand(tile, veh, 0, DC_EXEC, CMD_BUILD_RAIL_VEHICLE))) { - VehicleID veh = _new_vehicle_id; - AiRestoreVehicleOrders(GetVehicle(veh), &orderbak); - DoCommand(0, veh, 0, DC_EXEC, CMD_START_STOP_VEHICLE); - - DoCommand(0, veh, _ai_service_interval, DC_EXEC, CMD_CHANGE_SERVICE_INT); - } - } -} - -static void AiHandleReplaceRoadVeh(Company *c) -{ - const Vehicle *v = _companies_ai[c->index].cur_veh; - BackuppedOrders orderbak; - EngineID veh; - - if (!v->IsStoppedInDepot()) { - AiHandleGotoDepot(c, CMD_SEND_ROADVEH_TO_DEPOT); - return; - } - - veh = AiChooseRoadVehToReplaceWith(c, v); - if (veh != INVALID_ENGINE) { - TileIndex tile; - - BackupVehicleOrders(v, &orderbak); - tile = v->tile; - - if (CmdSucceeded(DoCommand(0, v->index, 0, DC_EXEC, CMD_SELL_ROAD_VEH)) && - CmdSucceeded(DoCommand(tile, veh, 0, DC_EXEC, CMD_BUILD_ROAD_VEH))) { - VehicleID veh = _new_vehicle_id; - - AiRestoreVehicleOrders(GetVehicle(veh), &orderbak); - DoCommand(0, veh, 0, DC_EXEC, CMD_START_STOP_VEHICLE); - DoCommand(0, veh, _ai_service_interval, DC_EXEC, CMD_CHANGE_SERVICE_INT); - } - } -} - -static void AiHandleReplaceAircraft(Company *c) -{ - const Vehicle *v = _companies_ai[c->index].cur_veh; - BackuppedOrders orderbak; - EngineID veh; - - if (!v->IsStoppedInDepot()) { - AiHandleGotoDepot(c, CMD_SEND_AIRCRAFT_TO_HANGAR); - return; - } - - veh = AiChooseAircraftToReplaceWith(c, v); - if (veh != INVALID_ENGINE) { - TileIndex tile; - - BackupVehicleOrders(v, &orderbak); - tile = v->tile; - - if (CmdSucceeded(DoCommand(0, v->index, 0, DC_EXEC, CMD_SELL_AIRCRAFT)) && - CmdSucceeded(DoCommand(tile, veh, 0, DC_EXEC, CMD_BUILD_AIRCRAFT))) { - VehicleID veh = _new_vehicle_id; - AiRestoreVehicleOrders(GetVehicle(veh), &orderbak); - DoCommand(0, veh, 0, DC_EXEC, CMD_START_STOP_VEHICLE); - - DoCommand(0, veh, _ai_service_interval, DC_EXEC, CMD_CHANGE_SERVICE_INT); - } - } -} - -static void AiHandleReplaceShip(Company *c) -{ - /* Ships are not implemented in this (broken) AI */ -} - -typedef EngineID CheckReplaceProc(const Company *c, const Vehicle *v); - -static CheckReplaceProc * const _veh_check_replace_proc[] = { - AiChooseTrainToReplaceWith, - AiChooseRoadVehToReplaceWith, - AiChooseShipToReplaceWith, - AiChooseAircraftToReplaceWith, -}; - -typedef void DoReplaceProc(Company *c); -static DoReplaceProc * const _veh_do_replace_proc[] = { - AiHandleReplaceTrain, - AiHandleReplaceRoadVeh, - AiHandleReplaceShip, - AiHandleReplaceAircraft -}; - -static void AiStateCheckReplaceVehicle(Company *c) -{ - const Vehicle *v = _companies_ai[c->index].cur_veh; - - if (!v->IsValid() || - v->owner != _current_company || - v->type > VEH_SHIP || - _veh_check_replace_proc[v->type - VEH_TRAIN](c, v) == INVALID_ENGINE) { - _companies_ai[c->index].state = AIS_VEH_LOOP; - } else { - _companies_ai[c->index].state_counter = 0; - _companies_ai[c->index].state = AIS_VEH_DO_REPLACE_VEHICLE; - } -} - -static void AiStateDoReplaceVehicle(Company *c) -{ - const Vehicle *v = _companies_ai[c->index].cur_veh; - - _companies_ai[c->index].state = AIS_VEH_LOOP; - // vehicle is not owned by the company anymore, something went very wrong. - if (!v->IsValid() || v->owner != _current_company) return; - _veh_do_replace_proc[v->type - VEH_TRAIN](c); -} - -struct FoundRoute { - int distance; - CargoID cargo; - void *from; - void *to; -}; - -static Town *AiFindRandomTown() -{ - return GetRandomTown(); -} - -static Industry *AiFindRandomIndustry() -{ - int num = RandomRange(GetMaxIndustryIndex()); - if (IsValidIndustryID(num)) return GetIndustry(num); - - return NULL; -} - -static void AiFindSubsidyIndustryRoute(FoundRoute *fr) -{ - uint i; - CargoID cargo; - const Subsidy *s; - Industry *from; - TileIndex to_xy; - - // initially error - fr->distance = -1; - - // Randomize subsidy index.. - i = RandomRange(lengthof(_subsidies) * 3); - if (i >= lengthof(_subsidies)) return; - - s = &_subsidies[i]; - - // Don't want passengers or mail - cargo = s->cargo_type; - if (cargo == CT_INVALID || - cargo == CT_PASSENGERS || - cargo == CT_MAIL || - s->age > 7) { - return; - } - fr->cargo = cargo; - - fr->from = from = GetIndustry(s->from); - - if (cargo == CT_GOODS || cargo == CT_FOOD) { - Town *to_tow = GetTown(s->to); - - if (to_tow->population < (cargo == CT_FOOD ? 200U : 900U)) return; // error - fr->to = to_tow; - to_xy = to_tow->xy; - } else { - Industry *to_ind = GetIndustry(s->to); - - fr->to = to_ind; - to_xy = to_ind->xy; - } - - fr->distance = DistanceManhattan(from->xy, to_xy); -} - -static void AiFindSubsidyPassengerRoute(FoundRoute *fr) -{ - uint i; - const Subsidy *s; - Town *from, *to; - - // initially error - fr->distance = -1; - - // Randomize subsidy index.. - i = RandomRange(lengthof(_subsidies) * 3); - if (i >= lengthof(_subsidies)) return; - - s = &_subsidies[i]; - - // Only want passengers - if (s->cargo_type != CT_PASSENGERS || s->age > 7) return; - fr->cargo = s->cargo_type; - - fr->from = from = GetTown(s->from); - fr->to = to = GetTown(s->to); - - // They must be big enough - if (from->population < 400 || to->population < 400) return; - - fr->distance = DistanceManhattan(from->xy, to->xy); -} - -static void AiFindRandomIndustryRoute(FoundRoute *fr) -{ - Industry *i; - uint32 r; - CargoID cargo; - - // initially error - fr->distance = -1; - - r = Random(); - - // pick a source - fr->from = i = AiFindRandomIndustry(); - if (i == NULL) return; - - // pick a random produced cargo - cargo = i->produced_cargo[0]; - if (r & 1 && i->produced_cargo[1] != CT_INVALID) cargo = i->produced_cargo[1]; - - fr->cargo = cargo; - - // don't allow passengers - if (cargo == CT_INVALID || cargo == CT_PASSENGERS) return; - - if (cargo != CT_GOODS && cargo != CT_FOOD) { - // pick a dest, and see if it can receive - Industry *i2 = AiFindRandomIndustry(); - if (i2 == NULL || i == i2 || - (i2->accepts_cargo[0] != cargo && - i2->accepts_cargo[1] != cargo && - i2->accepts_cargo[2] != cargo)) { - return; - } - - fr->to = i2; - fr->distance = DistanceManhattan(i->xy, i2->xy); - } else { - // pick a dest town, and see if it's big enough - Town *t = AiFindRandomTown(); - - if (t == NULL || t->population < (cargo == CT_FOOD ? 200U : 900U)) return; - - fr->to = t; - fr->distance = DistanceManhattan(i->xy, t->xy); - } -} - -static void AiFindRandomPassengerRoute(FoundRoute *fr) -{ - Town *source; - Town *dest; - - // initially error - fr->distance = -1; - - fr->from = source = AiFindRandomTown(); - if (source == NULL || source->population < 400) return; - - fr->to = dest = AiFindRandomTown(); - if (dest == NULL || source == dest || dest->population < 400) return; - - fr->distance = DistanceManhattan(source->xy, dest->xy); -} - -// Warn: depends on 'xy' being the first element in both Town and Industry -#define GET_TOWN_OR_INDUSTRY_TILE(p) (((Town*)(p))->xy) - -static bool AiCheckIfRouteIsGood(Company *c, FoundRoute *fr, byte bitmask) -{ - TileIndex from_tile, to_tile; - Station *st; - int dist; - uint same_station = 0; - - from_tile = GET_TOWN_OR_INDUSTRY_TILE(fr->from); - to_tile = GET_TOWN_OR_INDUSTRY_TILE(fr->to); - - dist = 0xFFFF; - FOR_ALL_STATIONS(st) { - int cur; - - if (st->owner != _current_company) continue; - cur = DistanceMax(from_tile, st->xy); - if (cur < dist) dist = cur; - cur = DistanceMax(to_tile, st->xy); - if (cur < dist) dist = cur; - if (to_tile == from_tile && st->xy == to_tile) same_station++; - } - - // To prevent the AI from building ten busstations in the same town, do some calculations - // For each road or airport station, we want 350 of population! - if ((bitmask == 2 || bitmask == 4) && - same_station > 2 && - ((Town*)fr->from)->population < same_station * 350) { - return false; - } - - /* Requiring distance to nearest station to be always under 37 tiles may be suboptimal, - * Especially for longer aircraft routes that start and end pretty at any arbitrary place on map - * While it may be nice for AI to cluster their creations together, hardcoded limit is not ideal. - * If AI will randomly start on some isolated spot, it will never get out of there. - * AI will have chance of randomly rejecting routes further than 37 tiles from their network, - * so there will be some attempt to cluster the network together */ - - /* Random value between 37 and 292. Low values are exponentially more likely - * With 50% chance the value will be under 52 tiles */ - int min_distance = 36 + (1 << (Random() % 9)); // 0..8 - - /* Make sure distance to closest station is < min_distance tiles. */ - if (dist != 0xFFFF && dist > min_distance) return false; - - if (_companies_ai[c->index].route_type_mask != 0 && - !(_companies_ai[c->index].route_type_mask & bitmask) && - !Chance16(1, 5)) { - return false; - } - - if (fr->cargo == CT_PASSENGERS || fr->cargo == CT_MAIL) { - const Town *from = (const Town*)fr->from; - const Town *to = (const Town*)fr->to; - - if (from->pct_pass_transported > 0x99 || - to->pct_pass_transported > 0x99) { - return false; - } - - // Make sure it has a reasonably good rating - if (from->ratings[_current_company] < -100 || - to->ratings[_current_company] < -100) { - return false; - } - } else { - const Industry *i = (const Industry*)fr->from; - - if (i->last_month_pct_transported[fr->cargo != i->produced_cargo[0]] > 0x99 || - i->last_month_production[fr->cargo != i->produced_cargo[0]] == 0) { - return false; - } - } - - _companies_ai[c->index].route_type_mask |= bitmask; - return true; -} - -static byte AiGetDirectionBetweenTiles(TileIndex a, TileIndex b) -{ - byte i = (TileX(a) < TileX(b)) ? 1 : 0; - if (TileY(a) >= TileY(b)) i ^= 3; - return i; -} - -static TileIndex AiGetPctTileBetween(TileIndex a, TileIndex b, byte pct) -{ - return TileXY( - TileX(a) + ((TileX(b) - TileX(a)) * pct >> 8), - TileY(a) + ((TileY(b) - TileY(a)) * pct >> 8) - ); -} - -static void AiWantLongIndustryRoute(Company *c) -{ - int i; - FoundRoute fr; - - i = 60; - for (;;) { - // look for one from the subsidy list - AiFindSubsidyIndustryRoute(&fr); - if (IsInsideMM(fr.distance, 60, 90 + 1)) break; - - // try a random one - AiFindRandomIndustryRoute(&fr); - if (IsInsideMM(fr.distance, 60, 90 + 1)) break; - - // only test 60 times - if (--i == 0) return; - } - - if (!AiCheckIfRouteIsGood(c, &fr, 1)) return; - - // Fill the source field - _companies_ai[c->index].dst.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.to); - _companies_ai[c->index].src.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.from); - - _companies_ai[c->index].src.use_tile = 0; - _companies_ai[c->index].src.rand_rng = 9; - _companies_ai[c->index].src.cur_building_rule = 0xFF; - _companies_ai[c->index].src.unk6 = 1; - _companies_ai[c->index].src.unk7 = 0; - _companies_ai[c->index].src.buildcmd_a = 0x24; - _companies_ai[c->index].src.buildcmd_b = 0xFF; - _companies_ai[c->index].src.direction = AiGetDirectionBetweenTiles( - _companies_ai[c->index].src.spec_tile, - _companies_ai[c->index].dst.spec_tile - ); - _companies_ai[c->index].src.cargo = fr.cargo | 0x80; - - // Fill the dest field - - _companies_ai[c->index].dst.use_tile = 0; - _companies_ai[c->index].dst.rand_rng = 9; - _companies_ai[c->index].dst.cur_building_rule = 0xFF; - _companies_ai[c->index].dst.unk6 = 1; - _companies_ai[c->index].dst.unk7 = 0; - _companies_ai[c->index].dst.buildcmd_a = 0x34; - _companies_ai[c->index].dst.buildcmd_b = 0xFF; - _companies_ai[c->index].dst.direction = AiGetDirectionBetweenTiles( - _companies_ai[c->index].dst.spec_tile, - _companies_ai[c->index].src.spec_tile - ); - _companies_ai[c->index].dst.cargo = fr.cargo; - - // Fill middle field 1 - _companies_ai[c->index].mid1.spec_tile = AiGetPctTileBetween( - _companies_ai[c->index].src.spec_tile, - _companies_ai[c->index].dst.spec_tile, - 0x55 - ); - _companies_ai[c->index].mid1.use_tile = 0; - _companies_ai[c->index].mid1.rand_rng = 6; - _companies_ai[c->index].mid1.cur_building_rule = 0xFF; - _companies_ai[c->index].mid1.unk6 = 2; - _companies_ai[c->index].mid1.unk7 = 1; - _companies_ai[c->index].mid1.buildcmd_a = 0x30; - _companies_ai[c->index].mid1.buildcmd_b = 0xFF; - _companies_ai[c->index].mid1.direction = _companies_ai[c->index].src.direction; - _companies_ai[c->index].mid1.cargo = fr.cargo; - - // Fill middle field 2 - _companies_ai[c->index].mid2.spec_tile = AiGetPctTileBetween( - _companies_ai[c->index].src.spec_tile, - _companies_ai[c->index].dst.spec_tile, - 0xAA - ); - _companies_ai[c->index].mid2.use_tile = 0; - _companies_ai[c->index].mid2.rand_rng = 6; - _companies_ai[c->index].mid2.cur_building_rule = 0xFF; - _companies_ai[c->index].mid2.unk6 = 2; - _companies_ai[c->index].mid2.unk7 = 1; - _companies_ai[c->index].mid2.buildcmd_a = 0xFF; - _companies_ai[c->index].mid2.buildcmd_b = 0xFF; - _companies_ai[c->index].mid2.direction = _companies_ai[c->index].dst.direction; - _companies_ai[c->index].mid2.cargo = fr.cargo; - - // Fill common fields - _companies_ai[c->index].cargo_type = fr.cargo; - _companies_ai[c->index].num_wagons = 3; - _companies_ai[c->index].build_kind = 2; - _companies_ai[c->index].num_build_rec = 4; - _companies_ai[c->index].num_loco_to_build = 2; - _companies_ai[c->index].num_want_fullload = 2; - _companies_ai[c->index].wagon_list[0] = INVALID_VEHICLE; - _companies_ai[c->index].order_list_blocks[0] = 0; - _companies_ai[c->index].order_list_blocks[1] = 1; - _companies_ai[c->index].order_list_blocks[2] = 255; - - _companies_ai[c->index].state = AIS_BUILD_DEFAULT_RAIL_BLOCKS; - _companies_ai[c->index].state_mode = UCHAR_MAX; - _companies_ai[c->index].state_counter = 0; - _companies_ai[c->index].timeout_counter = 0; -} - -static void AiWantMediumIndustryRoute(Company *c) -{ - int i; - FoundRoute fr; - - i = 60; - for (;;) { - // look for one from the subsidy list - AiFindSubsidyIndustryRoute(&fr); - if (IsInsideMM(fr.distance, 40, 60 + 1)) break; - - // try a random one - AiFindRandomIndustryRoute(&fr); - if (IsInsideMM(fr.distance, 40, 60 + 1)) break; - - // only test 60 times - if (--i == 0) return; - } - - if (!AiCheckIfRouteIsGood(c, &fr, 1)) return; - - // Fill the source field - _companies_ai[c->index].src.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.from); - _companies_ai[c->index].src.use_tile = 0; - _companies_ai[c->index].src.rand_rng = 9; - _companies_ai[c->index].src.cur_building_rule = 0xFF; - _companies_ai[c->index].src.unk6 = 1; - _companies_ai[c->index].src.unk7 = 0; - _companies_ai[c->index].src.buildcmd_a = 0x10; - _companies_ai[c->index].src.buildcmd_b = 0xFF; - _companies_ai[c->index].src.direction = AiGetDirectionBetweenTiles( - GET_TOWN_OR_INDUSTRY_TILE(fr.from), - GET_TOWN_OR_INDUSTRY_TILE(fr.to) - ); - _companies_ai[c->index].src.cargo = fr.cargo | 0x80; - - // Fill the dest field - _companies_ai[c->index].dst.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.to); - _companies_ai[c->index].dst.use_tile = 0; - _companies_ai[c->index].dst.rand_rng = 9; - _companies_ai[c->index].dst.cur_building_rule = 0xFF; - _companies_ai[c->index].dst.unk6 = 1; - _companies_ai[c->index].dst.unk7 = 0; - _companies_ai[c->index].dst.buildcmd_a = 0xFF; - _companies_ai[c->index].dst.buildcmd_b = 0xFF; - _companies_ai[c->index].dst.direction = AiGetDirectionBetweenTiles( - GET_TOWN_OR_INDUSTRY_TILE(fr.to), - GET_TOWN_OR_INDUSTRY_TILE(fr.from) - ); - _companies_ai[c->index].dst.cargo = fr.cargo; - - // Fill common fields - _companies_ai[c->index].cargo_type = fr.cargo; - _companies_ai[c->index].num_wagons = 3; - _companies_ai[c->index].build_kind = 1; - _companies_ai[c->index].num_build_rec = 2; - _companies_ai[c->index].num_loco_to_build = 1; - _companies_ai[c->index].num_want_fullload = 1; - _companies_ai[c->index].wagon_list[0] = INVALID_VEHICLE; - _companies_ai[c->index].order_list_blocks[0] = 0; - _companies_ai[c->index].order_list_blocks[1] = 1; - _companies_ai[c->index].order_list_blocks[2] = 255; - _companies_ai[c->index].state = AIS_BUILD_DEFAULT_RAIL_BLOCKS; - _companies_ai[c->index].state_mode = UCHAR_MAX; - _companies_ai[c->index].state_counter = 0; - _companies_ai[c->index].timeout_counter = 0; -} - -static void AiWantShortIndustryRoute(Company *c) -{ - int i; - FoundRoute fr; - - i = 60; - for (;;) { - // look for one from the subsidy list - AiFindSubsidyIndustryRoute(&fr); - if (IsInsideMM(fr.distance, 15, 40 + 1)) break; - - // try a random one - AiFindRandomIndustryRoute(&fr); - if (IsInsideMM(fr.distance, 15, 40 + 1)) break; - - // only test 60 times - if (--i == 0) return; - } - - if (!AiCheckIfRouteIsGood(c, &fr, 1)) return; - - // Fill the source field - _companies_ai[c->index].src.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.from); - _companies_ai[c->index].src.use_tile = 0; - _companies_ai[c->index].src.rand_rng = 9; - _companies_ai[c->index].src.cur_building_rule = 0xFF; - _companies_ai[c->index].src.unk6 = 1; - _companies_ai[c->index].src.unk7 = 0; - _companies_ai[c->index].src.buildcmd_a = 0x10; - _companies_ai[c->index].src.buildcmd_b = 0xFF; - _companies_ai[c->index].src.direction = AiGetDirectionBetweenTiles( - GET_TOWN_OR_INDUSTRY_TILE(fr.from), - GET_TOWN_OR_INDUSTRY_TILE(fr.to) - ); - _companies_ai[c->index].src.cargo = fr.cargo | 0x80; - - // Fill the dest field - _companies_ai[c->index].dst.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.to); - _companies_ai[c->index].dst.use_tile = 0; - _companies_ai[c->index].dst.rand_rng = 9; - _companies_ai[c->index].dst.cur_building_rule = 0xFF; - _companies_ai[c->index].dst.unk6 = 1; - _companies_ai[c->index].dst.unk7 = 0; - _companies_ai[c->index].dst.buildcmd_a = 0xFF; - _companies_ai[c->index].dst.buildcmd_b = 0xFF; - _companies_ai[c->index].dst.direction = AiGetDirectionBetweenTiles( - GET_TOWN_OR_INDUSTRY_TILE(fr.to), - GET_TOWN_OR_INDUSTRY_TILE(fr.from) - ); - _companies_ai[c->index].dst.cargo = fr.cargo; - - // Fill common fields - _companies_ai[c->index].cargo_type = fr.cargo; - _companies_ai[c->index].num_wagons = 2; - _companies_ai[c->index].build_kind = 1; - _companies_ai[c->index].num_build_rec = 2; - _companies_ai[c->index].num_loco_to_build = 1; - _companies_ai[c->index].num_want_fullload = 1; - _companies_ai[c->index].wagon_list[0] = INVALID_VEHICLE; - _companies_ai[c->index].order_list_blocks[0] = 0; - _companies_ai[c->index].order_list_blocks[1] = 1; - _companies_ai[c->index].order_list_blocks[2] = 255; - _companies_ai[c->index].state = AIS_BUILD_DEFAULT_RAIL_BLOCKS; - _companies_ai[c->index].state_mode = UCHAR_MAX; - _companies_ai[c->index].state_counter = 0; - _companies_ai[c->index].timeout_counter = 0; -} - -static void AiWantMailRoute(Company *c) -{ - int i; - FoundRoute fr; - - i = 60; - for (;;) { - // look for one from the subsidy list - AiFindSubsidyPassengerRoute(&fr); - if (IsInsideMM(fr.distance, 60, 110 + 1)) break; - - // try a random one - AiFindRandomPassengerRoute(&fr); - if (IsInsideMM(fr.distance, 60, 110 + 1)) break; - - // only test 60 times - if (--i == 0) return; - } - - fr.cargo = CT_MAIL; - if (!AiCheckIfRouteIsGood(c, &fr, 1)) return; - - // Fill the source field - _companies_ai[c->index].src.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.from); - _companies_ai[c->index].src.use_tile = 0; - _companies_ai[c->index].src.rand_rng = 7; - _companies_ai[c->index].src.cur_building_rule = 0xFF; - _companies_ai[c->index].src.unk6 = 1; - _companies_ai[c->index].src.unk7 = 0; - _companies_ai[c->index].src.buildcmd_a = 0x24; - _companies_ai[c->index].src.buildcmd_b = 0xFF; - _companies_ai[c->index].src.direction = AiGetDirectionBetweenTiles( - GET_TOWN_OR_INDUSTRY_TILE(fr.from), - GET_TOWN_OR_INDUSTRY_TILE(fr.to) - ); - _companies_ai[c->index].src.cargo = fr.cargo; - - // Fill the dest field - _companies_ai[c->index].dst.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.to); - _companies_ai[c->index].dst.use_tile = 0; - _companies_ai[c->index].dst.rand_rng = 7; - _companies_ai[c->index].dst.cur_building_rule = 0xFF; - _companies_ai[c->index].dst.unk6 = 1; - _companies_ai[c->index].dst.unk7 = 0; - _companies_ai[c->index].dst.buildcmd_a = 0x34; - _companies_ai[c->index].dst.buildcmd_b = 0xFF; - _companies_ai[c->index].dst.direction = AiGetDirectionBetweenTiles( - GET_TOWN_OR_INDUSTRY_TILE(fr.to), - GET_TOWN_OR_INDUSTRY_TILE(fr.from) - ); - _companies_ai[c->index].dst.cargo = fr.cargo; - - // Fill middle field 1 - _companies_ai[c->index].mid1.spec_tile = AiGetPctTileBetween( - GET_TOWN_OR_INDUSTRY_TILE(fr.from), - GET_TOWN_OR_INDUSTRY_TILE(fr.to), - 0x55 - ); - _companies_ai[c->index].mid1.use_tile = 0; - _companies_ai[c->index].mid1.rand_rng = 6; - _companies_ai[c->index].mid1.cur_building_rule = 0xFF; - _companies_ai[c->index].mid1.unk6 = 2; - _companies_ai[c->index].mid1.unk7 = 1; - _companies_ai[c->index].mid1.buildcmd_a = 0x30; - _companies_ai[c->index].mid1.buildcmd_b = 0xFF; - _companies_ai[c->index].mid1.direction = _companies_ai[c->index].src.direction; - _companies_ai[c->index].mid1.cargo = fr.cargo; - - // Fill middle field 2 - _companies_ai[c->index].mid2.spec_tile = AiGetPctTileBetween( - GET_TOWN_OR_INDUSTRY_TILE(fr.from), - GET_TOWN_OR_INDUSTRY_TILE(fr.to), - 0xAA - ); - _companies_ai[c->index].mid2.use_tile = 0; - _companies_ai[c->index].mid2.rand_rng = 6; - _companies_ai[c->index].mid2.cur_building_rule = 0xFF; - _companies_ai[c->index].mid2.unk6 = 2; - _companies_ai[c->index].mid2.unk7 = 1; - _companies_ai[c->index].mid2.buildcmd_a = 0xFF; - _companies_ai[c->index].mid2.buildcmd_b = 0xFF; - _companies_ai[c->index].mid2.direction = _companies_ai[c->index].dst.direction; - _companies_ai[c->index].mid2.cargo = fr.cargo; - - // Fill common fields - _companies_ai[c->index].cargo_type = fr.cargo; - _companies_ai[c->index].num_wagons = 3; - _companies_ai[c->index].build_kind = 2; - _companies_ai[c->index].num_build_rec = 4; - _companies_ai[c->index].num_loco_to_build = 2; - _companies_ai[c->index].num_want_fullload = 0; - _companies_ai[c->index].wagon_list[0] = INVALID_VEHICLE; - _companies_ai[c->index].order_list_blocks[0] = 0; - _companies_ai[c->index].order_list_blocks[1] = 1; - _companies_ai[c->index].order_list_blocks[2] = 255; - _companies_ai[c->index].state = AIS_BUILD_DEFAULT_RAIL_BLOCKS; - _companies_ai[c->index].state_mode = UCHAR_MAX; - _companies_ai[c->index].state_counter = 0; - _companies_ai[c->index].timeout_counter = 0; -} - -static void AiWantPassengerRoute(Company *c) -{ - int i; - FoundRoute fr; - - i = 60; - for (;;) { - // look for one from the subsidy list - AiFindSubsidyPassengerRoute(&fr); - if (IsInsideMM(fr.distance, 0, 55 + 1)) break; - - // try a random one - AiFindRandomPassengerRoute(&fr); - if (IsInsideMM(fr.distance, 0, 55 + 1)) break; - - // only test 60 times - if (--i == 0) return; - } - - fr.cargo = CT_PASSENGERS; - if (!AiCheckIfRouteIsGood(c, &fr, 1)) return; - - // Fill the source field - _companies_ai[c->index].src.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.from); - _companies_ai[c->index].src.use_tile = 0; - _companies_ai[c->index].src.rand_rng = 7; - _companies_ai[c->index].src.cur_building_rule = 0xFF; - _companies_ai[c->index].src.unk6 = 1; - _companies_ai[c->index].src.unk7 = 0; - _companies_ai[c->index].src.buildcmd_a = 0x10; - _companies_ai[c->index].src.buildcmd_b = 0xFF; - _companies_ai[c->index].src.direction = AiGetDirectionBetweenTiles( - GET_TOWN_OR_INDUSTRY_TILE(fr.from), - GET_TOWN_OR_INDUSTRY_TILE(fr.to) - ); - _companies_ai[c->index].src.cargo = fr.cargo; - - // Fill the dest field - _companies_ai[c->index].dst.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.to); - _companies_ai[c->index].dst.use_tile = 0; - _companies_ai[c->index].dst.rand_rng = 7; - _companies_ai[c->index].dst.cur_building_rule = 0xFF; - _companies_ai[c->index].dst.unk6 = 1; - _companies_ai[c->index].dst.unk7 = 0; - _companies_ai[c->index].dst.buildcmd_a = 0xFF; - _companies_ai[c->index].dst.buildcmd_b = 0xFF; - _companies_ai[c->index].dst.direction = AiGetDirectionBetweenTiles( - GET_TOWN_OR_INDUSTRY_TILE(fr.to), - GET_TOWN_OR_INDUSTRY_TILE(fr.from) - ); - _companies_ai[c->index].dst.cargo = fr.cargo; - - // Fill common fields - _companies_ai[c->index].cargo_type = fr.cargo; - _companies_ai[c->index].num_wagons = 2; - _companies_ai[c->index].build_kind = 1; - _companies_ai[c->index].num_build_rec = 2; - _companies_ai[c->index].num_loco_to_build = 1; - _companies_ai[c->index].num_want_fullload = 0; - _companies_ai[c->index].wagon_list[0] = INVALID_VEHICLE; - _companies_ai[c->index].order_list_blocks[0] = 0; - _companies_ai[c->index].order_list_blocks[1] = 1; - _companies_ai[c->index].order_list_blocks[2] = 255; - _companies_ai[c->index].state = AIS_BUILD_DEFAULT_RAIL_BLOCKS; - _companies_ai[c->index].state_mode = UCHAR_MAX; - _companies_ai[c->index].state_counter = 0; - _companies_ai[c->index].timeout_counter = 0; -} - -static void AiWantTrainRoute(Company *c) -{ - uint16 r = GB(Random(), 0, 16); - - _companies_ai[c->index].railtype_to_use = GetBestRailtype(c->index); - - if (r > 0xD000) { - AiWantLongIndustryRoute(c); - } else if (r > 0x6000) { - AiWantMediumIndustryRoute(c); - } else if (r > 0x1000) { - AiWantShortIndustryRoute(c); - } else if (r > 0x800) { - AiWantPassengerRoute(c); - } else { - AiWantMailRoute(c); - } -} - -static void AiWantLongRoadIndustryRoute(Company *c) -{ - int i; - FoundRoute fr; - - i = 60; - for (;;) { - // look for one from the subsidy list - AiFindSubsidyIndustryRoute(&fr); - if (IsInsideMM(fr.distance, 35, 55 + 1)) break; - - // try a random one - AiFindRandomIndustryRoute(&fr); - if (IsInsideMM(fr.distance, 35, 55 + 1)) break; - - // only test 60 times - if (--i == 0) return; - } - - if (!AiCheckIfRouteIsGood(c, &fr, 2)) return; - - // Fill the source field - _companies_ai[c->index].src.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.from); - _companies_ai[c->index].src.use_tile = 0; - _companies_ai[c->index].src.rand_rng = 9; - _companies_ai[c->index].src.cur_building_rule = 0xFF; - _companies_ai[c->index].src.buildcmd_a = 1; - _companies_ai[c->index].src.direction = 0; - _companies_ai[c->index].src.cargo = fr.cargo | 0x80; - - // Fill the dest field - _companies_ai[c->index].dst.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.to); - _companies_ai[c->index].dst.use_tile = 0; - _companies_ai[c->index].dst.rand_rng = 9; - _companies_ai[c->index].dst.cur_building_rule = 0xFF; - _companies_ai[c->index].dst.buildcmd_a = 0xFF; - _companies_ai[c->index].dst.direction = 0; - _companies_ai[c->index].dst.cargo = fr.cargo; - - // Fill common fields - _companies_ai[c->index].cargo_type = fr.cargo; - _companies_ai[c->index].num_build_rec = 2; - _companies_ai[c->index].num_loco_to_build = 5; - _companies_ai[c->index].num_want_fullload = 5; - -// _companies_ai[c->index].loco_id = INVALID_VEHICLE; - _companies_ai[c->index].order_list_blocks[0] = 0; - _companies_ai[c->index].order_list_blocks[1] = 1; - _companies_ai[c->index].order_list_blocks[2] = 255; - - _companies_ai[c->index].state = AIS_BUILD_DEFAULT_ROAD_BLOCKS; - _companies_ai[c->index].state_mode = UCHAR_MAX; - _companies_ai[c->index].state_counter = 0; - _companies_ai[c->index].timeout_counter = 0; -} - -static void AiWantMediumRoadIndustryRoute(Company *c) -{ - int i; - FoundRoute fr; - - i = 60; - for (;;) { - // look for one from the subsidy list - AiFindSubsidyIndustryRoute(&fr); - if (IsInsideMM(fr.distance, 15, 40 + 1)) break; - - // try a random one - AiFindRandomIndustryRoute(&fr); - if (IsInsideMM(fr.distance, 15, 40 + 1)) break; - - // only test 60 times - if (--i == 0) return; - } - - if (!AiCheckIfRouteIsGood(c, &fr, 2)) return; - - // Fill the source field - _companies_ai[c->index].src.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.from); - _companies_ai[c->index].src.use_tile = 0; - _companies_ai[c->index].src.rand_rng = 9; - _companies_ai[c->index].src.cur_building_rule = 0xFF; - _companies_ai[c->index].src.buildcmd_a = 1; - _companies_ai[c->index].src.direction = 0; - _companies_ai[c->index].src.cargo = fr.cargo | 0x80; - - // Fill the dest field - _companies_ai[c->index].dst.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.to); - _companies_ai[c->index].dst.use_tile = 0; - _companies_ai[c->index].dst.rand_rng = 9; - _companies_ai[c->index].dst.cur_building_rule = 0xFF; - _companies_ai[c->index].dst.buildcmd_a = 0xFF; - _companies_ai[c->index].dst.direction = 0; - _companies_ai[c->index].dst.cargo = fr.cargo; - - // Fill common fields - _companies_ai[c->index].cargo_type = fr.cargo; - _companies_ai[c->index].num_build_rec = 2; - _companies_ai[c->index].num_loco_to_build = 3; - _companies_ai[c->index].num_want_fullload = 3; - -// _companies_ai[c->index].loco_id = INVALID_VEHICLE; - _companies_ai[c->index].order_list_blocks[0] = 0; - _companies_ai[c->index].order_list_blocks[1] = 1; - _companies_ai[c->index].order_list_blocks[2] = 255; - - _companies_ai[c->index].state = AIS_BUILD_DEFAULT_ROAD_BLOCKS; - _companies_ai[c->index].state_mode = UCHAR_MAX; - _companies_ai[c->index].state_counter = 0; - _companies_ai[c->index].timeout_counter = 0; -} - -static void AiWantLongRoadPassengerRoute(Company *c) -{ - int i; - FoundRoute fr; - - i = 60; - for (;;) { - // look for one from the subsidy list - AiFindSubsidyPassengerRoute(&fr); - if (IsInsideMM(fr.distance, 55, 180 + 1)) break; - - // try a random one - AiFindRandomPassengerRoute(&fr); - if (IsInsideMM(fr.distance, 55, 180 + 1)) break; - - // only test 60 times - if (--i == 0) return; - } - - fr.cargo = CT_PASSENGERS; - - if (!AiCheckIfRouteIsGood(c, &fr, 2)) return; - - // Fill the source field - _companies_ai[c->index].src.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.to); - _companies_ai[c->index].src.use_tile = 0; - _companies_ai[c->index].src.rand_rng = 10; - _companies_ai[c->index].src.cur_building_rule = 0xFF; - _companies_ai[c->index].src.buildcmd_a = 1; - _companies_ai[c->index].src.direction = 0; - _companies_ai[c->index].src.cargo = CT_PASSENGERS; - - // Fill the dest field - _companies_ai[c->index].dst.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.from); - _companies_ai[c->index].dst.use_tile = 0; - _companies_ai[c->index].dst.rand_rng = 10; - _companies_ai[c->index].dst.cur_building_rule = 0xFF; - _companies_ai[c->index].dst.buildcmd_a = 0xFF; - _companies_ai[c->index].dst.direction = 0; - _companies_ai[c->index].dst.cargo = CT_PASSENGERS; - - // Fill common fields - _companies_ai[c->index].cargo_type = CT_PASSENGERS; - _companies_ai[c->index].num_build_rec = 2; - _companies_ai[c->index].num_loco_to_build = 4; - _companies_ai[c->index].num_want_fullload = 0; - -// _companies_ai[c->index].loco_id = INVALID_VEHICLE; - _companies_ai[c->index].order_list_blocks[0] = 0; - _companies_ai[c->index].order_list_blocks[1] = 1; - _companies_ai[c->index].order_list_blocks[2] = 255; - - _companies_ai[c->index].state = AIS_BUILD_DEFAULT_ROAD_BLOCKS; - _companies_ai[c->index].state_mode = UCHAR_MAX; - _companies_ai[c->index].state_counter = 0; - _companies_ai[c->index].timeout_counter = 0; -} - -static void AiWantPassengerRouteInsideTown(Company *c) -{ - int i; - FoundRoute fr; - Town *t; - - i = 60; - for (;;) { - // Find a town big enough - t = AiFindRandomTown(); - if (t != NULL && t->population >= 700) break; - - // only test 60 times - if (--i == 0) return; - } - - fr.cargo = CT_PASSENGERS; - fr.from = fr.to = t; - - if (!AiCheckIfRouteIsGood(c, &fr, 2)) return; - - // Fill the source field - _companies_ai[c->index].src.spec_tile = t->xy; - _companies_ai[c->index].src.use_tile = 0; - _companies_ai[c->index].src.rand_rng = 10; - _companies_ai[c->index].src.cur_building_rule = 0xFF; - _companies_ai[c->index].src.buildcmd_a = 1; - _companies_ai[c->index].src.direction = 0; - _companies_ai[c->index].src.cargo = CT_PASSENGERS; - - // Fill the dest field - _companies_ai[c->index].dst.spec_tile = t->xy; - _companies_ai[c->index].dst.use_tile = 0; - _companies_ai[c->index].dst.rand_rng = 10; - _companies_ai[c->index].dst.cur_building_rule = 0xFF; - _companies_ai[c->index].dst.buildcmd_a = 0xFF; - _companies_ai[c->index].dst.direction = 0; - _companies_ai[c->index].dst.cargo = CT_PASSENGERS; - - // Fill common fields - _companies_ai[c->index].cargo_type = CT_PASSENGERS; - _companies_ai[c->index].num_build_rec = 2; - _companies_ai[c->index].num_loco_to_build = 2; - _companies_ai[c->index].num_want_fullload = 0; - -// _companies_ai[c->index].loco_id = INVALID_VEHICLE; - _companies_ai[c->index].order_list_blocks[0] = 0; - _companies_ai[c->index].order_list_blocks[1] = 1; - _companies_ai[c->index].order_list_blocks[2] = 255; - - _companies_ai[c->index].state = AIS_BUILD_DEFAULT_ROAD_BLOCKS; - _companies_ai[c->index].state_mode = UCHAR_MAX; - _companies_ai[c->index].state_counter = 0; - _companies_ai[c->index].timeout_counter = 0; -} - -static void AiWantRoadRoute(Company *c) -{ - uint16 r = GB(Random(), 0, 16); - - if (r > 0x4000) { - AiWantLongRoadIndustryRoute(c); - } else if (r > 0x2000) { - AiWantMediumRoadIndustryRoute(c); - } else if (r > 0x1000) { - AiWantLongRoadPassengerRoute(c); - } else { - AiWantPassengerRouteInsideTown(c); - } -} - -static void AiWantPassengerAircraftRoute(Company *c) -{ - FoundRoute fr; - int i; - - /* Get aircraft that would be bought for this route - * (probably, as conditions may change before the route is fully built, - * like running out of money and having to select different aircraft, etc ...) */ - EngineID veh = AiChooseAircraftToBuild(c->money, _companies_ai[c->index].build_kind != 0 ? AIR_CTOL : 0); - - /* No aircraft buildable mean no aircraft route */ - if (veh == INVALID_ENGINE) return; - - const AircraftVehicleInfo *avi = AircraftVehInfo(veh); - - /* For passengers, "optimal" number of days in transit is about 80 to 100 - * Calculate "maximum optimal number of squares" from speed for 80 days - * 20 days should be enough for takeoff, land, taxi, etc ... - * - * "A vehicle traveling at 100kph will cross 5.6 tiles per day" -> - * Since in table aircraft speeds are in "real km/h", this should be accurate - * We get max_squares = avi->max_speed * 5.6 / 100.0 * 80 */ - int max_squares = avi->max_speed * 448 / 100; - - /* For example this will be 10456 tiles for 2334 km/h aircrafts with realistic aircraft speeds - * and 836 with "unrealistic" speeds, much more than the original 95 squares limit - * - * Size of the map, if not rectangular, it is the larger dimension of it - */ - int map_size = max(MapSizeX(), MapSizeY()); - - /* Minimum distance between airports is half of map size, clamped between 1% and 20% of optimum. - * May prevent building plane routes at all on small maps, but they will be ineffective there, so - * it is feature, not a bug. - * On smaller distances, buses or trains are usually more effective approach anyway. - * Additional safeguard is needing at least 20 squares, - * which may trigger in highly unusual configurations */ - int min_squares = max(20, max(max_squares / 100, min(max_squares / 5, map_size / 2))); - - /* Should not happen, unless aircraft with real speed under approx. 5 km/h is selected. - * No such exist, unless using some NewGRF with ballons, zeppelins or similar - * slow-moving stuff. In that case, bail out, it is faster to walk by foot anyway :). */ - if (max_squares < min_squares) return; - - i = 60; - for (;;) { - - // look for one from the subsidy list - AiFindSubsidyPassengerRoute(&fr); - if (IsInsideMM(fr.distance, min_squares, max_squares + 1)) break; - - // try a random one - AiFindRandomPassengerRoute(&fr); - if (IsInsideMM(fr.distance, min_squares, max_squares + 1)) break; - - // only test 60 times - if (--i == 0) return; - } - - fr.cargo = CT_PASSENGERS; - if (!AiCheckIfRouteIsGood(c, &fr, 4)) return; - - - // Fill the source field - _companies_ai[c->index].src.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.to); - _companies_ai[c->index].src.use_tile = 0; - _companies_ai[c->index].src.rand_rng = 12; - _companies_ai[c->index].src.cur_building_rule = 0xFF; - _companies_ai[c->index].src.cargo = fr.cargo; - - // Fill the dest field - _companies_ai[c->index].dst.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.from); - _companies_ai[c->index].dst.use_tile = 0; - _companies_ai[c->index].dst.rand_rng = 12; - _companies_ai[c->index].dst.cur_building_rule = 0xFF; - _companies_ai[c->index].dst.cargo = fr.cargo; - - // Fill common fields - _companies_ai[c->index].cargo_type = fr.cargo; - _companies_ai[c->index].build_kind = 0; - _companies_ai[c->index].num_build_rec = 2; - _companies_ai[c->index].num_loco_to_build = 1; - /* Using full load always may not be the best. - * Pick random value and rely on selling the vehicle & route - * afterwards if the choice was utterly wrong (or maybe altering the value if AI is improved) - * When traffic is very low or very assymetric, is is better not to full load - * When traffic is high, full/non-full make no difference - * It should be better to run with aircraft only one way full 6 times per year, - * rather than two way full 1 times. - * Practical experiments with AI show that the non-full-load aircrafts are usually - * those that survive - * Also, non-full load is more resistant against starving (by building better stations - * or using exclusive rights) - */ - _companies_ai[c->index].num_want_fullload = Chance16(1, 5); // 20% chance -// _companies_ai[c->index].loco_id = INVALID_VEHICLE; - _companies_ai[c->index].order_list_blocks[0] = 0; - _companies_ai[c->index].order_list_blocks[1] = 1; - _companies_ai[c->index].order_list_blocks[2] = 255; - - _companies_ai[c->index].state = AIS_AIRPORT_STUFF; - _companies_ai[c->index].timeout_counter = 0; -} - -static void AiWantOilRigAircraftRoute(Company *c) -{ - int i; - FoundRoute fr; - Town *t; - Industry *in; - - i = 60; - for (;;) { - // Find a town - t = AiFindRandomTown(); - if (t != NULL) { - // Find a random oil rig industry - in = AiFindRandomIndustry(); - if (in != NULL && GetIndustrySpec(in->type)->behaviour & INDUSTRYBEH_AI_AIRSHIP_ROUTES) { - if (DistanceManhattan(t->xy, in->xy) < 60) - break; - } - } - - // only test 60 times - if (--i == 0) return; - } - - fr.cargo = CT_PASSENGERS; - fr.from = fr.to = t; - - if (!AiCheckIfRouteIsGood(c, &fr, 4)) return; - - // Fill the source field - _companies_ai[c->index].src.spec_tile = t->xy; - _companies_ai[c->index].src.use_tile = 0; - _companies_ai[c->index].src.rand_rng = 12; - _companies_ai[c->index].src.cur_building_rule = 0xFF; - _companies_ai[c->index].src.cargo = CT_PASSENGERS; - - // Fill the dest field - _companies_ai[c->index].dst.spec_tile = in->xy; - _companies_ai[c->index].dst.use_tile = 0; - _companies_ai[c->index].dst.rand_rng = 5; - _companies_ai[c->index].dst.cur_building_rule = 0xFF; - _companies_ai[c->index].dst.cargo = CT_PASSENGERS; - - // Fill common fields - _companies_ai[c->index].cargo_type = CT_PASSENGERS; - _companies_ai[c->index].build_kind = 1; - _companies_ai[c->index].num_build_rec = 2; - _companies_ai[c->index].num_loco_to_build = 1; - _companies_ai[c->index].num_want_fullload = 0; -// _companies_ai[c->index].loco_id = INVALID_VEHICLE; - _companies_ai[c->index].order_list_blocks[0] = 0; - _companies_ai[c->index].order_list_blocks[1] = 1; - _companies_ai[c->index].order_list_blocks[2] = 255; - - _companies_ai[c->index].state = AIS_AIRPORT_STUFF; - _companies_ai[c->index].timeout_counter = 0; -} - -static void AiWantAircraftRoute(Company *c) -{ - uint16 r = (uint16)Random(); - - if (r >= 0x2AAA || _date < 0x3912 + DAYS_TILL_ORIGINAL_BASE_YEAR) { - AiWantPassengerAircraftRoute(c); - } else { - AiWantOilRigAircraftRoute(c); - } -} - - - -static void AiStateWantNewRoute(Company *c) -{ - uint16 r; - int i; - - if (c->money < AiGetBasePrice(c) * 500) { - _companies_ai[c->index].state = AIS_0; - return; - } - - i = 200; - for (;;) { - r = (uint16)Random(); - - if (_settings_game.ai.ai_disable_veh_train && - _settings_game.ai.ai_disable_veh_roadveh && - _settings_game.ai.ai_disable_veh_aircraft && - _settings_game.ai.ai_disable_veh_ship) { - return; - } - - if (r < 0x7626) { - if (_settings_game.ai.ai_disable_veh_train) continue; - AiWantTrainRoute(c); - } else if (r < 0xC4EA) { - if (_settings_game.ai.ai_disable_veh_roadveh) continue; - AiWantRoadRoute(c); - } else if (r < 0xD89B) { - if (_settings_game.ai.ai_disable_veh_aircraft) continue; - AiWantAircraftRoute(c); - } else { - /* Ships are not implemented in this (broken) AI */ - } - - // got a route? - if (_companies_ai[c->index].state != AIS_WANT_NEW_ROUTE) break; - - // time out? - if (--i == 0) { - if (++_companies_ai[c->index].state_counter == 556) _companies_ai[c->index].state = AIS_0; - break; - } - } -} - -static bool AiCheckTrackResources(TileIndex tile, const AiDefaultBlockData *p, byte cargo) -{ - uint rad = (_settings_game.station.modified_catchment) ? CA_TRAIN : CA_UNMODIFIED; - - for (; p->mode != 4; p++) { - AcceptedCargo values; - TileIndex tile2; - uint w; - uint h; - - if (p->mode != 1) continue; - - tile2 = TILE_ADD(tile, ToTileIndexDiff(p->tileoffs)); - w = GB(p->attr, 1, 3); - h = GB(p->attr, 4, 3); - - if (p->attr & 1) Swap(w, h); - - if (cargo & 0x80) { - GetProductionAroundTiles(values, tile2, w, h, rad); - return values[cargo & 0x7F] != 0; - } else { - GetAcceptanceAroundTiles(values, tile2, w, h, rad); - if (!(values[cargo] & ~7)) - return false; - if (cargo != CT_MAIL) - return true; - return !!((values[cargo] >> 1) & ~7); - } - } - - return true; -} - -static CommandCost AiDoBuildDefaultRailTrack(TileIndex tile, const AiDefaultBlockData *p, RailType railtype, uint32 flag) -{ - CommandCost ret; - CommandCost total_cost(EXPENSES_CONSTRUCTION); - Town *t = NULL; - int rating = 0; - int i, j, k; - - for (;;) { - // This will seldomly overflow for valid reasons. Mask it to be on the safe side. - uint c = TILE_MASK(tile + ToTileIndexDiff(p->tileoffs)); - - _cleared_town = NULL; - - if (p->mode < 2) { - if (p->mode == 0) { - // Depot - ret = DoCommand(c, railtype, p->attr, flag | DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, CMD_BUILD_TRAIN_DEPOT); - } else { - // Station - ret = DoCommand(c, (railtype & 0x0f) | (p->attr & 1) << 4 | (p->attr >> 4) << 8 | (p->attr >> 1 & 7) << 16, (INVALID_STATION << 16), flag | DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, CMD_BUILD_RAILROAD_STATION); - } - - if (CmdFailed(ret)) return CMD_ERROR; - total_cost.AddCost(ret); - -clear_town_stuff:; - if (_cleared_town != NULL) { - if (t != NULL && t != _cleared_town) - return CMD_ERROR; - t = _cleared_town; - rating += _cleared_town_rating; - } - } else if (p->mode == 2) { - /* Rail */ - if (IsTileType(c, MP_RAILWAY)) return CMD_ERROR; - - j = p->attr; - k = 0; - - /* Build the rail - * note: FOR_EACH_SET_BIT cannot be used here - */ - for (i = 0; i != 6; i++, j >>= 1) { - if (j & 1) { - k = i; - ret = DoCommand(c, railtype, i, flag | DC_AUTO | DC_NO_WATER, CMD_BUILD_SINGLE_RAIL); - if (CmdFailed(ret)) return CMD_ERROR; - total_cost.AddCost(ret); - } - } - - /* signals too? */ - if (j & 3) { - // Can't build signals on a road. - if (IsTileType(c, MP_ROAD)) return CMD_ERROR; - - if (flag & DC_EXEC) { - j = 4 - j; - do { - ret = DoCommand(c, k, 0, flag, CMD_BUILD_SIGNALS); - } while (--j); - } else { - ret.AddCost(_price.build_signals); - } - if (CmdFailed(ret)) return CMD_ERROR; - total_cost.AddCost(ret); - } - } else if (p->mode == 3) { - //Clear stuff and then build single rail. - if (GetTileSlope(c, NULL) != SLOPE_FLAT) return CMD_ERROR; - ret = DoCommand(c, 0, 0, flag | DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, CMD_LANDSCAPE_CLEAR); - if (CmdFailed(ret)) return CMD_ERROR; - total_cost.AddCost(ret); - total_cost.AddCost(_price.build_rail); - - if (flag & DC_EXEC) { - DoCommand(c, railtype, p->attr & 1, flag | DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, CMD_BUILD_SINGLE_RAIL); - } - - goto clear_town_stuff; - } else { - // Unk - break; - } - - p++; - } - - if (!(flag & DC_EXEC)) { - if (t != NULL && rating > t->ratings[_current_company]) { - return CMD_ERROR; - } - } - - return total_cost; -} - -// Returns rule and cost -static int AiBuildDefaultRailTrack(TileIndex tile, byte p0, byte p1, byte p2, byte p3, byte dir, byte cargo, RailType railtype, CommandCost *cost) -{ - int i; - const AiDefaultRailBlock *p; - - for (i = 0; (p = _default_rail_track_data[i]) != NULL; i++) { - if (p->p0 == p0 && p->p1 == p1 && p->p2 == p2 && p->p3 == p3 && - (p->dir == 0xFF || p->dir == dir || ((p->dir - 1) & 3) == dir)) { - *cost = AiDoBuildDefaultRailTrack(tile, p->data, railtype, DC_NO_TOWN_RATING); - if (CmdSucceeded(*cost) && AiCheckTrackResources(tile, p->data, cargo)) - return i; - } - } - - return -1; -} - -static const byte _terraform_up_flags[] = { - 14, 13, 12, 11, - 10, 9, 8, 7, - 6, 5, 4, 3, - 2, 1, 0, 1, - 2, 1, 4, 1, - 2, 1, 8, 1, - 2, 1, 4, 2, - 2, 1 -}; - -static const byte _terraform_down_flags[] = { - 1, 2, 3, 4, - 5, 6, 1, 8, - 9, 10, 8, 12, - 4, 2, 0, 0, - 1, 2, 3, 4, - 5, 6, 2, 8, - 9, 10, 1, 12, - 8, 4 -}; - -static void AiDoTerraformLand(TileIndex tile, DiagDirection dir, int unk, int mode) -{ - CompanyID old_company; - uint32 r; - Slope slope; - uint h; - - old_company = _current_company; - _current_company = OWNER_NONE; - - r = Random(); - - unk &= (int)r; - - do { - tile = TILE_MASK(tile + TileOffsByDiagDir(dir)); - - r >>= 2; - if (r & 2) { - dir = ChangeDiagDir(dir, (r & 1) ? DIAGDIRDIFF_90LEFT : DIAGDIRDIFF_90RIGHT); - } - } while (--unk >= 0); - - slope = GetTileSlope(tile, &h); - - if (slope != SLOPE_FLAT) { - if (mode > 0 || (mode == 0 && !(r & 0xC))) { - // Terraform up - DoCommand(tile, _terraform_up_flags[slope - 1], 1, - DC_EXEC | DC_AUTO | DC_NO_WATER, CMD_TERRAFORM_LAND); - } else if (h != 0) { - // Terraform down - DoCommand(tile, _terraform_down_flags[slope - 1], 0, - DC_EXEC | DC_AUTO | DC_NO_WATER, CMD_TERRAFORM_LAND); - } - } - - _current_company = old_company; -} - -static void AiStateBuildDefaultRailBlocks(Company *c) -{ - uint i; - int j; - AiBuildRec *aib; - int rule; - CommandCost cost; - - // time out? - if (++_companies_ai[c->index].timeout_counter == 1388) { - _companies_ai[c->index].state = AIS_DELETE_RAIL_BLOCKS; - return; - } - - // do the following 8 times - for (i = 0; i < 8; i++) { - // check if we can build the default track - aib = &_companies_ai[c->index].src; - j = _companies_ai[c->index].num_build_rec; - do { - // this item has already been built? - if (aib->cur_building_rule != 255) continue; - - // adjust the coordinate randomly, - // to make sure that we find a position. - aib->use_tile = AdjustTileCoordRandomly(aib->spec_tile, aib->rand_rng); - - // check if the track can be build there. - rule = AiBuildDefaultRailTrack(aib->use_tile, - _companies_ai[c->index].build_kind, _companies_ai[c->index].num_wagons, - aib->unk6, aib->unk7, - aib->direction, aib->cargo, - _companies_ai[c->index].railtype_to_use, - &cost - ); - - if (rule == -1) { - // cannot build, terraform after a while - if (_companies_ai[c->index].state_counter >= 600) { - AiDoTerraformLand(aib->use_tile, (DiagDirection)(Random() & 3), 3, (int8)_companies_ai[c->index].state_mode); - } - // also try the other terraform direction - if (++_companies_ai[c->index].state_counter >= 1000) { - _companies_ai[c->index].state_counter = 0; - _companies_ai[c->index].state_mode = -_companies_ai[c->index].state_mode; - } - } else if (CheckCompanyHasMoney(cost)) { - // company has money, build it. - aib->cur_building_rule = rule; - - AiDoBuildDefaultRailTrack( - aib->use_tile, - _default_rail_track_data[rule]->data, - _companies_ai[c->index].railtype_to_use, - DC_EXEC | DC_NO_TOWN_RATING - ); - } - } while (++aib, --j); - } - - // check if we're done with all of them - aib = &_companies_ai[c->index].src; - j = _companies_ai[c->index].num_build_rec; - do { - if (aib->cur_building_rule == 255) return; - } while (++aib, --j); - - // yep, all are done. switch state to the rail building state. - _companies_ai[c->index].state = AIS_BUILD_RAIL; - _companies_ai[c->index].state_mode = 255; -} - -static TileIndex AiGetEdgeOfDefaultRailBlock(byte rule, TileIndex tile, byte cmd, DiagDirection *dir) -{ - const AiDefaultBlockData *p = _default_rail_track_data[rule]->data; - - while (p->mode != 3 || !((--cmd) & 0x80)) p++; - - return tile + ToTileIndexDiff(p->tileoffs) - TileOffsByDiagDir(*dir = p->attr); -} - -struct AiRailPathFindData { - TileIndex tile; - TileIndex tile2; - int count; - bool flag; -}; - -static bool AiEnumFollowTrack(TileIndex tile, AiRailPathFindData *a, int track, uint length) -{ - if (a->flag) return true; - - if (length > 20 || tile == a->tile) { - a->flag = true; - return true; - } - - if (DistanceMax(tile, a->tile2) < 4) a->count++; - - return false; -} - -static bool AiDoFollowTrack(const Company *c) -{ - AiRailPathFindData arpfd; - - arpfd.tile = _companies_ai[c->index].start_tile_a; - arpfd.tile2 = _companies_ai[c->index].cur_tile_a; - arpfd.flag = false; - arpfd.count = 0; - FollowTrack(_companies_ai[c->index].cur_tile_a + TileOffsByDiagDir(_companies_ai[c->index].cur_dir_a), PATHFIND_FLAGS_NONE, TRANSPORT_RAIL, 0, ReverseDiagDir(_companies_ai[c->index].cur_dir_a), - (TPFEnumProc*)AiEnumFollowTrack, NULL, &arpfd); - return arpfd.count > 8; -} - -struct AiRailFinder { - TileIndex final_tile; - DiagDirection final_dir; - byte depth; - byte recursive_mode; - DiagDirection cur_best_dir; - DiagDirection best_dir; - byte cur_best_depth; - byte best_depth; - uint cur_best_dist; - const byte *best_ptr; - uint best_dist; - TileIndex cur_best_tile, best_tile; - TileIndex bridge_end_tile; - Company *company; -}; - -static const byte _ai_table_15[4][8] = { - {0, 0, 4, 3, 3, 1, 128 + 0, 64}, - {1, 1, 2, 0, 4, 2, 128 + 1, 65}, - {0, 2, 2, 3, 5, 1, 128 + 2, 66}, - {1, 3, 5, 0, 3, 2, 128 + 3, 67} -}; - - -static bool AiIsTileBanned(const Company *c, TileIndex tile, byte val) -{ - int i; - - for (i = 0; i != _companies_ai[c->index].banned_tile_count; i++) { - if (_companies_ai[c->index].banned_tiles[i] == tile && _companies_ai[c->index].banned_val[i] == val) { - return true; - } - } - return false; -} - -static void AiBanTile(Company *c, TileIndex tile, byte val) -{ - uint i; - - for (i = lengthof(_companies_ai[c->index].banned_tiles) - 1; i != 0; i--) { - _companies_ai[c->index].banned_tiles[i] = _companies_ai[c->index].banned_tiles[i - 1]; - _companies_ai[c->index].banned_val[i] = _companies_ai[c->index].banned_val[i - 1]; - } - - _companies_ai[c->index].banned_tiles[0] = tile; - _companies_ai[c->index].banned_val[0] = val; - - if (_companies_ai[c->index].banned_tile_count != lengthof(_companies_ai[c->index].banned_tiles)) { - _companies_ai[c->index].banned_tile_count++; - } -} - -static void AiBuildRailRecursive(AiRailFinder *arf, TileIndex tile, DiagDirection dir); - -static bool AiCheckRailPathBetter(AiRailFinder *arf, const byte *p) -{ - bool better = false; - - if (arf->recursive_mode < 1) { - // Mode is 0. This means destination has not been found yet. - // If the found path is shorter than the current one, remember it. - if (arf->cur_best_dist < arf->best_dist) { - arf->best_dir = arf->cur_best_dir; - arf->best_dist = arf->cur_best_dist; - arf->best_ptr = p; - arf->best_tile = arf->cur_best_tile; - better = true; - } - } else if (arf->recursive_mode > 1) { - // Mode is 2. - if (arf->best_dist != 0 || arf->cur_best_depth < arf->best_depth) { - arf->best_depth = arf->cur_best_depth; - arf->best_dist = 0; - arf->best_ptr = p; - arf->best_tile = 0; - better = true; - } - } - arf->recursive_mode = 0; - arf->cur_best_dist = UINT_MAX; - arf->cur_best_depth = 0xff; - - return better; -} - -static inline void AiCheckBuildRailBridgeHere(AiRailFinder *arf, TileIndex tile, const byte *p) -{ - Slope tileh; - uint z; - bool flag; - - DiagDirection dir2 = (DiagDirection)(p[0] & 3); - - tileh = GetTileSlope(tile, &z); - if (tileh == InclinedSlope(ReverseDiagDir(dir2)) || (tileh == SLOPE_FLAT && z != 0)) { - TileIndex tile_new = tile; - - // Allow bridges directly over bottom tiles - flag = z == 0; - for (;;) { - TileType type; - - if ((TileIndexDiff)tile_new < -TileOffsByDiagDir(dir2)) return; // Wraping around map, no bridge possible! - tile_new = TILE_MASK(tile_new + TileOffsByDiagDir(dir2)); - type = GetTileType(tile_new); - - if (type == MP_CLEAR || type == MP_TREES || GetTileSlope(tile_new, NULL) != SLOPE_FLAT) { - if (!flag) return; - break; - } - if (type != MP_WATER && type != MP_RAILWAY && type != MP_ROAD) return; - flag = true; - } - - // Is building a (rail)bridge possible at this place (type doesn't matter)? - if (CmdFailed(DoCommand(tile_new, tile, _companies_ai[arf->company->index].railtype_to_use << 8 | TRANSPORT_RAIL << 15, DC_AUTO, CMD_BUILD_BRIDGE))) { - return; - } - AiBuildRailRecursive(arf, tile_new, dir2); - - // At the bottom depth, check if the new path is better than the old one. - if (arf->depth == 1) { - if (AiCheckRailPathBetter(arf, p)) arf->bridge_end_tile = tile_new; - } - } -} - -static inline void AiCheckBuildRailTunnelHere(AiRailFinder *arf, TileIndex tile, const byte *p) -{ - uint z; - - if (GetTileSlope(tile, &z) == InclinedSlope((DiagDirection)(p[0] & 3)) && z != 0) { - CommandCost cost = DoCommand(tile, _companies_ai[arf->company->index].railtype_to_use, 0, DC_AUTO, CMD_BUILD_TUNNEL); - - if (CmdSucceeded(cost) && cost.GetCost() <= (arf->company->money >> 4)) { - AiBuildRailRecursive(arf, _build_tunnel_endtile, (DiagDirection)(p[0] & 3)); - if (arf->depth == 1) AiCheckRailPathBetter(arf, p); - } - } -} - - -static void AiBuildRailRecursive(AiRailFinder *arf, TileIndex tile, DiagDirection dir) -{ - const byte *p; - - tile = TILE_MASK(tile + TileOffsByDiagDir(dir)); - - // Reached destination? - if (tile == arf->final_tile) { - if (arf->final_dir != ReverseDiagDir(dir)) { - if (arf->recursive_mode != 2) arf->recursive_mode = 1; - } else if (arf->recursive_mode != 2) { - arf->recursive_mode = 2; - arf->cur_best_depth = arf->depth; - } else { - if (arf->depth < arf->cur_best_depth) arf->cur_best_depth = arf->depth; - } - return; - } - - // Depth too deep? - if (arf->depth >= 4) { - uint dist = DistanceMaxPlusManhattan(tile, arf->final_tile); - - if (dist < arf->cur_best_dist) { - // Store the tile that is closest to the final position. - arf->cur_best_depth = arf->depth; - arf->cur_best_dist = dist; - arf->cur_best_tile = tile; - arf->cur_best_dir = dir; - } - return; - } - - // Increase recursion depth - arf->depth++; - - // Grab pointer to list of stuff that is possible to build - p = _ai_table_15[dir]; - - // Try to build a single rail in all directions. - if (GetTileZ(tile) == 0) { - p += 6; - } else { - do { - // Make sure the tile is not in the list of banned tiles and that a rail can be built here. - if (!AiIsTileBanned(arf->company, tile, p[0]) && - CmdSucceeded(DoCommand(tile, _companies_ai[arf->company->index].railtype_to_use, p[0], DC_AUTO | DC_NO_WATER | DC_NO_RAIL_OVERLAP, CMD_BUILD_SINGLE_RAIL))) { - AiBuildRailRecursive(arf, tile, (DiagDirection)p[1]); - } - - // At the bottom depth? - if (arf->depth == 1) AiCheckRailPathBetter(arf, p); - - p += 2; - } while (!(p[0] & 0x80)); - } - - AiCheckBuildRailBridgeHere(arf, tile, p); - AiCheckBuildRailTunnelHere(arf, tile, p + 1); - - arf->depth--; -} - - -static void AiBuildRailConstruct(Company *c) -{ - AiRailFinder arf; - int i; - - // Check too much lookahead? - if (AiDoFollowTrack(c)) { - _companies_ai[c->index].state_counter = (Random()&0xE)+6; // Destruct this amount of blocks - _companies_ai[c->index].state_mode = 1; // Start destruct - - // Ban this tile and don't reach it for a while. - AiBanTile(c, _companies_ai[c->index].cur_tile_a, FindFirstBit(GetRailTrackStatus(_companies_ai[c->index].cur_tile_a))); - return; - } - - // Setup recursive finder and call it. - arf.company = c; - arf.final_tile = _companies_ai[c->index].cur_tile_b; - arf.final_dir = _companies_ai[c->index].cur_dir_b; - arf.depth = 0; - arf.recursive_mode = 0; - arf.best_ptr = NULL; - arf.cur_best_dist = UINT_MAX; - arf.cur_best_depth = 0xff; - arf.best_dist = UINT_MAX; - arf.best_depth = 0xff; - arf.cur_best_tile = 0; - arf.best_tile = 0; - AiBuildRailRecursive(&arf, _companies_ai[c->index].cur_tile_a, _companies_ai[c->index].cur_dir_a); - - // Reached destination? - if (arf.recursive_mode == 2 && arf.cur_best_depth == 0) { - _companies_ai[c->index].state_mode = 255; - return; - } - - // Didn't find anything to build? - if (arf.best_ptr == NULL) { - // Terraform some - for (i = 0; i != 5; i++) { - AiDoTerraformLand(_companies_ai[c->index].cur_tile_a, _companies_ai[c->index].cur_dir_a, 3, 0); - } - - if (++_companies_ai[c->index].state_counter == 21) { - _companies_ai[c->index].state_counter = 40; - _companies_ai[c->index].state_mode = 1; - - // Ban this tile - AiBanTile(c, _companies_ai[c->index].cur_tile_a, FindFirstBit(GetRailTrackStatus(_companies_ai[c->index].cur_tile_a))); - } - return; - } - - _companies_ai[c->index].cur_tile_a += TileOffsByDiagDir(_companies_ai[c->index].cur_dir_a); - - if (arf.best_ptr[0] & 0x80) { - TileIndex t1 = _companies_ai[c->index].cur_tile_a; - TileIndex t2 = arf.bridge_end_tile; - - int32 bridge_len = GetTunnelBridgeLength(t1, t2); - - DiagDirection dir = (TileX(t1) == TileX(t2) ? DIAGDIR_SE : DIAGDIR_SW); - Track track = DiagDirToDiagTrack(dir); - - if (t2 < t1) dir = ReverseDiagDir(dir); - - /* try to build a long rail instead of bridge... */ - bool fail = false; - CommandCost cost; - TileIndex t = t1; - - /* try to build one rail on each tile - can't use CMD_BUILD_RAILROAD_TRACK now, it can build one part of track without failing */ - do { - cost = DoCommand(t, _companies_ai[c->index].railtype_to_use, track, DC_AUTO | DC_NO_WATER, CMD_BUILD_SINGLE_RAIL); - /* do not allow building over existing track */ - if (CmdFailed(cost) || IsTileType(t, MP_RAILWAY)) { - fail = true; - break; - } - t += TileOffsByDiagDir(dir); - } while (t != t2); - - /* can we build long track? */ - if (!fail) cost = DoCommand(t1, t2, _companies_ai[c->index].railtype_to_use | (track << 4), DC_AUTO | DC_NO_WATER, CMD_BUILD_RAILROAD_TRACK); - - if (!fail && CmdSucceeded(cost) && cost.GetCost() <= c->money) { - DoCommand(t1, t2, _companies_ai[c->index].railtype_to_use | (track << 4), DC_AUTO | DC_NO_WATER | DC_EXEC, CMD_BUILD_RAILROAD_TRACK); - } else { - - /* Figure out which (rail)bridge type to build - * start with best bridge, then go down to worse and worse bridges - * unnecessary to check for worst bridge (i=0), since AI will always build that. */ - int i; - for (i = MAX_BRIDGES - 1; i != 0; i--) { - if (CheckBridge_Stuff(i, bridge_len)) { - CommandCost cost = DoCommand(t1, t2, i | _companies_ai[c->index].railtype_to_use << 8 | TRANSPORT_RAIL << 15, DC_AUTO, CMD_BUILD_BRIDGE); - if (CmdSucceeded(cost) && cost.GetCost() < (c->money >> 1) && cost.GetCost() < ((c->money + _economy.max_loan - c->current_loan) >> 5)) break; - } - } - - /* Build it */ - DoCommand(t1, t2, i | _companies_ai[c->index].railtype_to_use << 8 | TRANSPORT_RAIL << 15, DC_AUTO | DC_EXEC, CMD_BUILD_BRIDGE); - } - - _companies_ai[c->index].cur_tile_a = t2; - _companies_ai[c->index].state_counter = 0; - } else if (arf.best_ptr[0] & 0x40) { - // tunnel - DoCommand(_companies_ai[c->index].cur_tile_a, _companies_ai[c->index].railtype_to_use, 0, DC_AUTO | DC_EXEC, CMD_BUILD_TUNNEL); - _companies_ai[c->index].cur_tile_a = _build_tunnel_endtile; - _companies_ai[c->index].state_counter = 0; - } else { - // rail - _companies_ai[c->index].cur_dir_a = (DiagDirection)(arf.best_ptr[1] & 3); - DoCommand(_companies_ai[c->index].cur_tile_a, _companies_ai[c->index].railtype_to_use, arf.best_ptr[0], - DC_EXEC | DC_AUTO | DC_NO_WATER | DC_NO_RAIL_OVERLAP, CMD_BUILD_SINGLE_RAIL); - _companies_ai[c->index].state_counter = 0; - } - - if (arf.best_tile != 0) { - for (i = 0; i != 2; i++) { - AiDoTerraformLand(arf.best_tile, arf.best_dir, 3, 0); - } - } -} - -static bool AiRemoveTileAndGoForward(Company *c) -{ - const byte *ptr; - TileIndex tile = _companies_ai[c->index].cur_tile_a; - TileIndex tilenew; - - if (IsTileType(tile, MP_TUNNELBRIDGE)) { - if (IsTunnel(tile)) { - // Clear the tunnel and continue at the other side of it. - if (CmdFailed(DoCommand(tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR))) - return false; - _companies_ai[c->index].cur_tile_a = TILE_MASK(_build_tunnel_endtile - TileOffsByDiagDir(_companies_ai[c->index].cur_dir_a)); - return true; - } else { // IsBridge(tile) - // Check if the bridge points in the right direction. - // This is not really needed the first place AiRemoveTileAndGoForward is called. - if (DiagDirToAxis(GetTunnelBridgeDirection(tile)) != (_companies_ai[c->index].cur_dir_a & 1)) return false; - - tile = GetOtherBridgeEnd(tile); - - tilenew = TILE_MASK(tile - TileOffsByDiagDir(_companies_ai[c->index].cur_dir_a)); - // And clear the bridge. - if (CmdFailed(DoCommand(tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR))) - return false; - _companies_ai[c->index].cur_tile_a = tilenew; - return true; - } - } - - // Find the railtype at the position. Quit if no rail there. - TrackBits bits = GetRailTrackStatus(tile) & DiagdirReachesTracks(ReverseDiagDir(_companies_ai[c->index].cur_dir_a)); - if (bits == TRACK_BIT_NONE) return false; - - // Convert into a bit position that CMD_REMOVE_SINGLE_RAIL expects. - Track track = FindFirstTrack(bits); - - // Then remove and signals if there are any. - if (IsTileType(tile, MP_RAILWAY) && - GetRailTileType(tile) == RAIL_TILE_SIGNALS) { - DoCommand(tile, 0, 0, DC_EXEC, CMD_REMOVE_SIGNALS); - } - - // And also remove the rail. - if (CmdFailed(DoCommand(tile, 0, track, DC_EXEC, CMD_REMOVE_SINGLE_RAIL))) - return false; - - // Find the direction at the other edge of the rail. - ptr = _ai_table_15[ReverseDiagDir(_companies_ai[c->index].cur_dir_a)]; - while (ptr[0] != track) ptr += 2; - _companies_ai[c->index].cur_dir_a = ReverseDiagDir((DiagDirection)ptr[1]); - - // And then also switch tile. - _companies_ai[c->index].cur_tile_a = TILE_MASK(_companies_ai[c->index].cur_tile_a - TileOffsByDiagDir(_companies_ai[c->index].cur_dir_a)); - - return true; -} - - -static void AiBuildRailDestruct(Company *c) -{ - // Decrease timeout. - if (!--_companies_ai[c->index].state_counter) { - _companies_ai[c->index].state_mode = 2; - _companies_ai[c->index].state_counter = 0; - } - - // Don't do anything if the destination is already reached. - if (_companies_ai[c->index].cur_tile_a == _companies_ai[c->index].start_tile_a) return; - - AiRemoveTileAndGoForward(c); -} - - -static void AiBuildRail(Company *c) -{ - switch (_companies_ai[c->index].state_mode) { - case 0: // Construct mode, build new rail. - AiBuildRailConstruct(c); - break; - - case 1: // Destruct mode, destroy the rail currently built. - AiBuildRailDestruct(c); - break; - - case 2: { - uint i; - - // Terraform some and then try building again. - for (i = 0; i != 4; i++) { - AiDoTerraformLand(_companies_ai[c->index].cur_tile_a, _companies_ai[c->index].cur_dir_a, 3, 0); - } - - if (++_companies_ai[c->index].state_counter == 4) { - _companies_ai[c->index].state_counter = 0; - _companies_ai[c->index].state_mode = 0; - } - } - - default: break; - } -} - -static void AiStateBuildRail(Company *c) -{ - int num; - AiBuildRec *aib; - byte cmd; - TileIndex tile; - DiagDirection dir; - - // time out? - if (++_companies_ai[c->index].timeout_counter == 1388) { - _companies_ai[c->index].state = AIS_DELETE_RAIL_BLOCKS; - return; - } - - // Currently building a rail between two points? - if (_companies_ai[c->index].state_mode != 255) { - AiBuildRail(c); - - // Alternate between edges - Swap(_companies_ai[c->index].start_tile_a, _companies_ai[c->index].start_tile_b); - Swap(_companies_ai[c->index].cur_tile_a, _companies_ai[c->index].cur_tile_b); - Swap(_companies_ai[c->index].start_dir_a, _companies_ai[c->index].start_dir_b); - Swap(_companies_ai[c->index].cur_dir_a, _companies_ai[c->index].cur_dir_b); - return; - } - - // Now, find two new points to build between - num = _companies_ai[c->index].num_build_rec; - aib = &_companies_ai[c->index].src; - - for (;;) { - cmd = aib->buildcmd_a; - aib->buildcmd_a = 255; - if (cmd != 255) break; - - cmd = aib->buildcmd_b; - aib->buildcmd_b = 255; - if (cmd != 255) break; - - aib++; - if (--num == 0) { - _companies_ai[c->index].state = AIS_BUILD_RAIL_VEH; - _companies_ai[c->index].state_counter = 0; // timeout - return; - } - } - - // Find first edge to build from. - tile = AiGetEdgeOfDefaultRailBlock(aib->cur_building_rule, aib->use_tile, cmd & 3, &dir); - _companies_ai[c->index].start_tile_a = tile; - _companies_ai[c->index].cur_tile_a = tile; - _companies_ai[c->index].start_dir_a = dir; - _companies_ai[c->index].cur_dir_a = dir; - DoCommand(TILE_MASK(tile + TileOffsByDiagDir(dir)), 0, (dir & 1) ? 1 : 0, DC_EXEC, CMD_REMOVE_SINGLE_RAIL); - - assert(TILE_MASK(tile) != 0xFF00); - - // Find second edge to build to - aib = (&_companies_ai[c->index].src) + ((cmd >> 4) & 0xF); - tile = AiGetEdgeOfDefaultRailBlock(aib->cur_building_rule, aib->use_tile, (cmd >> 2) & 3, &dir); - _companies_ai[c->index].start_tile_b = tile; - _companies_ai[c->index].cur_tile_b = tile; - _companies_ai[c->index].start_dir_b = dir; - _companies_ai[c->index].cur_dir_b = dir; - DoCommand(TILE_MASK(tile + TileOffsByDiagDir(dir)), 0, (dir & 1) ? 1 : 0, DC_EXEC, CMD_REMOVE_SINGLE_RAIL); - - assert(TILE_MASK(tile) != 0xFF00); - - // And setup state. - _companies_ai[c->index].state_mode = 2; - _companies_ai[c->index].state_counter = 0; - _companies_ai[c->index].banned_tile_count = 0; -} - -static StationID AiGetStationIdByDef(TileIndex tile, int id) -{ - const AiDefaultBlockData *p = _default_rail_track_data[id]->data; - while (p->mode != 1) p++; - return GetStationIndex(TILE_ADD(tile, ToTileIndexDiff(p->tileoffs))); -} - -static EngineID AiFindBestWagon(CargoID cargo, RailType railtype) -{ - EngineID best_veh_index = INVALID_ENGINE; - uint16 best_capacity = 0; - uint16 best_speed = 0; - uint speed; - const Engine *e; - - FOR_ALL_ENGINES_OF_TYPE(e, VEH_TRAIN) { - EngineID i = e->index; - const RailVehicleInfo *rvi = &e->u.rail; - - if (!IsCompatibleRail(rvi->railtype, railtype) || - rvi->railveh_type != RAILVEH_WAGON || - !HasBit(e->company_avail, _current_company)) { - continue; - } - - if (rvi->cargo_type != cargo) continue; - - /* max_speed of 0 indicates no speed limit */ - speed = rvi->max_speed == 0 ? 0xFFFF : rvi->max_speed; - - if (rvi->capacity >= best_capacity && speed >= best_speed) { - best_capacity = rvi->capacity; - best_speed = best_speed; - best_veh_index = i; - } - } - - return best_veh_index; -} - -static void AiStateBuildRailVeh(Company *c) -{ - const AiDefaultBlockData *ptr; - TileIndex tile; - EngineID veh; - int i; - CargoID cargo; - CommandCost cost; - Vehicle *v; - VehicleID loco_id; - - ptr = _default_rail_track_data[_companies_ai[c->index].src.cur_building_rule]->data; - while (ptr->mode != 0) ptr++; - - tile = TILE_ADD(_companies_ai[c->index].src.use_tile, ToTileIndexDiff(ptr->tileoffs)); - - - cargo = _companies_ai[c->index].cargo_type; - for (i = 0;;) { - if (_companies_ai[c->index].wagon_list[i] == INVALID_VEHICLE) { - veh = AiFindBestWagon(cargo, _companies_ai[c->index].railtype_to_use); - /* veh will return INVALID_ENGINE if no suitable wagon is available. - * We shall treat this in the same way as having no money */ - if (veh == INVALID_ENGINE) goto handle_nocash; - cost = DoCommand(tile, veh, 0, DC_EXEC, CMD_BUILD_RAIL_VEHICLE); - if (CmdFailed(cost)) goto handle_nocash; - _companies_ai[c->index].wagon_list[i] = _new_vehicle_id; - _companies_ai[c->index].wagon_list[i + 1] = INVALID_VEHICLE; - return; - } - if (cargo == CT_MAIL) cargo = CT_PASSENGERS; - if (++i == _companies_ai[c->index].num_wagons * 2 - 1) break; - } - - // Which locomotive to build? - veh = AiChooseTrainToBuild(_companies_ai[c->index].railtype_to_use, c->money, cargo != CT_PASSENGERS ? 1 : 0, tile); - if (veh == INVALID_ENGINE) { -handle_nocash: - // after a while, if AI still doesn't have cash, get out of this block by selling the wagons. - if (++_companies_ai[c->index].state_counter == 1000) { - for (i = 0; _companies_ai[c->index].wagon_list[i] != INVALID_VEHICLE; i++) { - cost = DoCommand(tile, _companies_ai[c->index].wagon_list[i], 0, DC_EXEC, CMD_SELL_RAIL_WAGON); - assert(CmdSucceeded(cost)); - } - _companies_ai[c->index].state = AIS_0; - } - return; - } - - // Try to build the locomotive - cost = DoCommand(tile, veh, 0, DC_EXEC, CMD_BUILD_RAIL_VEHICLE); - assert(CmdSucceeded(cost)); - loco_id = _new_vehicle_id; - - // Sell a vehicle if the train is double headed. - v = GetVehicle(loco_id); - if (v->Next() != NULL) { - i = _companies_ai[c->index].wagon_list[_companies_ai[c->index].num_wagons * 2 - 2]; - _companies_ai[c->index].wagon_list[_companies_ai[c->index].num_wagons * 2 - 2] = INVALID_VEHICLE; - DoCommand(tile, i, 0, DC_EXEC, CMD_SELL_RAIL_WAGON); - } - - // Move the wagons onto the train - for (i = 0; _companies_ai[c->index].wagon_list[i] != INVALID_VEHICLE; i++) { - DoCommand(tile, _companies_ai[c->index].wagon_list[i] | (loco_id << 16), 0, DC_EXEC, CMD_MOVE_RAIL_VEHICLE); - } - - for (i = 0; _companies_ai[c->index].order_list_blocks[i] != 0xFF; i++) { - const AiBuildRec *aib = &_companies_ai[c->index].src + _companies_ai[c->index].order_list_blocks[i]; - bool is_pass = ( - _companies_ai[c->index].cargo_type == CT_PASSENGERS || - _companies_ai[c->index].cargo_type == CT_MAIL || - (_settings_game.game_creation.landscape == LT_TEMPERATE && _companies_ai[c->index].cargo_type == CT_VALUABLES) - ); - Order order; - - order.MakeGoToStation(AiGetStationIdByDef(aib->use_tile, aib->cur_building_rule)); - - if (!is_pass && i == 1) order.SetUnloadType(OUFB_UNLOAD); - if (_companies_ai[c->index].num_want_fullload != 0 && (is_pass || i == 0)) - order.SetLoadType(OLFB_FULL_LOAD); - - DoCommand(0, loco_id + (i << 16), order.Pack(), DC_EXEC, CMD_INSERT_ORDER); - } - - DoCommand(0, loco_id, 0, DC_EXEC, CMD_START_STOP_VEHICLE); - - DoCommand(0, loco_id, _ai_service_interval, DC_EXEC, CMD_CHANGE_SERVICE_INT); - - if (_companies_ai[c->index].num_want_fullload != 0) _companies_ai[c->index].num_want_fullload--; - - if (--_companies_ai[c->index].num_loco_to_build != 0) { -// _companies_ai[c->index].loco_id = INVALID_VEHICLE; - _companies_ai[c->index].wagon_list[0] = INVALID_VEHICLE; - } else { - _companies_ai[c->index].state = AIS_0; - } -} - -static void AiStateDeleteRailBlocks(Company *c) -{ - const AiBuildRec *aib = &_companies_ai[c->index].src; - uint num = _companies_ai[c->index].num_build_rec; - - do { - const AiDefaultBlockData *b; - - if (aib->cur_building_rule == 255) continue; - for (b = _default_rail_track_data[aib->cur_building_rule]->data; b->mode != 4; b++) { - DoCommand(TILE_ADD(aib->use_tile, ToTileIndexDiff(b->tileoffs)), 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR); - } - } while (++aib, --num); - - _companies_ai[c->index].state = AIS_0; -} - -static bool AiCheckRoadResources(TileIndex tile, const AiDefaultBlockData *p, byte cargo) -{ - uint values[NUM_CARGO]; - int rad; - - if (_settings_game.station.modified_catchment) { - rad = CA_TRUCK; // Same as CA_BUS at the moment? - } else { // change that at some point? - rad = 4; - } - - for (;; p++) { - if (p->mode == 4) { - return true; - } else if (p->mode == 1) { - TileIndex tile2 = TILE_ADD(tile, ToTileIndexDiff(p->tileoffs)); - - if (cargo & 0x80) { - GetProductionAroundTiles(values, tile2, 1, 1, rad); - return values[cargo & 0x7F] != 0; - } else { - GetAcceptanceAroundTiles(values, tile2, 1, 1, rad); - return (values[cargo]&~7) != 0; - } - } - } -} - -static bool _want_road_truck_station; -static CommandCost AiDoBuildDefaultRoadBlock(TileIndex tile, const AiDefaultBlockData *p, uint32 flag); - -// Returns rule and cost -static int AiFindBestDefaultRoadBlock(TileIndex tile, byte direction, byte cargo, CommandCost *cost) -{ - int i; - const AiDefaultRoadBlock *p; - - _want_road_truck_station = (cargo & 0x7F) != CT_PASSENGERS; - - for (i = 0; (p = _road_default_block_data[i]) != NULL; i++) { - if (p->dir == direction) { - *cost = AiDoBuildDefaultRoadBlock(tile, p->data, 0); - if (CmdSucceeded(*cost) && AiCheckRoadResources(tile, p->data, cargo)) - return i; - } - } - - return -1; -} - -static CommandCost AiDoBuildDefaultRoadBlock(TileIndex tile, const AiDefaultBlockData *p, uint32 flag) -{ - CommandCost ret; - CommandCost total_cost(EXPENSES_CONSTRUCTION); - Town *t = NULL; - int rating = 0; - int roadflag = 0; - - for (;p->mode != 4;p++) { - TileIndex c = TILE_MASK(tile + ToTileIndexDiff(p->tileoffs)); - - _cleared_town = NULL; - - if (p->mode == 2) { - if (IsNormalRoadTile(c) && - (GetRoadBits(c, ROADTYPE_ROAD) & p->attr) != 0) { - roadflag |= 2; - - // all bits are already built? - if ((GetRoadBits(c, ROADTYPE_ROAD) & p->attr) == p->attr) continue; - } - - ret = DoCommand(c, p->attr, 0, flag | DC_AUTO | DC_NO_WATER, CMD_BUILD_ROAD); - if (CmdFailed(ret)) return CMD_ERROR; - total_cost.AddCost(ret); - - continue; - } - - if (p->mode == 0) { - // Depot - ret = DoCommand(c, p->attr, 0, flag | DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, CMD_BUILD_ROAD_DEPOT); - goto clear_town_stuff; - } else if (p->mode == 1) { - if (_want_road_truck_station) { - // Truck station - ret = DoCommand(c, p->attr, ROADTYPES_ROAD << 2 | ROADSTOP_TRUCK | INVALID_STATION << 16, flag | DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, CMD_BUILD_ROAD_STOP); - } else { - // Bus station - ret = DoCommand(c, p->attr, ROADTYPES_ROAD << 2 | ROADSTOP_BUS | INVALID_STATION << 16, flag | DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, CMD_BUILD_ROAD_STOP); - } -clear_town_stuff:; - - if (CmdFailed(ret)) return CMD_ERROR; - total_cost.AddCost(ret); - - if (_cleared_town != NULL) { - if (t != NULL && t != _cleared_town) return CMD_ERROR; - t = _cleared_town; - rating += _cleared_town_rating; - } - } else if (p->mode == 3) { - if (flag & DC_EXEC) continue; - - if (GetTileSlope(c, NULL) != SLOPE_FLAT) return CMD_ERROR; - - if (!IsNormalRoadTile(c)) { - ret = DoCommand(c, 0, 0, flag | DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, CMD_LANDSCAPE_CLEAR); - if (CmdFailed(ret)) return CMD_ERROR; - } - - } - } - - if (!_want_road_truck_station && !(roadflag & 2)) return CMD_ERROR; - - if (!(flag & DC_EXEC)) { - if (t != NULL && rating > t->ratings[_current_company]) return CMD_ERROR; - } - return total_cost; -} - -// Make sure the blocks are not too close to each other -static bool AiCheckBlockDistances(Company *c, TileIndex tile) -{ - const AiBuildRec *aib = &_companies_ai[c->index].src; - uint num = _companies_ai[c->index].num_build_rec; - - do { - if (aib->cur_building_rule != 255) { - if (DistanceManhattan(aib->use_tile, tile) < 9) return false; - } - } while (++aib, --num); - - return true; -} - - -static void AiStateBuildDefaultRoadBlocks(Company *c) -{ - uint i; - int j; - AiBuildRec *aib; - int rule; - CommandCost cost; - - // time out? - if (++_companies_ai[c->index].timeout_counter == 1388) { - _companies_ai[c->index].state = AIS_DELETE_RAIL_BLOCKS; - return; - } - - // do the following 8 times - for (i = 0; i != 8; i++) { - // check if we can build the default track - aib = &_companies_ai[c->index].src; - j = _companies_ai[c->index].num_build_rec; - do { - // this item has already been built? - if (aib->cur_building_rule != 255) continue; - - // adjust the coordinate randomly, - // to make sure that we find a position. - aib->use_tile = AdjustTileCoordRandomly(aib->spec_tile, aib->rand_rng); - - // check if the road can be built there. - rule = AiFindBestDefaultRoadBlock( - aib->use_tile, aib->direction, aib->cargo, &cost - ); - - if (rule == -1) { - // cannot build, terraform after a while - if (_companies_ai[c->index].state_counter >= 600) { - AiDoTerraformLand(aib->use_tile, (DiagDirection)(Random() & 3), 3, (int8)_companies_ai[c->index].state_mode); - } - // also try the other terraform direction - if (++_companies_ai[c->index].state_counter >= 1000) { - _companies_ai[c->index].state_counter = 0; - _companies_ai[c->index].state_mode = -_companies_ai[c->index].state_mode; - } - } else if (CheckCompanyHasMoney(cost) && AiCheckBlockDistances(c, aib->use_tile)) { - CommandCost r; - - // company has money, build it. - aib->cur_building_rule = rule; - - r = AiDoBuildDefaultRoadBlock( - aib->use_tile, - _road_default_block_data[rule]->data, - DC_EXEC | DC_NO_TOWN_RATING - ); - assert(CmdSucceeded(r)); - } - } while (++aib, --j); - } - - // check if we're done with all of them - aib = &_companies_ai[c->index].src; - j = _companies_ai[c->index].num_build_rec; - do { - if (aib->cur_building_rule == 255) return; - } while (++aib, --j); - - // yep, all are done. switch state to the rail building state. - _companies_ai[c->index].state = AIS_BUILD_ROAD; - _companies_ai[c->index].state_mode = 255; -} - -struct AiRoadFinder { - TileIndex final_tile; - DiagDirection final_dir; - byte depth; - byte recursive_mode; - DiagDirection cur_best_dir; - DiagDirection best_dir; - byte cur_best_depth; - byte best_depth; - uint cur_best_dist; - const byte *best_ptr; - uint best_dist; - TileIndex cur_best_tile, best_tile; - TileIndex bridge_end_tile; - Company *company; -}; - -struct AiRoadEnum { - TileIndex dest; - TileIndex best_tile; - Trackdir best_track; - uint best_dist; -}; - -static void AiBuildRoadRecursive(AiRoadFinder *arf, TileIndex tile, DiagDirection dir); - -static bool AiCheckRoadPathBetter(AiRoadFinder *arf, const byte *p) -{ - bool better = false; - - if (arf->recursive_mode < 1) { - // Mode is 0. This means destination has not been found yet. - // If the found path is shorter than the current one, remember it. - if (arf->cur_best_dist < arf->best_dist || - (arf->cur_best_dist == arf->best_dist && arf->cur_best_depth < arf->best_depth)) { - arf->best_depth = arf->cur_best_depth; - arf->best_dist = arf->cur_best_dist; - arf->best_dir = arf->cur_best_dir; - arf->best_ptr = p; - arf->best_tile = arf->cur_best_tile; - better = true; - } - } else if (arf->recursive_mode > 1) { - // Mode is 2. - if (arf->best_dist != 0 || arf->cur_best_depth < arf->best_depth) { - arf->best_depth = arf->cur_best_depth; - arf->best_dist = 0; - arf->best_ptr = p; - arf->best_tile = 0; - better = true; - } - } - arf->recursive_mode = 0; - arf->cur_best_dist = UINT_MAX; - arf->cur_best_depth = 0xff; - - return better; -} - - -static bool AiEnumFollowRoad(TileIndex tile, AiRoadEnum *a, Trackdir track, uint length) -{ - uint dist = DistanceManhattan(tile, a->dest); - - if (dist <= a->best_dist) { - TileIndex tile2 = TILE_MASK(tile + TileOffsByDiagDir(TrackdirToExitdir(track))); - - if (IsNormalRoadTile(tile2)) { - a->best_dist = dist; - a->best_tile = tile; - a->best_track = track; - } - } - - return false; -} - -static bool AiCheckRoadFinished(Company *c) -{ - AiRoadEnum are; - TileIndex tile; - DiagDirection dir = _companies_ai[c->index].cur_dir_a; - - are.dest = _companies_ai[c->index].cur_tile_b; - tile = TILE_MASK(_companies_ai[c->index].cur_tile_a + TileOffsByDiagDir(dir)); - - if (IsRoadStopTile(tile) || IsRoadDepotTile(tile)) return false; - TrackdirBits bits = TrackStatusToTrackdirBits(GetTileTrackStatus(tile, TRANSPORT_ROAD, ROADTYPES_ROAD)) & DiagdirReachesTrackdirs(dir); - if (bits == TRACKDIR_BIT_NONE) return false; - - are.best_dist = UINT_MAX; - - while (bits != TRACKDIR_BIT_NONE) { - Trackdir trackdir = RemoveFirstTrackdir(&bits); - FollowTrack(tile, PATHFIND_FLAGS_DISABLE_TILE_HASH, TRANSPORT_ROAD, ROADTYPES_ROAD, TrackdirToExitdir(trackdir), (TPFEnumProc*)AiEnumFollowRoad, NULL, &are); - } - - if (DistanceManhattan(tile, are.dest) <= are.best_dist) return false; - - if (are.best_dist == 0) return true; - - _companies_ai[c->index].cur_tile_a = are.best_tile; - _companies_ai[c->index].cur_dir_a = TrackdirToExitdir(are.best_track); - return false; -} - - -static bool AiBuildRoadHelper(TileIndex tile, uint32 flags, int type) -{ - static const RoadBits _road_bits[] = { - ROAD_X, - ROAD_Y, - ROAD_NW | ROAD_NE, - ROAD_SW | ROAD_SE, - ROAD_NW | ROAD_SW, - ROAD_SE | ROAD_NE - }; - return CmdSucceeded(DoCommand(tile, _road_bits[type], 0, flags, CMD_BUILD_ROAD)); -} - -static inline void AiCheckBuildRoadBridgeHere(AiRoadFinder *arf, TileIndex tile, const byte *p) -{ - Slope tileh; - uint z; - bool flag; - - DiagDirection dir2 = (DiagDirection)(p[0] & 3); - - tileh = GetTileSlope(tile, &z); - if (tileh == InclinedSlope(ReverseDiagDir(dir2)) || (tileh == SLOPE_FLAT && z != 0)) { - TileIndex tile_new = tile; - - // Allow bridges directly over bottom tiles - flag = z == 0; - for (;;) { - TileType type; - - if ((TileIndexDiff)tile_new < -TileOffsByDiagDir(dir2)) return; // Wraping around map, no bridge possible! - tile_new = TILE_MASK(tile_new + TileOffsByDiagDir(dir2)); - type = GetTileType(tile_new); - - if (type == MP_CLEAR || type == MP_TREES || GetTileSlope(tile_new, NULL) != SLOPE_FLAT) { - // Allow a bridge if either we have a tile that's water, rail or street, - // or if we found an up tile. - if (!flag) return; - break; - } - if (type != MP_WATER && type != MP_RAILWAY && type != MP_ROAD) return; - flag = true; - } - - // Is building a (rail)bridge possible at this place (type doesn't matter)? - if (CmdFailed(DoCommand(tile_new, tile, ROADTYPES_ROAD << 8 | TRANSPORT_ROAD << 15, DC_AUTO, CMD_BUILD_BRIDGE))) - return; - AiBuildRoadRecursive(arf, tile_new, dir2); - - // At the bottom depth, check if the new path is better than the old one. - if (arf->depth == 1) { - if (AiCheckRoadPathBetter(arf, p)) arf->bridge_end_tile = tile_new; - } - } -} - -static inline void AiCheckBuildRoadTunnelHere(AiRoadFinder *arf, TileIndex tile, const byte *p) -{ - uint z; - - if (GetTileSlope(tile, &z) == InclinedSlope((DiagDirection)(p[0] & 3)) && z != 0) { - CommandCost cost = DoCommand(tile, 0x200, 0, DC_AUTO, CMD_BUILD_TUNNEL); - - if (CmdSucceeded(cost) && cost.GetCost() <= (arf->company->money >> 4)) { - AiBuildRoadRecursive(arf, _build_tunnel_endtile, (DiagDirection)(p[0] & 3)); - if (arf->depth == 1) AiCheckRoadPathBetter(arf, p); - } - } -} - - - -static void AiBuildRoadRecursive(AiRoadFinder *arf, TileIndex tile, DiagDirection dir) -{ - const byte *p; - - tile = TILE_MASK(tile + TileOffsByDiagDir(dir)); - - // Reached destination? - if (tile == arf->final_tile) { - if (ReverseDiagDir(arf->final_dir) == dir) { - arf->recursive_mode = 2; - arf->cur_best_depth = arf->depth; - } - return; - } - - // Depth too deep? - if (arf->depth >= 4) { - uint dist = DistanceMaxPlusManhattan(tile, arf->final_tile); - if (dist < arf->cur_best_dist) { - // Store the tile that is closest to the final position. - arf->cur_best_dist = dist; - arf->cur_best_tile = tile; - arf->cur_best_dir = dir; - arf->cur_best_depth = arf->depth; - } - return; - } - - // Increase recursion depth - arf->depth++; - - // Grab pointer to list of stuff that is possible to build - p = _ai_table_15[dir]; - - // Try to build a single rail in all directions. - if (GetTileZ(tile) == 0) { - p += 6; - } else { - do { - // Make sure that a road can be built here. - if (AiBuildRoadHelper(tile, DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, p[0])) { - AiBuildRoadRecursive(arf, tile, (DiagDirection)p[1]); - } - - // At the bottom depth? - if (arf->depth == 1) AiCheckRoadPathBetter(arf, p); - - p += 2; - } while (!(p[0] & 0x80)); - } - - AiCheckBuildRoadBridgeHere(arf, tile, p); - AiCheckBuildRoadTunnelHere(arf, tile, p + 1); - - arf->depth--; -} - - -static void AiBuildRoadConstruct(Company *c) -{ - AiRoadFinder arf; - int i; - TileIndex tile; - - // Reached destination? - if (AiCheckRoadFinished(c)) { - _companies_ai[c->index].state_mode = 255; - return; - } - - // Setup recursive finder and call it. - arf.company = c; - arf.final_tile = _companies_ai[c->index].cur_tile_b; - arf.final_dir = _companies_ai[c->index].cur_dir_b; - arf.depth = 0; - arf.recursive_mode = 0; - arf.best_ptr = NULL; - arf.cur_best_dist = UINT_MAX; - arf.cur_best_depth = 0xff; - arf.best_dist = UINT_MAX; - arf.best_depth = 0xff; - arf.cur_best_tile = 0; - arf.best_tile = 0; - AiBuildRoadRecursive(&arf, _companies_ai[c->index].cur_tile_a, _companies_ai[c->index].cur_dir_a); - - // Reached destination? - if (arf.recursive_mode == 2 && arf.cur_best_depth == 0) { - _companies_ai[c->index].state_mode = 255; - return; - } - - // Didn't find anything to build? - if (arf.best_ptr == NULL) { - // Terraform some -do_some_terraform: - for (i = 0; i != 5; i++) - AiDoTerraformLand(_companies_ai[c->index].cur_tile_a, _companies_ai[c->index].cur_dir_a, 3, 0); - - if (++_companies_ai[c->index].state_counter == 21) { - _companies_ai[c->index].state_mode = 1; - - _companies_ai[c->index].cur_tile_a = TILE_MASK(_companies_ai[c->index].cur_tile_a + TileOffsByDiagDir(_companies_ai[c->index].cur_dir_a)); - _companies_ai[c->index].cur_dir_a = ReverseDiagDir(_companies_ai[c->index].cur_dir_a); - _companies_ai[c->index].state_counter = 0; - } - return; - } - - tile = TILE_MASK(_companies_ai[c->index].cur_tile_a + TileOffsByDiagDir(_companies_ai[c->index].cur_dir_a)); - - if (arf.best_ptr[0] & 0x80) { - TileIndex t1 = tile; - TileIndex t2 = arf.bridge_end_tile; - - int32 bridge_len = GetTunnelBridgeLength(t1, t2); - - Axis axis = (TileX(t1) == TileX(t2) ? AXIS_Y : AXIS_X); - - /* try to build a long road instead of bridge - CMD_BUILD_LONG_ROAD has to fail if it couldn't build at least one piece! */ - CommandCost cost = DoCommand(t2, t1, (t2 < t1 ? 1 : 2) | (axis << 2) | (ROADTYPE_ROAD << 3), DC_AUTO | DC_NO_WATER, CMD_BUILD_LONG_ROAD); - - if (CmdSucceeded(cost) && cost.GetCost() <= c->money) { - DoCommand(t2, t1, (t2 < t1 ? 1 : 2) | (axis << 2) | (ROADTYPE_ROAD << 3), DC_AUTO | DC_EXEC | DC_NO_WATER, CMD_BUILD_LONG_ROAD); - } else { - int i; - - /* Figure out what (road)bridge type to build - * start with best bridge, then go down to worse and worse bridges - * unnecessary to check for worse bridge (i=0), since AI will always build that */ - for (i = MAX_BRIDGES - 1; i != 0; i--) { - if (CheckBridge_Stuff(i, bridge_len)) { - CommandCost cost = DoCommand(t1, t2, i | ROADTYPES_ROAD << 8 | TRANSPORT_ROAD << 15, DC_AUTO, CMD_BUILD_BRIDGE); - if (CmdSucceeded(cost) && cost.GetCost() < (c->money >> 1) && cost.GetCost() < ((c->money + _economy.max_loan - c->current_loan) >> 5)) break; - } - } - - /* Build it */ - DoCommand(t1, t2, i | ROADTYPES_ROAD << 8 | TRANSPORT_ROAD << 15, DC_AUTO | DC_EXEC, CMD_BUILD_BRIDGE); - } - - _companies_ai[c->index].cur_tile_a = t2; - _companies_ai[c->index].state_counter = 0; - } else if (arf.best_ptr[0] & 0x40) { - // tunnel - DoCommand(tile, 0x200, 0, DC_AUTO | DC_EXEC, CMD_BUILD_TUNNEL); - _companies_ai[c->index].cur_tile_a = _build_tunnel_endtile; - _companies_ai[c->index].state_counter = 0; - } else { - // road - if (!AiBuildRoadHelper(tile, DC_EXEC | DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, arf.best_ptr[0])) - goto do_some_terraform; - - _companies_ai[c->index].cur_dir_a = (DiagDirection)(arf.best_ptr[1] & 3); - _companies_ai[c->index].cur_tile_a = tile; - _companies_ai[c->index].state_counter = 0; - } - - if (arf.best_tile != 0) { - for (i = 0; i != 2; i++) - AiDoTerraformLand(arf.best_tile, arf.best_dir, 3, 0); - } -} - - -static void AiBuildRoad(Company *c) -{ - if (_companies_ai[c->index].state_mode < 1) { - // Construct mode, build new road. - AiBuildRoadConstruct(c); - } else if (_companies_ai[c->index].state_mode == 1) { - // Destruct mode, not implemented for roads. - _companies_ai[c->index].state_mode = 2; - _companies_ai[c->index].state_counter = 0; - } else if (_companies_ai[c->index].state_mode == 2) { - uint i; - - // Terraform some and then try building again. - for (i = 0; i != 4; i++) { - AiDoTerraformLand(_companies_ai[c->index].cur_tile_a, _companies_ai[c->index].cur_dir_a, 3, 0); - } - - if (++_companies_ai[c->index].state_counter == 4) { - _companies_ai[c->index].state_counter = 0; - _companies_ai[c->index].state_mode = 0; - } - } -} - -static TileIndex AiGetRoadBlockEdge(byte rule, TileIndex tile, DiagDirection *dir) -{ - const AiDefaultBlockData *p = _road_default_block_data[rule]->data; - while (p->mode != 1) p++; - *dir = p->attr; - return TILE_ADD(tile, ToTileIndexDiff(p->tileoffs)); -} - - -static void AiStateBuildRoad(Company *c) -{ - int num; - AiBuildRec *aib; - byte cmd; - TileIndex tile; - DiagDirection dir; - - // time out? - if (++_companies_ai[c->index].timeout_counter == 1388) { - _companies_ai[c->index].state = AIS_DELETE_ROAD_BLOCKS; - return; - } - - // Currently building a road between two points? - if (_companies_ai[c->index].state_mode != 255) { - AiBuildRoad(c); - - // Alternate between edges - Swap(_companies_ai[c->index].start_tile_a, _companies_ai[c->index].start_tile_b); - Swap(_companies_ai[c->index].cur_tile_a, _companies_ai[c->index].cur_tile_b); - Swap(_companies_ai[c->index].start_dir_a, _companies_ai[c->index].start_dir_b); - Swap(_companies_ai[c->index].cur_dir_a, _companies_ai[c->index].cur_dir_b); - - return; - } - - // Now, find two new points to build between - num = _companies_ai[c->index].num_build_rec; - aib = &_companies_ai[c->index].src; - - for (;;) { - cmd = aib->buildcmd_a; - aib->buildcmd_a = 255; - if (cmd != 255) break; - - aib++; - if (--num == 0) { - _companies_ai[c->index].state = AIS_BUILD_ROAD_VEHICLES; - return; - } - } - - // Find first edge to build from. - tile = AiGetRoadBlockEdge(aib->cur_building_rule, aib->use_tile, &dir); - _companies_ai[c->index].start_tile_a = tile; - _companies_ai[c->index].cur_tile_a = tile; - _companies_ai[c->index].start_dir_a = dir; - _companies_ai[c->index].cur_dir_a = dir; - - // Find second edge to build to - aib = (&_companies_ai[c->index].src) + (cmd & 0xF); - tile = AiGetRoadBlockEdge(aib->cur_building_rule, aib->use_tile, &dir); - _companies_ai[c->index].start_tile_b = tile; - _companies_ai[c->index].cur_tile_b = tile; - _companies_ai[c->index].start_dir_b = dir; - _companies_ai[c->index].cur_dir_b = dir; - - // And setup state. - _companies_ai[c->index].state_mode = 2; - _companies_ai[c->index].state_counter = 0; - _companies_ai[c->index].banned_tile_count = 0; -} - -static StationID AiGetStationIdFromRoadBlock(TileIndex tile, int id) -{ - const AiDefaultBlockData *p = _road_default_block_data[id]->data; - while (p->mode != 1) p++; - return GetStationIndex(TILE_ADD(tile, ToTileIndexDiff(p->tileoffs))); -} - -static void AiStateBuildRoadVehicles(Company *c) -{ - const AiDefaultBlockData *ptr; - TileIndex tile; - VehicleID loco_id; - EngineID veh; - uint i; - - ptr = _road_default_block_data[_companies_ai[c->index].src.cur_building_rule]->data; - for (; ptr->mode != 0; ptr++) {} - tile = TILE_ADD(_companies_ai[c->index].src.use_tile, ToTileIndexDiff(ptr->tileoffs)); - - veh = AiChooseRoadVehToBuild(_companies_ai[c->index].cargo_type, c->money, tile); - if (veh == INVALID_ENGINE) { - _companies_ai[c->index].state = AIS_0; - return; - } - - if (CmdFailed(DoCommand(tile, veh, 0, DC_EXEC, CMD_BUILD_ROAD_VEH))) return; - - loco_id = _new_vehicle_id; - - if (GetVehicle(loco_id)->cargo_type != _companies_ai[c->index].cargo_type) { - /* Cargo type doesn't match, so refit it */ - if (CmdFailed(DoCommand(tile, loco_id, _companies_ai[c->index].cargo_type, DC_EXEC, CMD_REFIT_ROAD_VEH))) { - /* Refit failed... sell the vehicle */ - DoCommand(tile, loco_id, 0, DC_EXEC, CMD_SELL_ROAD_VEH); - return; - } - } - - for (i = 0; _companies_ai[c->index].order_list_blocks[i] != 0xFF; i++) { - const AiBuildRec *aib = &_companies_ai[c->index].src + _companies_ai[c->index].order_list_blocks[i]; - bool is_pass = ( - _companies_ai[c->index].cargo_type == CT_PASSENGERS || - _companies_ai[c->index].cargo_type == CT_MAIL || - (_settings_game.game_creation.landscape == LT_TEMPERATE && _companies_ai[c->index].cargo_type == CT_VALUABLES) - ); - Order order; - - order.MakeGoToStation(AiGetStationIdFromRoadBlock(aib->use_tile, aib->cur_building_rule)); - - if (!is_pass && i == 1) order.SetUnloadType(OUFB_UNLOAD); - if (_companies_ai[c->index].num_want_fullload != 0 && (is_pass || i == 0)) - order.SetLoadType(OLFB_FULL_LOAD); - - DoCommand(0, loco_id + (i << 16), order.Pack(), DC_EXEC, CMD_INSERT_ORDER); - } - - DoCommand(0, loco_id, 0, DC_EXEC, CMD_START_STOP_VEHICLE); - DoCommand(0, loco_id, _ai_service_interval, DC_EXEC, CMD_CHANGE_SERVICE_INT); - - if (_companies_ai[c->index].num_want_fullload != 0) _companies_ai[c->index].num_want_fullload--; - if (--_companies_ai[c->index].num_loco_to_build == 0) _companies_ai[c->index].state = AIS_0; -} - -static void AiStateDeleteRoadBlocks(Company *c) -{ - const AiBuildRec *aib = &_companies_ai[c->index].src; - uint num = _companies_ai[c->index].num_build_rec; - - do { - const AiDefaultBlockData *b; - - if (aib->cur_building_rule == 255) continue; - for (b = _road_default_block_data[aib->cur_building_rule]->data; b->mode != 4; b++) { - if (b->mode > 1) continue; - DoCommand(TILE_ADD(aib->use_tile, ToTileIndexDiff(b->tileoffs)), 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR); - } - } while (++aib, --num); - - _companies_ai[c->index].state = AIS_0; -} - - -static void AiStateAirportStuff(Company *c) -{ - const Station *st; - int i; - AiBuildRec *aib; - byte rule; - - // Here we look for an airport we could use instead of building a new - // one. If we find such an aiport for any waypoint, - // AiStateBuildDefaultAirportBlocks() will kindly skip that one when - // building the waypoints. - - i = 0; - do { - // We do this all twice - once for the source (town in the case - // of oilrig route) and then for the destination (oilrig in the - // case of oilrig route). - aib = &_companies_ai[c->index].src + i; - - FOR_ALL_STATIONS(st) { - // Is this an airport? - if (!(st->facilities & FACIL_AIRPORT)) continue; - - // Do we own the airport? (Oilrigs aren't owned, though.) - if (st->owner != OWNER_NONE && st->owner != _current_company) continue; - - AirportFTAClass::Flags flags = st->Airport()->flags; - - /* if airport doesn't accept our kind of plane, dismiss it */ - if (!(flags & (_companies_ai[c->index].build_kind == 1 ? AirportFTAClass::HELICOPTERS : AirportFTAClass::AIRPLANES))) { - continue; - } - - // Dismiss airports too far away. - if (DistanceMax(st->airport_tile, aib->spec_tile) > aib->rand_rng) - continue; - - // It's ideal airport, let's take it! - - /* XXX: This part is utterly broken - rule should - * contain number of the rule appropriate for the - * airport type (country, town, ...), see - * _airport_default_block_data (rule is just an index - * in this array). But the only difference between the - * currently existing two rules (rule 0 - town and rule - * 1 - country) is the attr field which is used only - * when building new airports - and that's irrelevant - * for us. So using just about any rule will suffice - * here for now (some of the new airport types would be - * broken because they will probably need different - * tileoff values etc), no matter that - * IsHangarTile() makes no sense. --pasky */ - if (!(flags & AirportFTAClass::AIRPLANES)) { - /* Heliports should have maybe own rulesets but - * OTOH we don't want AI to pick them up when - * looking for a suitable airport type to build. - * So any of rules 0 or 1 would do for now. The - * original rule number was 2 but that's a bug - * because we have no such rule. */ - rule = 1; - } else { - rule = IsHangarTile(st->airport_tile); - } - - aib->cur_building_rule = rule; - aib->use_tile = st->airport_tile; - break; - } - } while (++i != _companies_ai[c->index].num_build_rec); - - _companies_ai[c->index].state = AIS_BUILD_DEFAULT_AIRPORT_BLOCKS; - _companies_ai[c->index].state_mode = 255; - _companies_ai[c->index].state_counter = 0; -} - -static CommandCost AiDoBuildDefaultAirportBlock(TileIndex tile, const AiDefaultBlockData *p, uint32 flag) -{ - uint32 avail_airports = GetValidAirports(); - CommandCost ret, total_cost(EXPENSES_CONSTRUCTION); - - for (; p->mode == 0; p++) { - if (!HasBit(avail_airports, p->attr)) return CMD_ERROR; - ret = DoCommand(TILE_MASK(tile + ToTileIndexDiff(p->tileoffs)), p->attr, INVALID_STATION << 16, flag | DC_AUTO | DC_NO_WATER, CMD_BUILD_AIRPORT); - if (CmdFailed(ret)) return CMD_ERROR; - total_cost.AddCost(ret); - } - - return total_cost; -} - -static bool AiCheckAirportResources(TileIndex tile, const AiDefaultBlockData *p, byte cargo) -{ - uint values[NUM_CARGO]; - - for (; p->mode == 0; p++) { - TileIndex tile2 = TILE_ADD(tile, ToTileIndexDiff(p->tileoffs)); - const AirportFTAClass *airport = GetAirport(p->attr); - uint w = airport->size_x; - uint h = airport->size_y; - uint rad = _settings_game.station.modified_catchment ? airport->catchment : (uint)CA_UNMODIFIED; - - if (cargo & 0x80) { - GetProductionAroundTiles(values, tile2, w, h, rad); - return values[cargo & 0x7F] != 0; - } else { - GetAcceptanceAroundTiles(values, tile2, w, h, rad); - return values[cargo] >= 8; - } - } - return true; -} - -static int AiFindBestDefaultAirportBlock(TileIndex tile, byte cargo, byte heli, CommandCost *cost) -{ - const AiDefaultBlockData *p; - - bool no_small = false; - - if (!heli) { - /* do not build small airport if we have large available and we are not building heli route */ - uint valid = GetValidAirports(); - for (uint i = 0; (p = _airport_default_block_data[i]) != NULL; i++) { - uint flags = GetAirport(p->attr)->flags; - if (HasBit(valid, p->attr) && (flags & AirportFTAClass::AIRPLANES) && !(flags & AirportFTAClass::SHORT_STRIP)) { - no_small = true; - break; - } - } - } - - for (uint i = 0; (p = _airport_default_block_data[i]) != NULL; i++) { - uint flags = GetAirport(p->attr)->flags; - /* If we are doing a helicopter service, avoid building airports where they can't land */ - if (heli && !(flags & AirportFTAClass::HELICOPTERS)) continue; - /* Similiar with aircraft ... */ - if (!heli && !(flags & AirportFTAClass::AIRPLANES)) continue; - /* Do not build small airport if we prefer large */ - if (no_small && (flags & AirportFTAClass::SHORT_STRIP)) continue; - - *cost = AiDoBuildDefaultAirportBlock(tile, p, 0); - if (CmdSucceeded(*cost) && AiCheckAirportResources(tile, p, cargo)) - return i; - } - return -1; -} - -static void AiStateBuildDefaultAirportBlocks(Company *c) -{ - int i, j; - AiBuildRec *aib; - int rule; - CommandCost cost; - - // time out? - if (++_companies_ai[c->index].timeout_counter == 1388) { - _companies_ai[c->index].state = AIS_0; - return; - } - - // do the following 8 times - i = 8; - do { - // check if we can build the default - aib = &_companies_ai[c->index].src; - j = _companies_ai[c->index].num_build_rec; - do { - // this item has already been built? - if (aib->cur_building_rule != 255) continue; - - // adjust the coordinate randomly, - // to make sure that we find a position. - aib->use_tile = AdjustTileCoordRandomly(aib->spec_tile, aib->rand_rng); - - // check if the aircraft stuff can be built there. - rule = AiFindBestDefaultAirportBlock(aib->use_tile, aib->cargo, _companies_ai[c->index].build_kind, &cost); - -// SetRedErrorSquare(aib->use_tile); - - if (rule == -1) { - // cannot build, terraform after a while - if (_companies_ai[c->index].state_counter >= 600) { - AiDoTerraformLand(aib->use_tile, (DiagDirection)(Random() & 3), 3, (int8)_companies_ai[c->index].state_mode); - } - // also try the other terraform direction - if (++_companies_ai[c->index].state_counter >= 1000) { - _companies_ai[c->index].state_counter = 0; - _companies_ai[c->index].state_mode = -_companies_ai[c->index].state_mode; - } - } else if (CheckCompanyHasMoney(cost) && AiCheckBlockDistances(c, aib->use_tile)) { - // company has money, build it. - CommandCost r; - - aib->cur_building_rule = rule; - - r = AiDoBuildDefaultAirportBlock( - aib->use_tile, - _airport_default_block_data[rule], - DC_EXEC | DC_NO_TOWN_RATING - ); - assert(CmdSucceeded(r)); - } - } while (++aib, --j); - } while (--i); - - // check if we're done with all of them - aib = &_companies_ai[c->index].src; - j = _companies_ai[c->index].num_build_rec; - do { - if (aib->cur_building_rule == 255) return; - } while (++aib, --j); - - // yep, all are done. switch state. - _companies_ai[c->index].state = AIS_BUILD_AIRCRAFT_VEHICLES; -} - -static StationID AiGetStationIdFromAircraftBlock(TileIndex tile, int id) -{ - const AiDefaultBlockData *p = _airport_default_block_data[id]; - while (p->mode != 1) p++; - return GetStationIndex(TILE_ADD(tile, ToTileIndexDiff(p->tileoffs))); -} - -static void AiStateBuildAircraftVehicles(Company *c) -{ - const AiDefaultBlockData *ptr; - TileIndex tile; - EngineID veh; - int i; - VehicleID loco_id; - - ptr = _airport_default_block_data[_companies_ai[c->index].src.cur_building_rule]; - for (; ptr->mode != 0; ptr++) {} - - tile = TILE_ADD(_companies_ai[c->index].src.use_tile, ToTileIndexDiff(ptr->tileoffs)); - - /* determine forbidden aircraft bits */ - byte forbidden = 0; - for (i = 0; _companies_ai[c->index].order_list_blocks[i] != 0xFF; i++) { - const AiBuildRec *aib = (&_companies_ai[c->index].src) + _companies_ai[c->index].order_list_blocks[i]; - const Station *st = GetStationByTile(aib->use_tile); - - if (st == NULL || !(st->facilities & FACIL_AIRPORT)) continue; - - AirportFTAClass::Flags flags = st->Airport()->flags; - if (!(flags & AirportFTAClass::AIRPLANES)) forbidden |= AIR_CTOL | AIR_FAST; // no planes for heliports / oil rigs - if (flags & AirportFTAClass::SHORT_STRIP) forbidden |= AIR_FAST; // no fast planes for small airports - } - - veh = AiChooseAircraftToBuild(c->money, forbidden); - if (veh == INVALID_ENGINE) return; - if (GetStationByTile(tile)->Airport()->nof_depots == 0) return; - - /* XXX - Have the AI pick the hangar terminal in an airport. Eg get airport-type - * and offset to the FIRST depot because the AI picks the st->xy tile */ - tile += ToTileIndexDiff(GetStationByTile(tile)->Airport()->airport_depots[0]); - if (CmdFailed(DoCommand(tile, veh, 0, DC_EXEC, CMD_BUILD_AIRCRAFT))) return; - loco_id = _new_vehicle_id; - - for (i = 0; _companies_ai[c->index].order_list_blocks[i] != 0xFF; i++) { - AiBuildRec *aib = (&_companies_ai[c->index].src) + _companies_ai[c->index].order_list_blocks[i]; - bool is_pass = (_companies_ai[c->index].cargo_type == CT_PASSENGERS || _companies_ai[c->index].cargo_type == CT_MAIL); - Order order; - - order.MakeGoToStation(AiGetStationIdFromAircraftBlock(aib->use_tile, aib->cur_building_rule)); - - if (!is_pass && i == 1) order.SetUnloadType(OUFB_UNLOAD); - if (_companies_ai[c->index].num_want_fullload != 0 && (is_pass || i == 0)) - order.SetLoadType(OLFB_FULL_LOAD); - - DoCommand(0, loco_id + (i << 16), order.Pack(), DC_EXEC, CMD_INSERT_ORDER); - } - - DoCommand(0, loco_id, 0, DC_EXEC, CMD_START_STOP_VEHICLE); - - DoCommand(0, loco_id, _ai_service_interval, DC_EXEC, CMD_CHANGE_SERVICE_INT); - - if (_companies_ai[c->index].num_want_fullload != 0) _companies_ai[c->index].num_want_fullload--; - - if (--_companies_ai[c->index].num_loco_to_build == 0) _companies_ai[c->index].state = AIS_0; -} - -static void AiStateCheckShipStuff(Company *c) -{ - /* Ships are not implemented in this (broken) AI */ -} - -static void AiStateBuildDefaultShipBlocks(Company *c) -{ - /* Ships are not implemented in this (broken) AI */ -} - -static void AiStateDoShipStuff(Company *c) -{ - /* Ships are not implemented in this (broken) AI */ -} - -static void AiStateSellVeh(Company *c) -{ - Vehicle *v = _companies_ai[c->index].cur_veh; - - if (v->owner == _current_company) { - if (v->type == VEH_TRAIN) { - - if (!IsRailDepotTile(v->tile) || v->u.rail.track != TRACK_BIT_DEPOT || !(v->vehstatus & VS_STOPPED)) { - if (!v->current_order.IsType(OT_GOTO_DEPOT)) - DoCommand(0, v->index, 0, DC_EXEC, CMD_SEND_TRAIN_TO_DEPOT); - goto going_to_depot; - } - - // Sell whole train - DoCommand(v->tile, v->index, 1, DC_EXEC, CMD_SELL_RAIL_WAGON); - - } else if (v->type == VEH_ROAD) { - if (!v->IsStoppedInDepot()) { - if (!v->current_order.IsType(OT_GOTO_DEPOT)) - DoCommand(0, v->index, 0, DC_EXEC, CMD_SEND_ROADVEH_TO_DEPOT); - goto going_to_depot; - } - - DoCommand(0, v->index, 0, DC_EXEC, CMD_SELL_ROAD_VEH); - } else if (v->type == VEH_AIRCRAFT) { - if (!v->IsStoppedInDepot()) { - if (!v->current_order.IsType(OT_GOTO_DEPOT)) - DoCommand(0, v->index, 0, DC_EXEC, CMD_SEND_AIRCRAFT_TO_HANGAR); - goto going_to_depot; - } - - DoCommand(0, v->index, 0, DC_EXEC, CMD_SELL_AIRCRAFT); - } else if (v->type == VEH_SHIP) { - /* Ships are not implemented in this (broken) AI */ - } - } - - goto return_to_loop; -going_to_depot:; - if (++_companies_ai[c->index].state_counter <= 832) return; - - if (v->current_order.IsType(OT_GOTO_DEPOT)) { - v->current_order.MakeDummy(); - InvalidateWindow(WC_VEHICLE_VIEW, v->index); - } -return_to_loop:; - _companies_ai[c->index].state = AIS_VEH_LOOP; -} - -static void AiStateRemoveStation(Company *c) -{ - // Remove stations that aren't in use by any vehicle - const Order *ord; - const Station *st; - TileIndex tile; - - // Go to this state when we're done. - _companies_ai[c->index].state = AIS_1; - - // Get a list of all stations that are in use by a vehicle - byte *in_use = MallocT<byte>(GetMaxStationIndex() + 1); - memset(in_use, 0, GetMaxStationIndex() + 1); - FOR_ALL_ORDERS(ord) { - if (ord->IsType(OT_GOTO_STATION)) in_use[ord->GetDestination()] = 1; - } - - // Go through all stations and delete those that aren't in use - FOR_ALL_STATIONS(st) { - if (st->owner == _current_company && !in_use[st->index] && - ( (st->bus_stops != NULL && (tile = st->bus_stops->xy) != INVALID_TILE) || - (st->truck_stops != NULL && (tile = st->truck_stops->xy) != INVALID_TILE) || - (tile = st->train_tile) != INVALID_TILE || - (tile = st->dock_tile) != INVALID_TILE || - (tile = st->airport_tile) != INVALID_TILE)) { - DoCommand(tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR); - } - } - - free(in_use); -} - -static void AiRemoveCompanyRailOrRoad(Company *c, TileIndex tile) -{ - TrackBits rails; - - if (IsTileType(tile, MP_RAILWAY)) { - if (!IsTileOwner(tile, _current_company)) return; - - if (IsPlainRailTile(tile)) { -is_rail_crossing:; - rails = GetRailTrackStatus(tile); - - if (rails == TRACK_BIT_HORZ || rails == TRACK_BIT_VERT) return; - - if (rails & TRACK_BIT_3WAY_NE) { -pos_0: - if ((GetRailTrackStatus(TILE_MASK(tile - TileDiffXY(1, 0))) & TRACK_BIT_3WAY_SW) == 0) { - _companies_ai[c->index].cur_dir_a = DIAGDIR_NE; - _companies_ai[c->index].cur_tile_a = tile; - _companies_ai[c->index].state = AIS_REMOVE_SINGLE_RAIL_TILE; - return; - } - } - - if (rails & TRACK_BIT_3WAY_SE) { -pos_1: - if ((GetRailTrackStatus(TILE_MASK(tile + TileDiffXY(0, 1))) & TRACK_BIT_3WAY_NW) == 0) { - _companies_ai[c->index].cur_dir_a = DIAGDIR_SE; - _companies_ai[c->index].cur_tile_a = tile; - _companies_ai[c->index].state = AIS_REMOVE_SINGLE_RAIL_TILE; - return; - } - } - - if (rails & TRACK_BIT_3WAY_SW) { -pos_2: - if ((GetRailTrackStatus(TILE_MASK(tile + TileDiffXY(1, 0))) & TRACK_BIT_3WAY_NE) == 0) { - _companies_ai[c->index].cur_dir_a = DIAGDIR_SW; - _companies_ai[c->index].cur_tile_a = tile; - _companies_ai[c->index].state = AIS_REMOVE_SINGLE_RAIL_TILE; - return; - } - } - - if (rails & TRACK_BIT_3WAY_NW) { -pos_3: - if ((GetRailTrackStatus(TILE_MASK(tile - TileDiffXY(0, 1))) & TRACK_BIT_3WAY_SE) == 0) { - _companies_ai[c->index].cur_dir_a = DIAGDIR_NW; - _companies_ai[c->index].cur_tile_a = tile; - _companies_ai[c->index].state = AIS_REMOVE_SINGLE_RAIL_TILE; - return; - } - } - } else { - static const byte _depot_bits[] = {0x19, 0x16, 0x25, 0x2A}; - - DiagDirection dir = GetRailDepotDirection(tile); - - if (GetRailTrackStatus(tile + TileOffsByDiagDir(dir)) & _depot_bits[dir]) - return; - - DoCommand(tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR); - } - } else if (IsTileType(tile, MP_ROAD)) { - if (IsLevelCrossing(tile)) goto is_rail_crossing; - - if (IsRoadDepot(tile)) { - if (!IsTileOwner(tile, _current_company)) return; - - DiagDirection dir; - TileIndex t; - - // Check if there are any stations around. - t = tile + TileDiffXY(-1, 0); - if (IsTileType(t, MP_STATION) && IsTileOwner(t, _current_company)) return; - - t = tile + TileDiffXY(1, 0); - if (IsTileType(t, MP_STATION) && IsTileOwner(t, _current_company)) return; - - t = tile + TileDiffXY(0, -1); - if (IsTileType(t, MP_STATION) && IsTileOwner(t, _current_company)) return; - - t = tile + TileDiffXY(0, 1); - if (IsTileType(t, MP_STATION) && IsTileOwner(t, _current_company)) return; - - dir = GetRoadDepotDirection(tile); - - DoCommand(tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR); - DoCommand( - TILE_MASK(tile + TileOffsByDiagDir(dir)), - DiagDirToRoadBits(ReverseDiagDir(dir)), - 0, - DC_EXEC, - CMD_REMOVE_ROAD); - } - } else if (IsTileType(tile, MP_TUNNELBRIDGE)) { - if (!IsTileOwner(tile, _current_company) || - !IsBridge(tile) || - GetTunnelBridgeTransportType(tile) != TRANSPORT_RAIL) { - return; - } - - rails = TRACK_BIT_NONE; - - switch (GetTunnelBridgeDirection(tile)) { - default: - case DIAGDIR_NE: goto pos_2; - case DIAGDIR_SE: goto pos_3; - case DIAGDIR_SW: goto pos_0; - case DIAGDIR_NW: goto pos_1; - } - } -} - -static void AiStateRemoveTrack(Company *c) -{ - /* Was 1000 for standard 8x8 maps. */ - int num = MapSizeX() * 4; - - do { - TileIndex tile = ++_companies_ai[c->index].state_counter; - - // Iterated all tiles? - if (tile >= MapSize()) { - _companies_ai[c->index].state = AIS_REMOVE_STATION; - return; - } - - // Remove company stuff in that tile - AiRemoveCompanyRailOrRoad(c, tile); - if (_companies_ai[c->index].state != AIS_REMOVE_TRACK) return; - } while (--num); -} - -static void AiStateRemoveSingleRailTile(Company *c) -{ - // Remove until we can't remove more. - if (!AiRemoveTileAndGoForward(c)) _companies_ai[c->index].state = AIS_REMOVE_TRACK; -} - -static AiStateAction * const _ai_actions[] = { - AiCase0, - AiCase1, - AiStateVehLoop, - AiStateCheckReplaceVehicle, - AiStateDoReplaceVehicle, - AiStateWantNewRoute, - - AiStateBuildDefaultRailBlocks, - AiStateBuildRail, - AiStateBuildRailVeh, - AiStateDeleteRailBlocks, - - AiStateBuildDefaultRoadBlocks, - AiStateBuildRoad, - AiStateBuildRoadVehicles, - AiStateDeleteRoadBlocks, - - AiStateAirportStuff, - AiStateBuildDefaultAirportBlocks, - AiStateBuildAircraftVehicles, - - AiStateCheckShipStuff, - AiStateBuildDefaultShipBlocks, - AiStateDoShipStuff, - - AiStateSellVeh, - AiStateRemoveStation, - AiStateRemoveTrack, - - AiStateRemoveSingleRailTile -}; - -extern void ShowBuyCompanyDialog(CompanyID company); - -static void AiHandleTakeover(Company *c) -{ - if (c->bankrupt_timeout != 0) { - c->bankrupt_timeout -= 8; - if (c->bankrupt_timeout > 0) return; - c->bankrupt_timeout = 0; - DeleteWindowById(WC_BUY_COMPANY, _current_company); - if (IsLocalCompany()) { - AskExitToGameMenu(); - return; - } - if (IsHumanCompany(_current_company)) return; - } - - if (c->bankrupt_asked == MAX_UVALUE(CompanyMask)) return; - - { - CompanyMask asked = c->bankrupt_asked; - Company *company, *best_company = NULL; - int32 best_val = -1; - - // Ask the guy with the highest performance hist. - FOR_ALL_COMPANIES(company) { - if (!(asked & 1) && - company->bankrupt_asked == 0 && - best_val < company->old_economy[1].performance_history) { - best_val = company->old_economy[1].performance_history; - best_company = company; - } - asked >>= 1; - } - - // Asked all companies? - if (best_val == -1) { - c->bankrupt_asked = MAX_UVALUE(CompanyMask); - return; - } - - SetBit(c->bankrupt_asked, best_company->index); - - if (best_company->index == _local_company) { - c->bankrupt_timeout = 4440; - ShowBuyCompanyDialog(_current_company); - return; - } - if (IsHumanCompany(best_company->index)) return; - - // Too little money for computer to buy it? - if (best_company->money >> 1 >= c->bankrupt_value) { - // Computer wants to buy it. - CompanyID old_company = _current_company; - _current_company = best_company->index; - DoCommand(0, old_company, 0, DC_EXEC, CMD_BUY_COMPANY); - _current_company = old_company; - } - } -} - -static void AiAdjustLoan(const Company *c) -{ - Money base = AiGetBasePrice(c); - - if (c->money > base * 1400) { - // Decrease loan - if (c->current_loan != 0) { - DoCommand(0, 0, 0, DC_EXEC, CMD_DECREASE_LOAN); - } - } else if (c->money < base * 500) { - // Increase loan - if (c->current_loan < _economy.max_loan && - c->num_valid_stat_ent >= 2 && - -(c->old_economy[0].expenses + c->old_economy[1].expenses) < base * 60) { - DoCommand(0, 0, 0, DC_EXEC, CMD_INCREASE_LOAN); - } - } -} - -static void AiBuildCompanyHQ(Company *c) -{ - TileIndex tile; - - if (c->location_of_HQ == INVALID_TILE && - c->last_build_coordinate != 0) { - tile = AdjustTileCoordRandomly(c->last_build_coordinate, 8); - DoCommand(tile, 0, 0, DC_EXEC | DC_AUTO | DC_NO_WATER, CMD_BUILD_COMPANY_HQ); - } -} - - -void AiDoGameLoop(Company *c) -{ - if (c->bankrupt_asked != 0) { - AiHandleTakeover(c); - return; - } - - // Ugly hack to make sure the service interval of the AI is good, not looking - // to the patch-setting - // Also, it takes into account the setting if the service-interval is in days - // or in % - _ai_service_interval = _settings_game.vehicle.servint_ispercent ? 80 : 180; - - if (IsHumanCompany(_current_company)) return; - - AiAdjustLoan(c); - AiBuildCompanyHQ(c); - -#if 0 - { - static byte old_state = 99; - static bool hasdots = false; - char *_ai_state_names[] = { - "AiCase0", - "AiCase1", - "AiStateVehLoop", - "AiStateCheckReplaceVehicle", - "AiStateDoReplaceVehicle", - "AiStateWantNewRoute", - "AiStateBuildDefaultRailBlocks", - "AiStateBuildRail", - "AiStateBuildRailVeh", - "AiStateDeleteRailBlocks", - "AiStateBuildDefaultRoadBlocks", - "AiStateBuildRoad", - "AiStateBuildRoadVehicles", - "AiStateDeleteRoadBlocks", - "AiStateAirportStuff", - "AiStateBuildDefaultAirportBlocks", - "AiStateBuildAircraftVehicles", - "AiStateCheckShipStuff", - "AiStateBuildDefaultShipBlocks", - "AiStateDoShipStuff", - "AiStateSellVeh", - "AiStateRemoveStation", - "AiStateRemoveTrack", - "AiStateRemoveSingleRailTile" - }; - - if (_companies_ai[c->index].state != old_state) { - if (hasdots) - printf("\n"); - hasdots = false; - printf("AiState: %s\n", _ai_state_names[old_state=_companies_ai[c->index].state]); - } else { - printf("."); - hasdots = true; - } - } -#endif - - _ai_actions[_companies_ai[c->index].state](c); -} diff --git a/src/ai/default/default.h b/src/ai/default/default.h deleted file mode 100644 index 843cf0f17..000000000 --- a/src/ai/default/default.h +++ /dev/null @@ -1,69 +0,0 @@ -/* $Id$ */ - -/** @file default.h The original AI. */ - -#ifndef DEFAULT_H -#define DEFAULT_H - -#include "../../direction_type.h" -#include "../../vehicle_type.h" -#include "../../rail_type.h" - -void AiDoGameLoop(Company *c); - -struct AiBuildRec { - TileIndex spec_tile; - TileIndex use_tile; - byte rand_rng; - byte cur_building_rule; - byte unk6; - byte unk7; - byte buildcmd_a; - byte buildcmd_b; - byte direction; - CargoID cargo; -}; - -struct CompanyAI { - byte state; - byte tick; ///< Used to determine how often to move - uint32 state_counter; ///< Can hold tile index! - uint16 timeout_counter; - - byte state_mode; - byte banned_tile_count; - RailTypeByte railtype_to_use; - - CargoID cargo_type; - byte num_wagons; - byte build_kind; - byte num_build_rec; - byte num_loco_to_build; - byte num_want_fullload; - - byte route_type_mask; - - TileIndex start_tile_a; - TileIndex cur_tile_a; - DiagDirectionByte cur_dir_a; - DiagDirectionByte start_dir_a; - - TileIndex start_tile_b; - TileIndex cur_tile_b; - DiagDirectionByte cur_dir_b; - DiagDirectionByte start_dir_b; - - Vehicle *cur_veh; ///< only used by some states - - AiBuildRec src, dst, mid1, mid2; - - VehicleID wagon_list[9]; - byte order_list_blocks[20]; - - TileIndex banned_tiles[16]; - byte banned_val[16]; -}; - -extern CompanyAI _companies_ai[MAX_COMPANIES]; - -#endif diff --git a/src/ai/trolly/build.cpp b/src/ai/trolly/build.cpp deleted file mode 100644 index 0723386f7..000000000 --- a/src/ai/trolly/build.cpp +++ /dev/null @@ -1,328 +0,0 @@ -/* $Id$ */ - -/** @file build.cpp Building support for the trolly AI. */ - -#include "../../stdafx.h" -#include "../../openttd.h" -#include "../../debug.h" -#include "../../road_map.h" -#include "../../command_func.h" -#include "trolly.h" -#include "../../engine_func.h" -#include "../../engine_base.h" -#include "../../variables.h" -#include "../../bridge.h" -#include "../../vehicle_func.h" -#include "../../vehicle_base.h" -#include "../../company_base.h" -#include "../../company_func.h" -#include "../ai.h" -#include "../../tunnelbridge.h" - - -// Build HQ -// Params: -// tile : tile where HQ is going to be build -bool AiNew_Build_CompanyHQ(Company *c, TileIndex tile) -{ - if (CmdFailed(AI_DoCommand(tile, 0, 0, DC_AUTO | DC_NO_WATER, CMD_BUILD_COMPANY_HQ))) - return false; - AI_DoCommand(tile, 0, 0, DC_EXEC | DC_AUTO | DC_NO_WATER, CMD_BUILD_COMPANY_HQ); - return true; -} - - -// Build station -// Params: -// type : AI_TRAIN/AI_BUS/AI_TRUCK : indicates the type of station -// tile : tile where station is going to be build -// length : in case of AI_TRAIN: length of station -// numtracks : in case of AI_TRAIN: tracks of station -// direction : the direction of the station -// flag : flag passed to DoCommand (normally 0 to get the cost or DC_EXEC to build it) -CommandCost AiNew_Build_Station(Company *c, byte type, TileIndex tile, byte length, byte numtracks, byte direction, byte flag) -{ - if (type == AI_TRAIN) - return AI_DoCommand(tile, (direction << 4) + (numtracks << 8) + (length << 16), INVALID_STATION << 16, flag | DC_AUTO | DC_NO_WATER, CMD_BUILD_RAILROAD_STATION); - - if (type == AI_BUS) - return AI_DoCommand(tile, direction, ROADTYPES_ROAD << 2 | ROADSTOP_BUS | INVALID_STATION << 16, flag | DC_AUTO | DC_NO_WATER, CMD_BUILD_ROAD_STOP); - - return AI_DoCommand(tile, direction, ROADTYPES_ROAD << 2 | ROADSTOP_TRUCK | INVALID_STATION << 16, flag | DC_AUTO | DC_NO_WATER, CMD_BUILD_ROAD_STOP); -} - - -// Builds a brdige. The second best out of the ones available for this company -// Params: -// tile_a : starting point -// tile_b : end point -// flag : flag passed to DoCommand -CommandCost AiNew_Build_Bridge(Company *c, TileIndex tile_a, TileIndex tile_b, byte flag) -{ - int bridge_type, bridge_len, type, type2; - - // Find a good bridgetype (the best money can buy) - bridge_len = GetTunnelBridgeLength(tile_a, tile_b); - type = type2 = 0; - for (bridge_type = MAX_BRIDGES - 1; bridge_type >= 0; bridge_type--) { - if (CheckBridge_Stuff(bridge_type, bridge_len)) { - type2 = type; - type = bridge_type; - // We found two bridges, exit - if (type2 != 0) break; - } - } - // There is only one bridge that can be built - if (type2 == 0 && type != 0) type2 = type; - - // Now, simply, build the bridge! - if (_companies_ainew[c->index].tbt == AI_TRAIN) { - return AI_DoCommand(tile_a, tile_b, type2 | RAILTYPE_RAIL << 8 | TRANSPORT_RAIL << 15, flag | DC_AUTO, CMD_BUILD_BRIDGE); - } else { - return AI_DoCommand(tile_a, tile_b, type2 | ROADTYPES_ROAD << 8 | TRANSPORT_ROAD << 15, flag | DC_AUTO, CMD_BUILD_BRIDGE); - } -} - - -// Build the route part by part -// Basicly what this function do, is build that amount of parts of the route -// that go in the same direction. It sets 'part' to the last part of the route builded. -// The return value is the cost for the builded parts -// -// Params: -// PathFinderInfo : Pointer to the PathFinderInfo used for AiPathFinder -// part : Which part we need to build -// -// TODO: skip already builded road-pieces (e.g.: cityroad) -CommandCost AiNew_Build_RoutePart(Company *c, Ai_PathFinderInfo *PathFinderInfo, byte flag) -{ - int part = PathFinderInfo->position; - byte *route_extra = PathFinderInfo->route_extra; - TileIndex *route = PathFinderInfo->route; - int dir; - int old_dir = -1; - CommandCost cost; - CommandCost res; - // We need to calculate the direction with the parent of the parent.. so we skip - // the first pieces and the last piece - if (part < 1) part = 1; - // When we are done, stop it - if (part >= PathFinderInfo->route_length - 1) { - PathFinderInfo->position = -2; - return CommandCost(); - } - - - if (PathFinderInfo->rail_or_road) { - // Tunnel code - if ((AI_PATHFINDER_FLAG_TUNNEL & route_extra[part]) != 0) { - cost.AddCost(AI_DoCommand(route[part], 0, 0, flag, CMD_BUILD_TUNNEL)); - PathFinderInfo->position++; - // TODO: problems! - if (CmdFailed(cost)) { - DEBUG(ai, 0, "[BuildPath] tunnel could not be built (0x%X)", route[part]); - return CommandCost(); - } - return cost; - } - // Bridge code - if ((AI_PATHFINDER_FLAG_BRIDGE & route_extra[part]) != 0) { - cost.AddCost(AiNew_Build_Bridge(c, route[part], route[part - 1], flag)); - PathFinderInfo->position++; - // TODO: problems! - if (CmdFailed(cost)) { - DEBUG(ai, 0, "[BuildPath] bridge could not be built (0x%X, 0x%X)", route[part], route[part - 1]); - return CommandCost(); - } - return cost; - } - - // Build normal rail - // Keep it doing till we go an other way - if (route_extra[part - 1] == 0 && route_extra[part] == 0) { - while (route_extra[part] == 0) { - // Get the current direction - dir = AiNew_GetRailDirection(route[part - 1], route[part], route[part + 1]); - // Is it the same as the last one? - if (old_dir != -1 && old_dir != dir) break; - old_dir = dir; - // Build the tile - res = AI_DoCommand(route[part], 0, dir, flag, CMD_BUILD_SINGLE_RAIL); - if (CmdFailed(res)) { - // Problem.. let's just abort it all! - _companies_ainew[c->index].state = AI_STATE_NOTHING; - return CommandCost(); - } - cost.AddCost(res); - // Go to the next tile - part++; - // Check if it is still in range.. - if (part >= PathFinderInfo->route_length - 1) break; - } - part--; - } - // We want to return the last position, so we go back one - PathFinderInfo->position = part; - } else { - // Tunnel code - if ((AI_PATHFINDER_FLAG_TUNNEL & route_extra[part]) != 0) { - cost.AddCost(AI_DoCommand(route[part], 0x200 | ROADTYPES_ROAD, 0, flag, CMD_BUILD_TUNNEL)); - PathFinderInfo->position++; - // TODO: problems! - if (CmdFailed(cost)) { - DEBUG(ai, 0, "[BuildPath] tunnel could not be built (0x%X)", route[part]); - return CommandCost(); - } - return cost; - } - // Bridge code - if ((AI_PATHFINDER_FLAG_BRIDGE & route_extra[part]) != 0) { - cost.AddCost(AiNew_Build_Bridge(c, route[part], route[part + 1], flag)); - PathFinderInfo->position++; - // TODO: problems! - if (CmdFailed(cost)) { - DEBUG(ai, 0, "[BuildPath] bridge could not be built (0x%X, 0x%X)", route[part], route[part + 1]); - return CommandCost(); - } - return cost; - } - - // Build normal road - // Keep it doing till we go an other way - // EnsureNoVehicleOnGround makes sure we don't build on a tile where a vehicle is. This way - // it will wait till the vehicle is gone.. - if (route_extra[part - 1] == 0 && route_extra[part] == 0 && (flag != DC_EXEC || EnsureNoVehicleOnGround(route[part]))) { - while (route_extra[part] == 0 && (flag != DC_EXEC || EnsureNoVehicleOnGround(route[part]))) { - // Get the current direction - dir = AiNew_GetRoadDirection(route[part - 1], route[part], route[part + 1]); - // Is it the same as the last one? - if (old_dir != -1 && old_dir != dir) break; - old_dir = dir; - // There is already some road, and it is a bridge.. don't build!!! - if (!IsTileType(route[part], MP_TUNNELBRIDGE)) { - // Build the tile - res = AI_DoCommand(route[part], dir, 0, flag | DC_NO_WATER, CMD_BUILD_ROAD); - // Currently, we ignore CMD_ERRORs! - if (CmdFailed(res) && flag == DC_EXEC && !IsTileType(route[part], MP_ROAD) && !EnsureNoVehicleOnGround(route[part])) { - // Problem.. let's just abort it all! - DEBUG(ai, 0, "[BuidPath] route building failed at tile 0x%X, aborting", route[part]); - _companies_ainew[c->index].state = AI_STATE_NOTHING; - return CommandCost(); - } - - if (CmdSucceeded(res)) cost.AddCost(res); - } - // Go to the next tile - part++; - // Check if it is still in range.. - if (part >= PathFinderInfo->route_length - 1) break; - } - part--; - // We want to return the last position, so we go back one - } - if (!EnsureNoVehicleOnGround(route[part]) && flag == DC_EXEC) part--; - PathFinderInfo->position = part; - } - - return cost; -} - - -// This functions tries to find the best vehicle for this type of cargo -// It returns INVALID_ENGINE if not suitable engine is found -EngineID AiNew_PickVehicle(Company *c) -{ - if (_companies_ainew[c->index].tbt == AI_TRAIN) { - // Not supported yet - return INVALID_ENGINE; - } else { - EngineID best_veh_index = INVALID_ENGINE; - int32 best_veh_rating = 0; - const Engine *e; - - /* Loop through all road vehicles */ - FOR_ALL_ENGINES_OF_TYPE(e, VEH_ROAD) { - EngineID i = e->index; - const RoadVehicleInfo *rvi = &e->u.road; - - /* Skip vehicles which can't take our cargo type */ - if (rvi->cargo_type != _companies_ainew[c->index].cargo && !CanRefitTo(i, _companies_ainew[c->index].cargo)) continue; - - /* Skip trams */ - if (HasBit(EngInfo(i)->misc_flags, EF_ROAD_TRAM)) continue; - - // Is it availiable? - // Also, check if the reliability of the vehicle is above the AI_VEHICLE_MIN_RELIABILTY - if (!HasBit(e->company_avail, _current_company) || e->reliability * 100 < AI_VEHICLE_MIN_RELIABILTY << 16) continue; - - /* Rate and compare the engine by speed & capacity */ - int rating = rvi->max_speed * rvi->capacity; - if (rating <= best_veh_rating) continue; - - // Can we build it? - CommandCost ret = AI_DoCommand(0, i, 0, DC_QUERY_COST, CMD_BUILD_ROAD_VEH); - if (CmdFailed(ret)) continue; - - best_veh_rating = rating; - best_veh_index = i; - } - - return best_veh_index; - } -} - - -void CcAI(bool success, TileIndex tile, uint32 p1, uint32 p2) -{ - Company *c = GetCompany(_current_company); - - if (success) { - _companies_ainew[c->index].state = AI_STATE_GIVE_ORDERS; - _companies_ainew[c->index].veh_id = _new_vehicle_id; - - if (GetVehicle(_companies_ainew[c->index].veh_id)->cargo_type != _companies_ainew[c->index].cargo) { - /* Cargo type doesn't match, so refit it */ - if (CmdFailed(DoCommand(tile, _companies_ainew[c->index].veh_id, _companies_ainew[c->index].cargo, DC_EXEC, CMD_REFIT_ROAD_VEH))) { - /* Refit failed, so sell the vehicle */ - DoCommand(tile, _companies_ainew[c->index].veh_id, 0, DC_EXEC, CMD_SELL_ROAD_VEH); - _companies_ainew[c->index].state = AI_STATE_NOTHING; - } - } - } else { - /* XXX this should be handled more gracefully */ - _companies_ainew[c->index].state = AI_STATE_NOTHING; - } -} - - -// Builds the best vehicle possible -CommandCost AiNew_Build_Vehicle(Company *c, TileIndex tile, byte flag) -{ - EngineID i = AiNew_PickVehicle(c); - - if (i == INVALID_ENGINE) return CMD_ERROR; - if (_companies_ainew[c->index].tbt == AI_TRAIN) return CMD_ERROR; - - if (flag & DC_EXEC) { - return AI_DoCommandCc(tile, i, 0, flag, CMD_BUILD_ROAD_VEH, CcAI); - } else { - return AI_DoCommand(tile, i, 0, flag, CMD_BUILD_ROAD_VEH); - } -} - -CommandCost AiNew_Build_Depot(Company *c, TileIndex tile, DiagDirection direction, byte flag) -{ - CommandCost ret, ret2; - if (_companies_ainew[c->index].tbt == AI_TRAIN) { - return AI_DoCommand(tile, 0, direction, flag | DC_AUTO | DC_NO_WATER, CMD_BUILD_TRAIN_DEPOT); - } else { - ret = AI_DoCommand(tile, direction, 0, flag | DC_AUTO | DC_NO_WATER, CMD_BUILD_ROAD_DEPOT); - if (CmdFailed(ret2)) return ret; - // Try to build the road from the depot - ret2 = AI_DoCommand(tile + TileOffsByDiagDir(direction), DiagDirToRoadBits(ReverseDiagDir(direction)), 0, flag | DC_AUTO | DC_NO_WATER, CMD_BUILD_ROAD); - // If it fails, ignore it.. - if (CmdFailed(ret2)) return ret; - ret.AddCost(ret2); - return ret; - } -} diff --git a/src/ai/trolly/pathfinder.cpp b/src/ai/trolly/pathfinder.cpp deleted file mode 100644 index 0e08ddf77..000000000 --- a/src/ai/trolly/pathfinder.cpp +++ /dev/null @@ -1,482 +0,0 @@ -/* $Id$ */ - -/** @file pathfinder.cpp Pathfinder support for the trolly AI. */ - -#include "../../stdafx.h" -#include "../../openttd.h" -#include "../../bridge_map.h" -#include "../../debug.h" -#include "../../command_func.h" -#include "trolly.h" -#include "../../tunnel_map.h" -#include "../../bridge.h" -#include "../../tunnelbridge_map.h" -#include "../ai.h" -#include "../../variables.h" -#include "../../company_base.h" -#include "../../company_func.h" -#include "../../tunnelbridge.h" - - -#define TEST_STATION_NO_DIR 0xFF - -// Tests if a station can be build on the given spot -// TODO: make it train compatible -static bool TestCanBuildStationHere(TileIndex tile, byte dir) -{ - Company *c = GetCompany(_current_company); - - if (dir == TEST_STATION_NO_DIR) { - CommandCost ret; - // TODO: currently we only allow spots that can be access from al 4 directions... - // should be fixed!!! - for (dir = 0; dir < 4; dir++) { - ret = AiNew_Build_Station(c, _companies_ainew[c->index].tbt, tile, 1, 1, dir, DC_QUERY_COST); - if (CmdSucceeded(ret)) return true; - } - return false; - } - - // return true if command succeeded, so the inverse of CmdFailed() - return CmdSucceeded(AiNew_Build_Station(c, _companies_ainew[c->index].tbt, tile, 1, 1, dir, DC_QUERY_COST)); -} - - -static bool IsRoad(TileIndex tile) -{ - return - // MP_ROAD, but not a road depot? - (IsTileType(tile, MP_ROAD) && !IsRoadDepot(tile)) || - (IsTileType(tile, MP_TUNNELBRIDGE) && GetTunnelBridgeTransportType(tile) == TRANSPORT_ROAD); -} - - -// Checks if a tile 'a' is between the tiles 'b' and 'c' -#define TILES_BETWEEN(a, b, c) (TileX(a) >= TileX(b) && TileX(a) <= TileX(c) && TileY(a) >= TileY(b) && TileY(a) <= TileY(c)) - - -// Check if the current tile is in our end-area -static int32 AyStar_AiPathFinder_EndNodeCheck(AyStar *aystar, OpenListNode *current) -{ - const Ai_PathFinderInfo *PathFinderInfo = (Ai_PathFinderInfo*)aystar->user_target; - - // It is not allowed to have a station on the end of a bridge or tunnel ;) - if (current->path.node.user_data[0] != 0) return AYSTAR_DONE; - if (TILES_BETWEEN(current->path.node.tile, PathFinderInfo->end_tile_tl, PathFinderInfo->end_tile_br)) - if (IsTileType(current->path.node.tile, MP_CLEAR) || IsTileType(current->path.node.tile, MP_TREES)) - if (current->path.parent == NULL || TestCanBuildStationHere(current->path.node.tile, AiNew_GetDirection(current->path.parent->node.tile, current->path.node.tile))) - return AYSTAR_FOUND_END_NODE; - - return AYSTAR_DONE; -} - - -// Calculates the hash -// Currently it is a 10 bit hash, so the hash array has a max depth of 6 bits (so 64) -static uint AiPathFinder_Hash(uint key1, uint key2) -{ - return (TileX(key1) & 0x1F) + ((TileY(key1) & 0x1F) << 5); -} - - -// Clear the memory of all the things -static void AyStar_AiPathFinder_Free(AyStar *aystar) -{ - AyStarMain_Free(aystar); - delete aystar; -} - - -static int32 AyStar_AiPathFinder_CalculateG(AyStar *aystar, AyStarNode *current, OpenListNode *parent); -static int32 AyStar_AiPathFinder_CalculateH(AyStar *aystar, AyStarNode *current, OpenListNode *parent); -static void AyStar_AiPathFinder_FoundEndNode(AyStar *aystar, OpenListNode *current); -static void AyStar_AiPathFinder_GetNeighbours(AyStar *aystar, OpenListNode *current); - - -// This creates the AiPathFinder -AyStar *new_AyStar_AiPathFinder(int max_tiles_around, Ai_PathFinderInfo *PathFinderInfo) -{ - PathNode start_node; - uint x; - uint y; - // Create AyStar - AyStar *result = new AyStar(); - init_AyStar(result, AiPathFinder_Hash, 1 << 10); - // Set the function pointers - result->CalculateG = AyStar_AiPathFinder_CalculateG; - result->CalculateH = AyStar_AiPathFinder_CalculateH; - result->EndNodeCheck = AyStar_AiPathFinder_EndNodeCheck; - result->FoundEndNode = AyStar_AiPathFinder_FoundEndNode; - result->GetNeighbours = AyStar_AiPathFinder_GetNeighbours; - - result->free = AyStar_AiPathFinder_Free; - - // Set some information - result->loops_per_tick = AI_PATHFINDER_LOOPS_PER_TICK; - result->max_path_cost = 0; - result->max_search_nodes = AI_PATHFINDER_MAX_SEARCH_NODES; - - // Set the user_data to the PathFinderInfo - result->user_target = PathFinderInfo; - - // Set the start node - start_node.parent = NULL; - start_node.node.direction = INVALID_TRACKDIR; - start_node.node.user_data[0] = 0; - - // Now we add all the starting tiles - for (x = TileX(PathFinderInfo->start_tile_tl); x <= TileX(PathFinderInfo->start_tile_br); x++) { - for (y = TileY(PathFinderInfo->start_tile_tl); y <= TileY(PathFinderInfo->start_tile_br); y++) { - start_node.node.tile = TileXY(x, y); - result->addstart(result, &start_node.node, 0); - } - } - - return result; -} - - -// To reuse AyStar we sometimes have to clean all the memory -void clean_AyStar_AiPathFinder(AyStar *aystar, Ai_PathFinderInfo *PathFinderInfo) -{ - PathNode start_node; - uint x; - uint y; - - aystar->clear(aystar); - - // Set the user_data to the PathFinderInfo - aystar->user_target = PathFinderInfo; - - // Set the start node - start_node.parent = NULL; - start_node.node.direction = INVALID_TRACKDIR; - start_node.node.user_data[0] = 0; - start_node.node.tile = PathFinderInfo->start_tile_tl; - - // Now we add all the starting tiles - for (x = TileX(PathFinderInfo->start_tile_tl); x <= TileX(PathFinderInfo->start_tile_br); x++) { - for (y = TileY(PathFinderInfo->start_tile_tl); y <= TileY(PathFinderInfo->start_tile_br); y++) { - TileIndex tile = TileXY(x, y); - - if (!IsTileType(tile, MP_CLEAR) && !IsTileType(tile, MP_TREES)) continue; - if (!TestCanBuildStationHere(tile, TEST_STATION_NO_DIR)) continue; - start_node.node.tile = tile; - aystar->addstart(aystar, &start_node.node, 0); - } - } -} - - -// The h-value, simple calculation -static int32 AyStar_AiPathFinder_CalculateH(AyStar *aystar, AyStarNode *current, OpenListNode *parent) -{ - const Ai_PathFinderInfo *PathFinderInfo = (Ai_PathFinderInfo*)aystar->user_target; - int r, r2; - - if (PathFinderInfo->end_direction != AI_PATHFINDER_NO_DIRECTION) { - // The station is pointing to a direction, add a tile towards that direction, so the H-value is more accurate - r = DistanceManhattan(current->tile, PathFinderInfo->end_tile_tl + TileOffsByDiagDir(PathFinderInfo->end_direction)); - r2 = DistanceManhattan(current->tile, PathFinderInfo->end_tile_br + TileOffsByDiagDir(PathFinderInfo->end_direction)); - } else { - // No direction, so just get the fastest route to the station - r = DistanceManhattan(current->tile, PathFinderInfo->end_tile_tl); - r2 = DistanceManhattan(current->tile, PathFinderInfo->end_tile_br); - } - // See if the bottomright is faster than the topleft.. - if (r2 < r) r = r2; - return r * AI_PATHFINDER_H_MULTIPLER; -} - - -// We found the end.. let's get the route back and put it in an array -static void AyStar_AiPathFinder_FoundEndNode(AyStar *aystar, OpenListNode *current) -{ - Ai_PathFinderInfo *PathFinderInfo = (Ai_PathFinderInfo*)aystar->user_target; - uint i = 0; - PathNode *parent = ¤t->path; - - do { - PathFinderInfo->route_extra[i] = parent->node.user_data[0]; - PathFinderInfo->route[i++] = parent->node.tile; - if (i > lengthof(PathFinderInfo->route)) { - // We ran out of space for the PathFinder - DEBUG(ai, 0, "No more space in pathfinder route[] array"); - PathFinderInfo->route_length = -1; // -1 indicates out of space - return; - } - parent = parent->parent; - } while (parent != NULL); - PathFinderInfo->route_length = i; - DEBUG(ai, 1, "Found route of %d nodes long in %d nodes of searching", i, Hash_Size(&aystar->ClosedListHash)); -} - - -// What tiles are around us. -static void AyStar_AiPathFinder_GetNeighbours(AyStar *aystar, OpenListNode *current) -{ - CommandCost ret; - int dir; - - Ai_PathFinderInfo *PathFinderInfo = (Ai_PathFinderInfo*)aystar->user_target; - - aystar->num_neighbours = 0; - - // Go through all surrounding tiles and check if they are within the limits - for (DiagDirection i = DIAGDIR_BEGIN; i < DIAGDIR_END; i++) { - TileIndex ctile = current->path.node.tile; // Current tile - TileIndex atile = ctile + TileOffsByDiagDir(i); // Adjacent tile - - if (TileX(atile) > 1 && TileX(atile) < MapMaxX() - 1 && - TileY(atile) > 1 && TileY(atile) < MapMaxY() - 1) { - // We also directly test if the current tile can connect to this tile.. - // We do this simply by just building the tile! - - // If the next step is a bridge, we have to enter it the right way - if (!PathFinderInfo->rail_or_road && IsRoad(atile)) { - if (IsTileType(atile, MP_TUNNELBRIDGE)) { - if (GetTunnelBridgeDirection(atile) != i) continue; - } - } - - if ((AI_PATHFINDER_FLAG_BRIDGE & current->path.node.user_data[0]) != 0 || - (AI_PATHFINDER_FLAG_TUNNEL & current->path.node.user_data[0]) != 0) { - // We are a bridge/tunnel, how cool!! - // This means we can only point forward.. get the direction from the user_data - if ((uint)i != (current->path.node.user_data[0] >> 8)) continue; - } - dir = 0; - - // First, check if we have a parent - if (current->path.parent == NULL && current->path.node.user_data[0] == 0) { - // If not, this means we are at the starting station - if (PathFinderInfo->start_direction != AI_PATHFINDER_NO_DIRECTION) { - // We do need a direction? - if (AiNew_GetDirection(ctile, atile) != PathFinderInfo->start_direction) { - // We are not pointing the right way, invalid tile - continue; - } - } - } else if (current->path.node.user_data[0] == 0) { - if (PathFinderInfo->rail_or_road) { - // Rail check - dir = AiNew_GetRailDirection(current->path.parent->node.tile, ctile, atile); - ret = AI_DoCommand(ctile, 0, dir, DC_AUTO | DC_NO_WATER, CMD_BUILD_SINGLE_RAIL); - if (CmdFailed(ret)) continue; -#ifdef AI_PATHFINDER_NO_90DEGREES_TURN - if (current->path.parent->parent != NULL) { - // Check if we don't make a 90degree curve - int dir1 = AiNew_GetRailDirection(current->path.parent->parent->node.tile, current->path.parent->node.tile, ctile); - if (_illegal_curves[dir1] == dir || _illegal_curves[dir] == dir1) { - continue; - } - } -#endif - } else { - // Road check - dir = AiNew_GetRoadDirection(current->path.parent->node.tile, ctile, atile); - if (IsRoad(ctile)) { - if (IsTileType(ctile, MP_TUNNELBRIDGE)) { - // We have a bridge, how nicely! We should mark it... - dir = 0; - } else { - // It already has road.. check if we miss any bits! - if ((GetAnyRoadBits(ctile, ROADTYPE_ROAD) & dir) != dir) { - // We do miss some pieces :( - dir &= ~GetAnyRoadBits(ctile, ROADTYPE_ROAD); - } else { - dir = 0; - } - } - } - // Only destruct things if it is MP_CLEAR of MP_TREES - if (dir != 0) { - ret = AI_DoCommand(ctile, dir, 0, DC_AUTO | DC_NO_WATER, CMD_BUILD_ROAD); - if (CmdFailed(ret)) continue; - } - } - } - - // The tile can be connected - aystar->neighbours[aystar->num_neighbours].tile = atile; - aystar->neighbours[aystar->num_neighbours].user_data[0] = 0; - aystar->neighbours[aystar->num_neighbours++].direction = INVALID_TRACKDIR; - } - } - - // Next step, check for bridges and tunnels - if (current->path.parent != NULL && current->path.node.user_data[0] == 0) { - // First we get the dir from this tile and his parent - DiagDirection dir = AiNew_GetDirection(current->path.parent->node.tile, current->path.node.tile); - // It means we can only walk with the track, so the bridge has to be in the same direction - TileIndex tile = current->path.node.tile; - TileIndex new_tile = tile; - Slope tileh = GetTileSlope(tile, NULL); - - // Bridges can only be build on land that is not flat - // And if there is a road or rail blocking - if (tileh != SLOPE_FLAT || - (PathFinderInfo->rail_or_road && IsTileType(tile + TileOffsByDiagDir(dir), MP_ROAD)) || - (!PathFinderInfo->rail_or_road && IsTileType(tile + TileOffsByDiagDir(dir), MP_RAILWAY))) { - for (;;) { - new_tile += TileOffsByDiagDir(dir); - - // Precheck, is the length allowed? - if (!CheckBridge_Stuff(0, GetTunnelBridgeLength(tile, new_tile))) break; - - // Check if we hit the station-tile.. we don't like that! - if (TILES_BETWEEN(new_tile, PathFinderInfo->end_tile_tl, PathFinderInfo->end_tile_br)) break; - - // Try building the bridge.. - ret = AI_DoCommand(tile, new_tile, (0 << 8) + (MAX_BRIDGES / 2), DC_AUTO, CMD_BUILD_BRIDGE); - if (CmdFailed(ret)) continue; - // We can build a bridge here.. add him to the neighbours - aystar->neighbours[aystar->num_neighbours].tile = new_tile; - aystar->neighbours[aystar->num_neighbours].user_data[0] = AI_PATHFINDER_FLAG_BRIDGE + (dir << 8); - aystar->neighbours[aystar->num_neighbours++].direction = INVALID_TRACKDIR; - // We can only have 12 neighbours, and we need 1 left for tunnels - if (aystar->num_neighbours == 11) break; - } - } - - // Next, check for tunnels! - // Tunnels can only be built on slopes corresponding to the direction - // For now, we check both sides for this tile.. terraforming gives fuzzy result - if (tileh == InclinedSlope(dir)) { - // Now simply check if a tunnel can be build - ret = AI_DoCommand(tile, (PathFinderInfo->rail_or_road ? 0 : 0x200), 0, DC_AUTO, CMD_BUILD_TUNNEL); - tileh = GetTileSlope(_build_tunnel_endtile, NULL); - if (CmdSucceeded(ret) && IsInclinedSlope(tileh)) { - aystar->neighbours[aystar->num_neighbours].tile = _build_tunnel_endtile; - aystar->neighbours[aystar->num_neighbours].user_data[0] = AI_PATHFINDER_FLAG_TUNNEL + (dir << 8); - aystar->neighbours[aystar->num_neighbours++].direction = INVALID_TRACKDIR; - } - } - } -} - - -extern Foundation GetRailFoundation(Slope tileh, TrackBits bits); // XXX function declaration in .c -extern Foundation GetRoadFoundation(Slope tileh, RoadBits bits); // XXX function declaration in .c - -// The most important function: it calculates the g-value -static int32 AyStar_AiPathFinder_CalculateG(AyStar *aystar, AyStarNode *current, OpenListNode *parent) -{ - Ai_PathFinderInfo *PathFinderInfo = (Ai_PathFinderInfo*)aystar->user_target; - int res = 0; - Slope tileh = GetTileSlope(current->tile, NULL); - Slope parent_tileh = GetTileSlope(parent->path.node.tile, NULL); - - // Check if we hit the end-tile - if (TILES_BETWEEN(current->tile, PathFinderInfo->end_tile_tl, PathFinderInfo->end_tile_br)) { - // We are at the end-tile, check if we had a direction or something... - if (PathFinderInfo->end_direction != AI_PATHFINDER_NO_DIRECTION && AiNew_GetDirection(current->tile, parent->path.node.tile) != PathFinderInfo->end_direction) { - // We are not pointing the right way, invalid tile - return AYSTAR_INVALID_NODE; - } - // If it was valid, drop out.. we don't build on the endtile - return 0; - } - - // Give everything a small penalty - res += AI_PATHFINDER_PENALTY; - - if (!PathFinderInfo->rail_or_road) { - // Road has the lovely advantage it can use other road... check if - // the current tile is road, and if so, give a good bonus - if (IsRoad(current->tile)) { - res -= AI_PATHFINDER_ROAD_ALREADY_EXISTS_BONUS; - } - } - - // We should give a penalty when the tile is going up or down.. this is one way to do so! - // Too bad we have to count it from the parent.. but that is not so bad. - // We also dislike long routes on slopes, since they do not look too realistic - // when there is a flat land all around, they are more expensive to build, and - // especially they essentially block the ability to connect or cross the road - // from one side. - if (parent_tileh != SLOPE_FLAT && parent->path.parent != NULL) { - // Skip if the tile was from a bridge or tunnel - if (parent->path.node.user_data[0] == 0 && current->user_data[0] == 0) { - if (PathFinderInfo->rail_or_road) { - Foundation f = GetRailFoundation(parent_tileh, (TrackBits)(1 << AiNew_GetRailDirection(parent->path.parent->node.tile, parent->path.node.tile, current->tile))); - if (IsInclinedFoundation(f) || (!IsFoundation(f) && IsInclinedSlope(parent_tileh))) { - res += AI_PATHFINDER_TILE_GOES_UP_PENALTY; - } else { - res += AI_PATHFINDER_FOUNDATION_PENALTY; - } - } else { - if (!IsRoad(parent->path.node.tile) || !IsTileType(parent->path.node.tile, MP_TUNNELBRIDGE)) { - Foundation f = GetRoadFoundation(parent_tileh, (RoadBits)AiNew_GetRoadDirection(parent->path.parent->node.tile, parent->path.node.tile, current->tile)); - if (IsInclinedFoundation(f) || (!IsFoundation(f) && IsInclinedSlope(parent_tileh))) { - res += AI_PATHFINDER_TILE_GOES_UP_PENALTY; - } else { - res += AI_PATHFINDER_FOUNDATION_PENALTY; - } - } - } - } - } - - // Are we part of a tunnel? - if ((AI_PATHFINDER_FLAG_TUNNEL & current->user_data[0]) != 0) { - int r; - // Tunnels are very expensive when build on long routes.. - // Ironicly, we are using BridgeCode here ;) - r = AI_PATHFINDER_TUNNEL_PENALTY * GetTunnelBridgeLength(current->tile, parent->path.node.tile); - res += r + (r >> 8); - } - - // Are we part of a bridge? - if ((AI_PATHFINDER_FLAG_BRIDGE & current->user_data[0]) != 0) { - // That means for every length a penalty - res += AI_PATHFINDER_BRIDGE_PENALTY * GetTunnelBridgeLength(current->tile, parent->path.node.tile); - // Check if we are going up or down, first for the starting point - // In user_data[0] is at the 8th bit the direction - if (!HasBridgeFlatRamp(parent_tileh, (Axis)((current->user_data[0] >> 8) & 1))) res += AI_PATHFINDER_BRIDGE_GOES_UP_PENALTY; - // Second for the end point - if (!HasBridgeFlatRamp(tileh, (Axis)((current->user_data[0] >> 8) & 1))) res += AI_PATHFINDER_BRIDGE_GOES_UP_PENALTY; - } - - // To prevent the AI from taking the fastest way in tiles, but not the fastest way - // in speed, we have to give a good penalty to direction changing - // This way, we get almost the fastest way in tiles, and a very good speed on the track - if (!PathFinderInfo->rail_or_road) { - if (parent->path.parent != NULL && - AiNew_GetDirection(current->tile, parent->path.node.tile) != AiNew_GetDirection(parent->path.node.tile, parent->path.parent->node.tile)) { - // When road exists, we don't like turning, but its free, so don't be to piggy about it - if (IsRoad(parent->path.node.tile)) { - res += AI_PATHFINDER_DIRECTION_CHANGE_ON_EXISTING_ROAD_PENALTY; - } else { - res += AI_PATHFINDER_DIRECTION_CHANGE_PENALTY; - } - } - } else { - // For rail we have 1 exeption: diagonal rail.. - // So we fetch 2 raildirection. That of the current one, and of the one before that - if (parent->path.parent != NULL && parent->path.parent->parent != NULL) { - int dir1 = AiNew_GetRailDirection(parent->path.parent->node.tile, parent->path.node.tile, current->tile); - int dir2 = AiNew_GetRailDirection(parent->path.parent->parent->node.tile, parent->path.parent->node.tile, parent->path.node.tile); - // First, see if we are on diagonal path, that is better than straight path - if (dir1 > 1) res -= AI_PATHFINDER_DIAGONAL_BONUS; - - // First see if they are different - if (dir1 != dir2) { - // dir 2 and 3 are 1 diagonal track, and 4 and 5. - if (!(((dir1 == 2 || dir1 == 3) && (dir2 == 2 || dir2 == 3)) || ((dir1 == 4 || dir1 == 5) && (dir2 == 4 || dir2 == 5)))) { - // It is not, so we changed of direction - res += AI_PATHFINDER_DIRECTION_CHANGE_PENALTY; - } - if (parent->path.parent->parent->parent != NULL) { - int dir3 = AiNew_GetRailDirection(parent->path.parent->parent->parent->node.tile, parent->path.parent->parent->node.tile, parent->path.parent->node.tile); - // Check if we changed 3 tiles of direction in 3 tiles.. bad!!! - if ((dir1 == 0 || dir1 == 1) && dir2 > 1 && (dir3 == 0 || dir3 == 1)) { - res += AI_PATHFINDER_CURVE_PENALTY; - } - } - } - } - } - - return (res < 0) ? 0 : res; -} diff --git a/src/ai/trolly/shared.cpp b/src/ai/trolly/shared.cpp deleted file mode 100644 index 1183f0371..000000000 --- a/src/ai/trolly/shared.cpp +++ /dev/null @@ -1,121 +0,0 @@ -/* $Id$ */ - -/** @file shared.cpp Shared functions for the trolly AI. */ - -#include "../../stdafx.h" -#include "../../openttd.h" -#include "../../debug.h" -#include "../../map_func.h" -#include "../../vehicle_base.h" -#include "../../company_base.h" -#include "trolly.h" - -int AiNew_GetRailDirection(TileIndex tile_a, TileIndex tile_b, TileIndex tile_c) -{ - // 0 = vert - // 1 = horz - // 2 = dig up-left - // 3 = dig down-right - // 4 = dig down-left - // 5 = dig up-right - - uint x1 = TileX(tile_a); - uint x2 = TileX(tile_b); - uint x3 = TileX(tile_c); - - uint y1 = TileY(tile_a); - uint y2 = TileY(tile_b); - uint y3 = TileY(tile_c); - - if (y1 == y2 && y2 == y3) return 0; - if (x1 == x2 && x2 == x3) return 1; - if (y2 > y1) return x2 > x3 ? 2 : 4; - if (x2 > x1) return y2 > y3 ? 2 : 5; - if (y1 > y2) return x2 > x3 ? 5 : 3; - if (x1 > x2) return y2 > y3 ? 4 : 3; - - return 0; -} - -int AiNew_GetRoadDirection(TileIndex tile_a, TileIndex tile_b, TileIndex tile_c) -{ - int x1, x2, x3; - int y1, y2, y3; - int r; - - x1 = TileX(tile_a); - x2 = TileX(tile_b); - x3 = TileX(tile_c); - - y1 = TileY(tile_a); - y2 = TileY(tile_b); - y3 = TileY(tile_c); - - r = 0; - - if (x1 < x2) r += 8; - if (y1 < y2) r += 1; - if (x1 > x2) r += 2; - if (y1 > y2) r += 4; - - if (x2 < x3) r += 2; - if (y2 < y3) r += 4; - if (x2 > x3) r += 8; - if (y2 > y3) r += 1; - - return r; -} - -// Get's the direction between 2 tiles seen from tile_a -DiagDirection AiNew_GetDirection(TileIndex tile_a, TileIndex tile_b) -{ - if (TileY(tile_a) < TileY(tile_b)) return DIAGDIR_SE; - if (TileY(tile_a) > TileY(tile_b)) return DIAGDIR_NW; - if (TileX(tile_a) < TileX(tile_b)) return DIAGDIR_SW; - return DIAGDIR_NE; -} - - -// This functions looks up if this vehicle is special for this AI -// and returns his flag -uint AiNew_GetSpecialVehicleFlag(Company *c, Vehicle *v) -{ - uint i; - - for (i = 0; i < AI_MAX_SPECIAL_VEHICLES; i++) { - if (_companies_ainew[c->index].special_vehicles[i].veh_id == v->index) { - return _companies_ainew[c->index].special_vehicles[i].flag; - } - } - - // Not found :( - return 0; -} - - -bool AiNew_SetSpecialVehicleFlag(Company *c, Vehicle *v, uint flag) -{ - int new_id = -1; - uint i; - - for (i = 0; i < AI_MAX_SPECIAL_VEHICLES; i++) { - if (_companies_ainew[c->index].special_vehicles[i].veh_id == v->index) { - _companies_ainew[c->index].special_vehicles[i].flag |= flag; - return true; - } - if (new_id == -1 && - _companies_ainew[c->index].special_vehicles[i].veh_id == 0 && - _companies_ainew[c->index].special_vehicles[i].flag == 0) { - new_id = i; - } - } - - // Out of special_vehicle spots :s - if (new_id == -1) { - DEBUG(ai, 1, "special_vehicles list is too small"); - return false; - } - _companies_ainew[c->index].special_vehicles[new_id].veh_id = v->index; - _companies_ainew[c->index].special_vehicles[new_id].flag = flag; - return true; -} diff --git a/src/ai/trolly/trolly.cpp b/src/ai/trolly/trolly.cpp deleted file mode 100644 index 6b874ec0d..000000000 --- a/src/ai/trolly/trolly.cpp +++ /dev/null @@ -1,1348 +0,0 @@ -/* $Id$ */ - -/** - * @file trolly.cpp Implementation of the trolly AI. - * - * This AI was created as a direct reaction to the big demand for some good AIs - * in OTTD. Too bad it never left alpha-stage, and it is considered dead in its - * current form. - * By the time of writing this, we, the creator of this AI and a good friend of - * mine, are designing a whole new AI-system that allows us to create AIs - * easier and without all the fuzz we encountered while I was working on this - * AI. By the time that system is finished, you can expect that this AI will - * dissapear, because it is pretty obselete and bad programmed. - * - * Meanwhile I wish you all much fun with this AI; if you are interested as - * AI-developer in this AI, I advise you not stare too long to some code, some - * things in here really are... strange ;) But in either way: enjoy :) - * - * -- TrueLight :: 2005-09-01 - */ - -#include "../../stdafx.h" -#include "../../openttd.h" -#include "../../debug.h" -#include "../../road_map.h" -#include "../../station_map.h" -#include "../../command_func.h" -#include "trolly.h" -#include "../../depot_base.h" -#include "../../town.h" -#include "../../industry.h" -#include "../../station_base.h" -#include "../../engine_func.h" -#include "../../engine_base.h" -#include "../../gui.h" -#include "../../vehicle_base.h" -#include "../../vehicle_func.h" -#include "../../date_func.h" -#include "../ai.h" -#include "../../company_base.h" -#include "../../company_func.h" - -#include "table/strings.h" - -CompanyAiNew _companies_ainew[MAX_COMPANIES]; - -// This function is called after StartUp. It is the init of an AI -static void AiNew_State_FirstTime(Company *c) -{ - // This assert is used to protect those function from misuse - // You have quickly a small mistake in the state-array - // With that, everything would go wrong. Finding that, is almost impossible - // With this assert, that problem can never happen. - assert(_companies_ainew[c->index].state == AI_STATE_FIRST_TIME); - // We first have to init some things - - if (_current_company == 1) ShowErrorMessage(INVALID_STRING_ID, TEMP_AI_IN_PROGRESS, 0, 0); - - // The PathFinder (AyStar) - // TODO: Maybe when an AI goes bankrupt, this is de-init - // or when coming from a savegame.. should be checked out! - _companies_ainew[c->index].path_info.start_tile_tl = 0; - _companies_ainew[c->index].path_info.start_tile_br = 0; - _companies_ainew[c->index].path_info.end_tile_tl = 0; - _companies_ainew[c->index].path_info.end_tile_br = 0; - _companies_ainew[c->index].pathfinder = new_AyStar_AiPathFinder(12, &_companies_ainew[c->index].path_info); - - _companies_ainew[c->index].idle = 0; - _companies_ainew[c->index].last_vehiclecheck_date = _date; - - // We ALWAYS start with a bus route.. just some basic money ;) - _companies_ainew[c->index].action = AI_ACTION_BUS_ROUTE; - - // Let's popup the news, and after that, start building.. - _companies_ainew[c->index].state = AI_STATE_WAKE_UP; -} - - -// This function just waste some time -// It keeps it more real. The AI can build on such tempo no normal user -// can ever keep up with that. The competitor_speed already delays a bit -// but after the AI finished a track it really needs to go to sleep. -// -// Let's say, we sleep between one and three days if the AI is put on Very Fast. -// This means that on Very Slow it will be between 16 and 48 days.. slow enough? -static void AiNew_State_Nothing(Company *c) -{ - assert(_companies_ainew[c->index].state == AI_STATE_NOTHING); - // If we are done idling, start over again - if (_companies_ainew[c->index].idle == 0) _companies_ainew[c->index].idle = AI_RandomRange(DAY_TICKS * 2) + DAY_TICKS; - if (--_companies_ainew[c->index].idle == 0) { - // We are done idling.. what you say? Let's do something! - // I mean.. the next tick ;) - _companies_ainew[c->index].state = AI_STATE_WAKE_UP; - } -} - - -// This function picks out a task we are going to do. -// Currently supported: -// - Make new route -// - Check route -// - Build HQ -static void AiNew_State_WakeUp(Company *c) -{ - assert(_companies_ainew[c->index].state == AI_STATE_WAKE_UP); - // First, check if we have a HQ - if (c->location_of_HQ == INVALID_TILE) { - // We have no HQ yet, build one on a random place - // Random till we found a place for it! - // TODO: this should not be on a random place.. - AiNew_Build_CompanyHQ(c, AI_Random() % MapSize()); - // Enough for now, but we want to come back here the next time - // so we do not change any status - return; - } - - Money money = c->money - AI_MINIMUM_MONEY; - - // Let's pick an action! - if (_companies_ainew[c->index].action == AI_ACTION_NONE) { - int r = AI_Random() & 0xFF; - if (c->current_loan > 0 && - c->old_economy[1].income > AI_MINIMUM_INCOME_FOR_LOAN && - r < 10) { - _companies_ainew[c->index].action = AI_ACTION_REPAY_LOAN; - } else if (_companies_ainew[c->index].last_vehiclecheck_date + AI_DAYS_BETWEEN_VEHICLE_CHECKS < _date) { - // Check all vehicles once in a while - _companies_ainew[c->index].action = AI_ACTION_CHECK_ALL_VEHICLES; - _companies_ainew[c->index].last_vehiclecheck_date = _date; - } else if (r < 100 && !_settings_game.ai.ai_disable_veh_roadveh) { - // Do we have any spots for road-vehicles left open? - if (GetFreeUnitNumber(VEH_ROAD) <= _settings_game.vehicle.max_roadveh) { - if (r < 85) { - _companies_ainew[c->index].action = AI_ACTION_TRUCK_ROUTE; - } else { - _companies_ainew[c->index].action = AI_ACTION_BUS_ROUTE; - } - } -#if 0 - } else if (r < 200 && !_settings_game.ai.ai_disable_veh_train) { - if (GetFreeUnitNumber(VEH_TRAIN) <= _settings_game.vehicle.max_trains) { - _companies_ainew[c->index].action = AI_ACTION_TRAIN_ROUTE; - } -#endif - } - - _companies_ainew[c->index].counter = 0; - } - - if (_companies_ainew[c->index].counter++ > AI_MAX_TRIES_FOR_SAME_ROUTE) { - _companies_ainew[c->index].action = AI_ACTION_NONE; - return; - } - - if (_settings_game.ai.ai_disable_veh_roadveh && ( - _companies_ainew[c->index].action == AI_ACTION_BUS_ROUTE || - _companies_ainew[c->index].action == AI_ACTION_TRUCK_ROUTE - )) { - _companies_ainew[c->index].action = AI_ACTION_NONE; - return; - } - - if (_companies_ainew[c->index].action == AI_ACTION_REPAY_LOAN && - money > AI_MINIMUM_LOAN_REPAY_MONEY) { - // We start repaying some money.. - _companies_ainew[c->index].state = AI_STATE_REPAY_MONEY; - return; - } - - if (_companies_ainew[c->index].action == AI_ACTION_CHECK_ALL_VEHICLES) { - _companies_ainew[c->index].state = AI_STATE_CHECK_ALL_VEHICLES; - return; - } - - // It is useless to start finding a route if we don't have enough money - // to build the route anyway.. - if (_companies_ainew[c->index].action == AI_ACTION_BUS_ROUTE && - money > AI_MINIMUM_BUS_ROUTE_MONEY) { - if (GetFreeUnitNumber(VEH_ROAD) > _settings_game.vehicle.max_roadveh) { - _companies_ainew[c->index].action = AI_ACTION_NONE; - return; - } - _companies_ainew[c->index].cargo = AI_NEED_CARGO; - _companies_ainew[c->index].state = AI_STATE_LOCATE_ROUTE; - _companies_ainew[c->index].tbt = AI_BUS; // Bus-route - return; - } - if (_companies_ainew[c->index].action == AI_ACTION_TRUCK_ROUTE && - money > AI_MINIMUM_TRUCK_ROUTE_MONEY) { - if (GetFreeUnitNumber(VEH_ROAD) > _settings_game.vehicle.max_roadveh) { - _companies_ainew[c->index].action = AI_ACTION_NONE; - return; - } - _companies_ainew[c->index].cargo = AI_NEED_CARGO; - _companies_ainew[c->index].last_id = 0; - _companies_ainew[c->index].state = AI_STATE_LOCATE_ROUTE; - _companies_ainew[c->index].tbt = AI_TRUCK; - return; - } - - _companies_ainew[c->index].state = AI_STATE_NOTHING; -} - - -static void AiNew_State_ActionDone(Company *c) -{ - _companies_ainew[c->index].action = AI_ACTION_NONE; - _companies_ainew[c->index].state = AI_STATE_NOTHING; -} - - -// Check if a city or industry is good enough to start a route there -static bool AiNew_Check_City_or_Industry(Company *c, int ic, byte type) -{ - if (type == AI_CITY) { - const Town *t = GetTown(ic); - const Station *st; - uint count = 0; - int j = 0; - - // We don't like roadconstructions, don't even true such a city - if (t->road_build_months != 0) return false; - - // Check if the rating in a city is high enough - // If not, take a chance if we want to continue - if (t->ratings[_current_company] < 0 && AI_CHANCE16(1, 4)) return false; - - if (t->max_pass - t->act_pass < AI_CHECKCITY_NEEDED_CARGO && !AI_CHANCE16(1, AI_CHECKCITY_CITY_CHANCE)) return false; - - // Check if we have build a station in this town the last 6 months - // else we don't do it. This is done, because stat updates can be slow - // and sometimes it takes up to 4 months before the stats are corectly. - // This way we don't get 12 busstations in one city of 100 population ;) - FOR_ALL_STATIONS(st) { - // Do we own it? - if (st->owner == _current_company) { - // Are we talking busses? - if (_companies_ainew[c->index].tbt == AI_BUS && (FACIL_BUS_STOP & st->facilities) != FACIL_BUS_STOP) continue; - // Is it the same city as we are in now? - if (st->town != t) continue; - // When was this station build? - if (_date - st->build_date < AI_CHECKCITY_DATE_BETWEEN) return false; - // Cound the amount of stations in this city that we own - count++; - } else { - // We do not own it, request some info about the station - // we want to know if this station gets the same good. If so, - // we want to know its rating. If it is too high, we are not going - // to build there - if (!st->goods[CT_PASSENGERS].last_speed) continue; - // Is it around our city - if (DistanceManhattan(st->xy, t->xy) > 10) continue; - // It does take this cargo.. what is his rating? - if (st->goods[CT_PASSENGERS].rating < AI_CHECKCITY_CARGO_RATING) continue; - j++; - // When this is the first station, we build a second with no problem ;) - if (j == 1) continue; - // The rating is high.. second station... - // a little chance that we still continue - // But if there are 3 stations of this size, we never go on... - if (j == 2 && AI_CHANCE16(1, AI_CHECKCITY_CARGO_RATING_CHANCE)) continue; - // We don't like this station :( - return false; - } - } - - // We are about to add one... - count++; - // Check if we the city can provide enough cargo for this amount of stations.. - if (count * AI_CHECKCITY_CARGO_PER_STATION > t->max_pass) return false; - - // All check are okay, so we can build here! - return true; - } - if (type == AI_INDUSTRY) { - const Industry *i = GetIndustry(ic); - const Station *st; - int count = 0; - int j = 0; - - if (i->town != NULL && i->town->ratings[_current_company] < 0 && AI_CHANCE16(1, 4)) return false; - - // No limits on delevering stations! - // Or for industry that does not give anything yet - if (i->produced_cargo[0] == CT_INVALID || i->last_month_production[0] == 0) return true; - - if (i->last_month_production[0] - i->last_month_transported[0] < AI_CHECKCITY_NEEDED_CARGO) return false; - - // Check if we have build a station in this town the last 6 months - // else we don't do it. This is done, because stat updates can be slow - // and sometimes it takes up to 4 months before the stats are corectly. - FOR_ALL_STATIONS(st) { - // Do we own it? - if (st->owner == _current_company) { - // Are we talking trucks? - if (_companies_ainew[c->index].tbt == AI_TRUCK && (FACIL_TRUCK_STOP & st->facilities) != FACIL_TRUCK_STOP) continue; - // Is it the same city as we are in now? - if (st->town != i->town) continue; - // When was this station build? - if (_date - st->build_date < AI_CHECKCITY_DATE_BETWEEN) return false; - // Cound the amount of stations in this city that we own - count++; - } else { - // We do not own it, request some info about the station - // we want to know if this station gets the same good. If so, - // we want to know its rating. If it is too high, we are not going - // to build there - if (i->produced_cargo[0] == CT_INVALID) continue; - // It does not take this cargo - if (!st->goods[i->produced_cargo[0]].last_speed) continue; - // Is it around our industry - if (DistanceManhattan(st->xy, i->xy) > 5) continue; - // It does take this cargo.. what is his rating? - if (st->goods[i->produced_cargo[0]].rating < AI_CHECKCITY_CARGO_RATING) continue; - j++; - // The rating is high.. a little chance that we still continue - // But if there are 2 stations of this size, we never go on... - if (j == 1 && AI_CHANCE16(1, AI_CHECKCITY_CARGO_RATING_CHANCE)) continue; - // We don't like this station :( - return false; - } - } - - // We are about to add one... - count++; - // Check if we the city can provide enough cargo for this amount of stations.. - if (count * AI_CHECKCITY_CARGO_PER_STATION > i->last_month_production[0]) return false; - - // All check are okay, so we can build here! - return true; - } - - return true; -} - - -// This functions tries to locate a good route -static void AiNew_State_LocateRoute(Company *c) -{ - assert(_companies_ainew[c->index].state == AI_STATE_LOCATE_ROUTE); - // For now, we only support PASSENGERS, CITY and BUSSES - - // We don't have a route yet - if (_companies_ainew[c->index].cargo == AI_NEED_CARGO) { - _companies_ainew[c->index].new_cost = 0; // No cost yet - _companies_ainew[c->index].temp = -1; - // Reset the counter - _companies_ainew[c->index].counter = 0; - - _companies_ainew[c->index].from_ic = -1; - _companies_ainew[c->index].to_ic = -1; - if (_companies_ainew[c->index].tbt == AI_BUS) { - // For now we only have a passenger route - _companies_ainew[c->index].cargo = CT_PASSENGERS; - - // Find a route to cities - _companies_ainew[c->index].from_type = AI_CITY; - _companies_ainew[c->index].to_type = AI_CITY; - } else if (_companies_ainew[c->index].tbt == AI_TRUCK) { - _companies_ainew[c->index].cargo = AI_NO_CARGO; - - _companies_ainew[c->index].from_type = AI_INDUSTRY; - _companies_ainew[c->index].to_type = AI_INDUSTRY; - } - - // Now we are doing initing, we wait one tick - return; - } - - // Increase the counter and abort if it is taking too long! - _companies_ainew[c->index].counter++; - if (_companies_ainew[c->index].counter > AI_LOCATE_ROUTE_MAX_COUNTER) { - // Switch back to doing nothing! - _companies_ainew[c->index].state = AI_STATE_NOTHING; - return; - } - - // We are going to locate a city from where we are going to connect - if (_companies_ainew[c->index].from_ic == -1) { - if (_companies_ainew[c->index].temp == -1) { - // First, we pick a random spot to search from - if (_companies_ainew[c->index].from_type == AI_CITY) { - _companies_ainew[c->index].temp = AI_RandomRange(GetMaxTownIndex() + 1); - } else { - _companies_ainew[c->index].temp = AI_RandomRange(GetMaxIndustryIndex() + 1); - } - } - - if (!AiNew_Check_City_or_Industry(c, _companies_ainew[c->index].temp, _companies_ainew[c->index].from_type)) { - // It was not a valid city - // increase the temp with one, and return. We will come back later here - // to try again - _companies_ainew[c->index].temp++; - if (_companies_ainew[c->index].from_type == AI_CITY) { - if (_companies_ainew[c->index].temp > GetMaxTownIndex()) _companies_ainew[c->index].temp = 0; - } else { - if (_companies_ainew[c->index].temp > GetMaxIndustryIndex()) _companies_ainew[c->index].temp = 0; - } - - // Don't do an attempt if we are trying the same id as the last time... - if (_companies_ainew[c->index].last_id == _companies_ainew[c->index].temp) return; - _companies_ainew[c->index].last_id = _companies_ainew[c->index].temp; - - return; - } - - // We found a good city/industry, save the data of it - _companies_ainew[c->index].from_ic = _companies_ainew[c->index].temp; - - // Start the next tick with finding a to-city - _companies_ainew[c->index].temp = -1; - return; - } - - // Find a to-city - if (_companies_ainew[c->index].temp == -1) { - // First, we pick a random spot to search to - if (_companies_ainew[c->index].to_type == AI_CITY) { - _companies_ainew[c->index].temp = AI_RandomRange(GetMaxTownIndex() + 1); - } else { - _companies_ainew[c->index].temp = AI_RandomRange(GetMaxIndustryIndex() + 1); - } - } - - // The same city is not allowed - // Also check if the city is valid - if (_companies_ainew[c->index].temp != _companies_ainew[c->index].from_ic && AiNew_Check_City_or_Industry(c, _companies_ainew[c->index].temp, _companies_ainew[c->index].to_type)) { - // Maybe it is valid.. - - /* We need to know if they are not to far apart from eachother.. - * We do that by checking how much cargo we have to move and how long the - * route is. - */ - - if (_companies_ainew[c->index].from_type == AI_CITY && _companies_ainew[c->index].tbt == AI_BUS) { - const Town *town_from = GetTown(_companies_ainew[c->index].from_ic); - const Town *town_temp = GetTown(_companies_ainew[c->index].temp); - uint distance = DistanceManhattan(town_from->xy, town_temp->xy); - int max_cargo; - - max_cargo = town_from->max_pass + town_temp->max_pass; - max_cargo -= town_from->act_pass + town_temp->act_pass; - - // max_cargo is now the amount of cargo we can move between the two cities - // If it is more than the distance, we allow it - if (distance <= max_cargo * AI_LOCATEROUTE_BUS_CARGO_DISTANCE) { - // We found a good city/industry, save the data of it - _companies_ainew[c->index].to_ic = _companies_ainew[c->index].temp; - _companies_ainew[c->index].state = AI_STATE_FIND_STATION; - - DEBUG(ai, 1, "[LocateRoute] found bus-route of %d tiles long (from %d to %d)", - distance, - _companies_ainew[c->index].from_ic, - _companies_ainew[c->index].temp - ); - - _companies_ainew[c->index].from_tile = 0; - _companies_ainew[c->index].to_tile = 0; - - return; - } - } else if (_companies_ainew[c->index].tbt == AI_TRUCK) { - const Industry *ind_from = GetIndustry(_companies_ainew[c->index].from_ic); - const Industry *ind_temp = GetIndustry(_companies_ainew[c->index].temp); - bool found = false; - int max_cargo = 0; - uint i; - - // TODO: in max_cargo, also check other cargo (beside [0]) - // First we check if the from_ic produces cargo that this ic accepts - if (ind_from->produced_cargo[0] != CT_INVALID && ind_from->last_month_production[0] != 0) { - for (i = 0; i < lengthof(ind_temp->accepts_cargo); i++) { - if (ind_temp->accepts_cargo[i] == CT_INVALID) break; - if (ind_from->produced_cargo[0] == ind_temp->accepts_cargo[i]) { - // Found a compatible industry - max_cargo = ind_from->last_month_production[0] - ind_from->last_month_transported[0]; - found = true; - _companies_ainew[c->index].from_deliver = true; - _companies_ainew[c->index].to_deliver = false; - break; - } - } - } - if (!found && ind_temp->produced_cargo[0] != CT_INVALID && ind_temp->last_month_production[0] != 0) { - // If not check if the current ic produces cargo that the from_ic accepts - for (i = 0; i < lengthof(ind_from->accepts_cargo); i++) { - if (ind_from->accepts_cargo[i] == CT_INVALID) break; - if (ind_from->produced_cargo[0] == ind_from->accepts_cargo[i]) { - // Found a compatbiel industry - found = true; - max_cargo = ind_temp->last_month_production[0] - ind_temp->last_month_transported[0]; - _companies_ainew[c->index].from_deliver = false; - _companies_ainew[c->index].to_deliver = true; - break; - } - } - } - if (found) { - // Yeah, they are compatible!!! - // Check the length against the amount of goods - uint distance = DistanceManhattan(ind_from->xy, ind_temp->xy); - - if (distance > AI_LOCATEROUTE_TRUCK_MIN_DISTANCE && - distance <= max_cargo * AI_LOCATEROUTE_TRUCK_CARGO_DISTANCE) { - _companies_ainew[c->index].to_ic = _companies_ainew[c->index].temp; - if (_companies_ainew[c->index].from_deliver) { - _companies_ainew[c->index].cargo = ind_from->produced_cargo[0]; - } else { - _companies_ainew[c->index].cargo = ind_temp->produced_cargo[0]; - } - _companies_ainew[c->index].state = AI_STATE_FIND_STATION; - - DEBUG(ai, 1, "[LocateRoute] found truck-route of %d tiles long (from %d to %d)", - distance, - _companies_ainew[c->index].from_ic, - _companies_ainew[c->index].temp - ); - - _companies_ainew[c->index].from_tile = 0; - _companies_ainew[c->index].to_tile = 0; - - return; - } - } - } - } - - // It was not a valid city - // increase the temp with one, and return. We will come back later here - // to try again - _companies_ainew[c->index].temp++; - if (_companies_ainew[c->index].to_type == AI_CITY) { - if (_companies_ainew[c->index].temp > GetMaxTownIndex()) _companies_ainew[c->index].temp = 0; - } else { - if (_companies_ainew[c->index].temp > GetMaxIndustryIndex()) _companies_ainew[c->index].temp = 0; - } - - // Don't do an attempt if we are trying the same id as the last time... - if (_companies_ainew[c->index].last_id == _companies_ainew[c->index].temp) return; - _companies_ainew[c->index].last_id = _companies_ainew[c->index].temp; -} - - -// Check if there are not more than a certain amount of vehicles pointed to a certain -// station. This to prevent 10 busses going to one station, which gives... problems ;) -static bool AiNew_CheckVehicleStation(Company *c, Station *st) -{ - int count = 0; - Vehicle *v; - - // Also check if we don't have already a lot of busses to this city... - FOR_ALL_VEHICLES(v) { - if (v->owner == _current_company) { - const Order *order; - - FOR_VEHICLE_ORDERS(v, order) { - if (order->IsType(OT_GOTO_STATION) && GetStation(order->GetDestination()) == st) { - // This vehicle has this city in its list - count++; - } - } - } - } - - if (count > AI_CHECK_MAX_VEHICLE_PER_STATION) return false; - return true; -} - -// This function finds a good spot for a station -static void AiNew_State_FindStation(Company *c) -{ - TileIndex tile; - Station *st; - int count = 0; - EngineID i; - TileIndex new_tile = 0; - DiagDirection direction = DIAGDIR_NE; - Town *town = NULL; - assert(_companies_ainew[c->index].state == AI_STATE_FIND_STATION); - - if (_companies_ainew[c->index].from_tile == 0) { - // First we scan for a station in the from-city - if (_companies_ainew[c->index].from_type == AI_CITY) { - town = GetTown(_companies_ainew[c->index].from_ic); - tile = town->xy; - } else { - tile = GetIndustry(_companies_ainew[c->index].from_ic)->xy; - } - } else if (_companies_ainew[c->index].to_tile == 0) { - // Second we scan for a station in the to-city - if (_companies_ainew[c->index].to_type == AI_CITY) { - town = GetTown(_companies_ainew[c->index].to_ic); - tile = town->xy; - } else { - tile = GetIndustry(_companies_ainew[c->index].to_ic)->xy; - } - } else { - // Unsupported request - // Go to FIND_PATH - _companies_ainew[c->index].temp = -1; - _companies_ainew[c->index].state = AI_STATE_FIND_PATH; - return; - } - - // First, we are going to look at the stations that already exist inside the city - // If there is enough cargo left in the station, we take that station - // If that is not possible, and there are more than 2 stations in the city, abort - i = AiNew_PickVehicle(c); - // Euhmz, this should not happen _EVER_ - // Quit finding a route... - if (i == INVALID_ENGINE) { - _companies_ainew[c->index].state = AI_STATE_NOTHING; - return; - } - - FOR_ALL_STATIONS(st) { - if (st->owner == _current_company) { - if (_companies_ainew[c->index].tbt == AI_BUS && (FACIL_BUS_STOP & st->facilities) == FACIL_BUS_STOP) { - if (st->town == town) { - // Check how much cargo there is left in the station - if ((int)st->goods[_companies_ainew[c->index].cargo].cargo.Count() > RoadVehInfo(i)->capacity * AI_STATION_REUSE_MULTIPLER) { - if (AiNew_CheckVehicleStation(c, st)) { - // We did found a station that was good enough! - new_tile = st->xy; - direction = GetRoadStopDir(st->xy); - break; - } - } - count++; - } - } - } - } - // We are going to add a new station... - if (new_tile == 0) count++; - // No more than 2 stations allowed in a city - // This is because only the best 2 stations of one cargo do get any cargo - if (count > 2) { - _companies_ainew[c->index].state = AI_STATE_NOTHING; - return; - } - - if (new_tile == 0 && _companies_ainew[c->index].tbt == AI_BUS) { - uint x, y, i = 0; - CommandCost r; - uint best; - uint accepts[NUM_CARGO]; - TileIndex found_spot[AI_FINDSTATION_TILE_RANGE*AI_FINDSTATION_TILE_RANGE * 4]; - uint found_best[AI_FINDSTATION_TILE_RANGE*AI_FINDSTATION_TILE_RANGE * 4]; - // To find a good spot we scan a range from the center, a get the point - // where we get the most cargo and where it is buildable. - // TODO: also check for station of myself and make sure we are not - // taking eachothers passengers away (bad result when it does not) - for (x = TileX(tile) - AI_FINDSTATION_TILE_RANGE; x <= TileX(tile) + AI_FINDSTATION_TILE_RANGE; x++) { - for (y = TileY(tile) - AI_FINDSTATION_TILE_RANGE; y <= TileY(tile) + AI_FINDSTATION_TILE_RANGE; y++) { - new_tile = TileXY(x, y); - if (IsTileType(new_tile, MP_CLEAR) || IsTileType(new_tile, MP_TREES)) { - // This tile we can build on! - // Check acceptance - // XXX - Get the catchment area - GetAcceptanceAroundTiles(accepts, new_tile, 1, 1, 4); - // >> 3 == 0 means no cargo - if (accepts[_companies_ainew[c->index].cargo] >> 3 == 0) continue; - // See if we can build the station - r = AiNew_Build_Station(c, _companies_ainew[c->index].tbt, new_tile, 0, 0, 0, DC_QUERY_COST); - if (CmdFailed(r)) continue; - // We can build it, so add it to found_spot - found_spot[i] = new_tile; - found_best[i++] = accepts[_companies_ainew[c->index].cargo]; - } - } - } - - // If i is still zero, we did not find anything - if (i == 0) { - _companies_ainew[c->index].state = AI_STATE_NOTHING; - return; - } - - // Go through all the found_best and check which has the highest value - best = 0; - new_tile = 0; - - for (x = 0; x < i; x++) { - if (found_best[x] > best || - (found_best[x] == best && DistanceManhattan(tile, new_tile) > DistanceManhattan(tile, found_spot[x]))) { - new_tile = found_spot[x]; - best = found_best[x]; - } - } - - // See how much it is going to cost us... - r = AiNew_Build_Station(c, _companies_ainew[c->index].tbt, new_tile, 0, 0, 0, DC_QUERY_COST); - _companies_ainew[c->index].new_cost += r.GetCost(); - - direction = (DiagDirection)AI_PATHFINDER_NO_DIRECTION; - } else if (new_tile == 0 && _companies_ainew[c->index].tbt == AI_TRUCK) { - // Truck station locater works differently.. a station can be on any place - // as long as it is in range. So we give back code AI_STATION_RANGE - // so the pathfinder routine can work it out! - new_tile = AI_STATION_RANGE; - direction = (DiagDirection)AI_PATHFINDER_NO_DIRECTION; - } - - if (_companies_ainew[c->index].from_tile == 0) { - _companies_ainew[c->index].from_tile = new_tile; - _companies_ainew[c->index].from_direction = direction; - // Now we found thisone, go in for to_tile - return; - } else if (_companies_ainew[c->index].to_tile == 0) { - _companies_ainew[c->index].to_tile = new_tile; - _companies_ainew[c->index].to_direction = direction; - // K, done placing stations! - _companies_ainew[c->index].temp = -1; - _companies_ainew[c->index].state = AI_STATE_FIND_PATH; - return; - } -} - - -// We try to find a path between 2 points -static void AiNew_State_FindPath(Company *c) -{ - int r; - assert(_companies_ainew[c->index].state == AI_STATE_FIND_PATH); - - // First time, init some data - if (_companies_ainew[c->index].temp == -1) { - // Init path_info - if (_companies_ainew[c->index].from_tile == AI_STATION_RANGE) { - const Industry *i = GetIndustry(_companies_ainew[c->index].from_ic); - - // For truck routes we take a range around the industry - _companies_ainew[c->index].path_info.start_tile_tl = i->xy - TileDiffXY(1, 1); - _companies_ainew[c->index].path_info.start_tile_br = i->xy + TileDiffXY(i->width + 1, i->height + 1); - _companies_ainew[c->index].path_info.start_direction = _companies_ainew[c->index].from_direction; - } else { - _companies_ainew[c->index].path_info.start_tile_tl = _companies_ainew[c->index].from_tile; - _companies_ainew[c->index].path_info.start_tile_br = _companies_ainew[c->index].from_tile; - _companies_ainew[c->index].path_info.start_direction = _companies_ainew[c->index].from_direction; - } - - if (_companies_ainew[c->index].to_tile == AI_STATION_RANGE) { - const Industry *i = GetIndustry(_companies_ainew[c->index].to_ic); - - _companies_ainew[c->index].path_info.end_tile_tl = i->xy - TileDiffXY(1, 1); - _companies_ainew[c->index].path_info.end_tile_br = i->xy + TileDiffXY(i->width + 1, i->height + 1); - _companies_ainew[c->index].path_info.end_direction = _companies_ainew[c->index].to_direction; - } else { - _companies_ainew[c->index].path_info.end_tile_tl = _companies_ainew[c->index].to_tile; - _companies_ainew[c->index].path_info.end_tile_br = _companies_ainew[c->index].to_tile; - _companies_ainew[c->index].path_info.end_direction = _companies_ainew[c->index].to_direction; - } - - _companies_ainew[c->index].path_info.rail_or_road = (_companies_ainew[c->index].tbt == AI_TRAIN); - - // First, clean the pathfinder with our new begin and endpoints - clean_AyStar_AiPathFinder(_companies_ainew[c->index].pathfinder, &_companies_ainew[c->index].path_info); - - _companies_ainew[c->index].temp = 0; - } - - // Start the pathfinder - r = _companies_ainew[c->index].pathfinder->main(_companies_ainew[c->index].pathfinder); - switch (r) { - case AYSTAR_NO_PATH: - DEBUG(ai, 1, "No route found by pathfinder"); - // Start all over again - _companies_ainew[c->index].state = AI_STATE_NOTHING; - break; - - case AYSTAR_FOUND_END_NODE: // We found the end-point - _companies_ainew[c->index].temp = -1; - _companies_ainew[c->index].state = AI_STATE_FIND_DEPOT; - break; - - // In any other case, we are still busy finding the route - default: break; - } -} - - -// This function tries to locate a good place for a depot! -static void AiNew_State_FindDepot(Company *c) -{ - // To place the depot, we walk through the route, and if we find a lovely spot (MP_CLEAR, MP_TREES), we place it there.. - // Simple, easy, works! - // To make the depot stand in the middle of the route, we start from the center.. - // But first we walk through the route see if we can find a depot that is ours - // this keeps things nice ;) - int g, i; - CommandCost r; - DiagDirection j; - TileIndex tile; - assert(_companies_ainew[c->index].state == AI_STATE_FIND_DEPOT); - - _companies_ainew[c->index].depot_tile = 0; - - for (i = 2; i < _companies_ainew[c->index].path_info.route_length - 2; i++) { - tile = _companies_ainew[c->index].path_info.route[i]; - for (j = DIAGDIR_BEGIN; j < DIAGDIR_END; j++) { - TileIndex t = tile + TileOffsByDiagDir(j); - - if (IsRoadDepotTile(t) && - IsTileOwner(t, _current_company) && - GetRoadDepotDirection(t) == ReverseDiagDir(j)) { - _companies_ainew[c->index].depot_tile = t; - _companies_ainew[c->index].depot_direction = ReverseDiagDir(j); - _companies_ainew[c->index].state = AI_STATE_VERIFY_ROUTE; - return; - } - } - } - - // This routine let depot finding start in the middle, and work his way to the stations - // It makes depot placing nicer :) - i = _companies_ainew[c->index].path_info.route_length / 2; - g = 1; - while (i > 1 && i < _companies_ainew[c->index].path_info.route_length - 2) { - i += g; - g *= -1; - (g < 0 ? g-- : g++); - - if (_companies_ainew[c->index].path_info.route_extra[i] != 0 || _companies_ainew[c->index].path_info.route_extra[i + 1] != 0) { - // Bridge or tunnel.. we can't place a depot there - continue; - } - - tile = _companies_ainew[c->index].path_info.route[i]; - - for (j = DIAGDIR_BEGIN; j < DIAGDIR_END; j++) { - TileIndex t = tile + TileOffsByDiagDir(j); - - // It may not be placed on the road/rail itself - // And because it is not build yet, we can't see it on the tile.. - // So check the surrounding tiles :) - if (t == _companies_ainew[c->index].path_info.route[i - 1] || - t == _companies_ainew[c->index].path_info.route[i + 1]) { - continue; - } - // Not around a bridge? - if (_companies_ainew[c->index].path_info.route_extra[i] != 0) continue; - if (IsTileType(tile, MP_TUNNELBRIDGE)) continue; - // Is the terrain clear? - if (IsTileType(t, MP_CLEAR) || IsTileType(t, MP_TREES)) { - // If the current tile is on a slope then we do not allow this - if (GetTileSlope(tile, NULL) != SLOPE_FLAT) continue; - // Check if everything went okay.. - r = AiNew_Build_Depot(c, t, ReverseDiagDir(j), 0); - if (CmdFailed(r)) continue; - // Found a spot! - _companies_ainew[c->index].new_cost += r.GetCost(); - _companies_ainew[c->index].depot_tile = t; - _companies_ainew[c->index].depot_direction = ReverseDiagDir(j); // Reverse direction - _companies_ainew[c->index].state = AI_STATE_VERIFY_ROUTE; - return; - } - } - } - - // Failed to find a depot? - _companies_ainew[c->index].state = AI_STATE_NOTHING; -} - - -// This function calculates how many vehicles there are needed on this -// traject. -// It works pretty simple: get the length, see how much we move around -// and hussle that, and you know how many vehicles there are needed. -// It returns the cost for the vehicles -static int AiNew_HowManyVehicles(Company *c) -{ - if (_companies_ainew[c->index].tbt == AI_BUS) { - // For bus-routes we look at the time before we are back in the station - EngineID i; - int length, tiles_a_day; - int amount; - i = AiNew_PickVehicle(c); - if (i == INVALID_ENGINE) return 0; - // Passenger run.. how long is the route? - length = _companies_ainew[c->index].path_info.route_length; - // Calculating tiles a day a vehicle moves is not easy.. this is how it must be done! - tiles_a_day = RoadVehInfo(i)->max_speed * DAY_TICKS / 256 / 16; - if (tiles_a_day == 0) tiles_a_day = 1; - // We want a vehicle in a station once a month at least, so, calculate it! - // (the * 2 is because we have 2 stations ;)) - amount = length * 2 * 2 / tiles_a_day / 30; - if (amount == 0) amount = 1; - return amount; - } else if (_companies_ainew[c->index].tbt == AI_TRUCK) { - // For truck-routes we look at the cargo - EngineID i; - int length, amount, tiles_a_day; - int max_cargo; - i = AiNew_PickVehicle(c); - if (i == INVALID_ENGINE) return 0; - // Passenger run.. how long is the route? - length = _companies_ainew[c->index].path_info.route_length; - // Calculating tiles a day a vehicle moves is not easy.. this is how it must be done! - tiles_a_day = RoadVehInfo(i)->max_speed * DAY_TICKS / 256 / 16; - if (tiles_a_day == 0) tiles_a_day = 1; - if (_companies_ainew[c->index].from_deliver) { - max_cargo = GetIndustry(_companies_ainew[c->index].from_ic)->last_month_production[0]; - } else { - max_cargo = GetIndustry(_companies_ainew[c->index].to_ic)->last_month_production[0]; - } - - // This is because moving 60% is more than we can dream of! - max_cargo *= 6; - max_cargo /= 10; - // We want all the cargo to be gone in a month.. so, we know the cargo it delivers - // we know what the vehicle takes with him, and we know the time it takes him - // to get back here.. now let's do some math! - amount = 2 * length * max_cargo / tiles_a_day / 30 / RoadVehInfo(i)->capacity; - amount += 1; - return amount; - } else { - // Currently not supported - return 0; - } -} - - -// This function checks: -// - If the route went okay -// - Calculates the amount of money needed to build the route -// - Calculates how much vehicles needed for the route -static void AiNew_State_VerifyRoute(Company *c) -{ - int res, i; - assert(_companies_ainew[c->index].state == AI_STATE_VERIFY_ROUTE); - - // Let's calculate the cost of the path.. - // new_cost already contains the cost of the stations - _companies_ainew[c->index].path_info.position = -1; - - do { - _companies_ainew[c->index].path_info.position++; - _companies_ainew[c->index].new_cost += AiNew_Build_RoutePart(c, &_companies_ainew[c->index].path_info, DC_QUERY_COST).GetCost(); - } while (_companies_ainew[c->index].path_info.position != -2); - - // Now we know the price of build station + path. Now check how many vehicles - // we need and what the price for that will be - res = AiNew_HowManyVehicles(c); - // If res == 0, no vehicle was found, or an other problem did occour - if (res == 0) { - _companies_ainew[c->index].state = AI_STATE_NOTHING; - return; - } - _companies_ainew[c->index].amount_veh = res; - _companies_ainew[c->index].cur_veh = 0; - - // Check how much it it going to cost us.. - for (i = 0; i < res; i++) { - _companies_ainew[c->index].new_cost += AiNew_Build_Vehicle(c, 0, DC_QUERY_COST).GetCost(); - } - - // Now we know how much the route is going to cost us - // Check if we have enough money for it! - if (_companies_ainew[c->index].new_cost > c->money - AI_MINIMUM_MONEY) { - // Too bad.. - DEBUG(ai, 1, "Insufficient funds to build route (%" OTTD_PRINTF64 "d)", (int64)_companies_ainew[c->index].new_cost); - _companies_ainew[c->index].state = AI_STATE_NOTHING; - return; - } - - // Now we can build the route, check the direction of the stations! - if (_companies_ainew[c->index].from_direction == AI_PATHFINDER_NO_DIRECTION) { - _companies_ainew[c->index].from_direction = AiNew_GetDirection(_companies_ainew[c->index].path_info.route[_companies_ainew[c->index].path_info.route_length - 1], _companies_ainew[c->index].path_info.route[_companies_ainew[c->index].path_info.route_length - 2]); - } - if (_companies_ainew[c->index].to_direction == AI_PATHFINDER_NO_DIRECTION) { - _companies_ainew[c->index].to_direction = AiNew_GetDirection(_companies_ainew[c->index].path_info.route[0], _companies_ainew[c->index].path_info.route[1]); - } - if (_companies_ainew[c->index].from_tile == AI_STATION_RANGE) - _companies_ainew[c->index].from_tile = _companies_ainew[c->index].path_info.route[_companies_ainew[c->index].path_info.route_length - 1]; - if (_companies_ainew[c->index].to_tile == AI_STATION_RANGE) - _companies_ainew[c->index].to_tile = _companies_ainew[c->index].path_info.route[0]; - - _companies_ainew[c->index].state = AI_STATE_BUILD_STATION; - _companies_ainew[c->index].temp = 0; - - DEBUG(ai, 1, "The route is set and buildable, building 0x%X to 0x%X...", _companies_ainew[c->index].from_tile, _companies_ainew[c->index].to_tile); -} - - -// Build the stations -static void AiNew_State_BuildStation(Company *c) -{ - CommandCost res; - assert(_companies_ainew[c->index].state == AI_STATE_BUILD_STATION); - if (_companies_ainew[c->index].temp == 0) { - if (!IsTileType(_companies_ainew[c->index].from_tile, MP_STATION)) - res = AiNew_Build_Station(c, _companies_ainew[c->index].tbt, _companies_ainew[c->index].from_tile, 0, 0, _companies_ainew[c->index].from_direction, DC_EXEC); - } else { - if (!IsTileType(_companies_ainew[c->index].to_tile, MP_STATION)) - res = AiNew_Build_Station(c, _companies_ainew[c->index].tbt, _companies_ainew[c->index].to_tile, 0, 0, _companies_ainew[c->index].to_direction, DC_EXEC); - _companies_ainew[c->index].state = AI_STATE_BUILD_PATH; - } - if (CmdFailed(res)) { - DEBUG(ai, 0, "[BuildStation] station could not be built (0x%X)", _companies_ainew[c->index].to_tile); - _companies_ainew[c->index].state = AI_STATE_NOTHING; - // If the first station _was_ build, destroy it - if (_companies_ainew[c->index].temp != 0) - AI_DoCommand(_companies_ainew[c->index].from_tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR); - return; - } - _companies_ainew[c->index].temp++; -} - - -// Build the path -static void AiNew_State_BuildPath(Company *c) -{ - assert(_companies_ainew[c->index].state == AI_STATE_BUILD_PATH); - // _companies_ainew[c->index].temp is set to -1 when this function is called for the first time - if (_companies_ainew[c->index].temp == -1) { - DEBUG(ai, 1, "Starting to build new path"); - // Init the counter - _companies_ainew[c->index].counter = (4 - _settings_game.difficulty.competitor_speed) * AI_BUILDPATH_PAUSE + 1; - // Set the position to the startingplace (-1 because in a minute we do ++) - _companies_ainew[c->index].path_info.position = -1; - // And don't do this again - _companies_ainew[c->index].temp = 0; - } - // Building goes very fast on normal rate, so we are going to slow it down.. - // By let the counter count from AI_BUILDPATH_PAUSE to 0, we have a nice way :) - if (--_companies_ainew[c->index].counter != 0) return; - _companies_ainew[c->index].counter = (4 - _settings_game.difficulty.competitor_speed) * AI_BUILDPATH_PAUSE + 1; - - // Increase the building position - _companies_ainew[c->index].path_info.position++; - // Build route - AiNew_Build_RoutePart(c, &_companies_ainew[c->index].path_info, DC_EXEC); - if (_companies_ainew[c->index].path_info.position == -2) { - // This means we are done building! - - if (_companies_ainew[c->index].tbt == AI_TRUCK && !_settings_game.pf.roadveh_queue) { - // If they not queue, they have to go up and down to try again at a station... - // We don't want that, so try building some road left or right of the station - DiagDirection dir1, dir2, dir3; - TileIndex tile; - CommandCost ret; - for (int i = 0; i < 2; i++) { - if (i == 0) { - tile = _companies_ainew[c->index].from_tile + TileOffsByDiagDir(_companies_ainew[c->index].from_direction); - dir1 = ChangeDiagDir(_companies_ainew[c->index].from_direction, DIAGDIRDIFF_90LEFT); - dir2 = ChangeDiagDir(_companies_ainew[c->index].from_direction, DIAGDIRDIFF_90RIGHT); - dir3 = _companies_ainew[c->index].from_direction; - } else { - tile = _companies_ainew[c->index].to_tile + TileOffsByDiagDir(_companies_ainew[c->index].to_direction); - dir1 = ChangeDiagDir(_companies_ainew[c->index].to_direction, DIAGDIRDIFF_90LEFT); - dir2 = ChangeDiagDir(_companies_ainew[c->index].to_direction, DIAGDIRDIFF_90RIGHT); - dir3 = _companies_ainew[c->index].to_direction; - } - - ret = AI_DoCommand(tile, DiagDirToRoadBits(ReverseDiagDir(dir1)), 0, DC_EXEC | DC_NO_WATER, CMD_BUILD_ROAD); - if (CmdSucceeded(ret)) { - TileIndexDiff offset = TileOffsByDiagDir(dir1); - if (IsTileType(tile + offset, MP_CLEAR) || IsTileType(tile + offset, MP_TREES)) { - ret = AI_DoCommand(tile + offset, AiNew_GetRoadDirection(tile, tile + offset, tile + offset + offset), 0, DC_EXEC | DC_NO_WATER, CMD_BUILD_ROAD); - if (CmdSucceeded(ret)) { - if (IsTileType(tile + offset + offset, MP_CLEAR) || IsTileType(tile + offset + offset, MP_TREES)) - AI_DoCommand(tile + offset + offset, AiNew_GetRoadDirection(tile + offset, tile + offset + offset, tile + offset + offset + offset), 0, DC_EXEC | DC_NO_WATER, CMD_BUILD_ROAD); - } - } - } - - ret = AI_DoCommand(tile, DiagDirToRoadBits(ReverseDiagDir(dir2)), 0, DC_EXEC | DC_NO_WATER, CMD_BUILD_ROAD); - if (CmdSucceeded(ret)) { - TileIndexDiff offset = TileOffsByDiagDir(dir2); - if (IsTileType(tile + offset, MP_CLEAR) || IsTileType(tile + offset, MP_TREES)) { - ret = AI_DoCommand(tile + offset, AiNew_GetRoadDirection(tile, tile + offset, tile + offset + offset), 0, DC_EXEC | DC_NO_WATER, CMD_BUILD_ROAD); - if (CmdSucceeded(ret)) { - if (IsTileType(tile + offset + offset, MP_CLEAR) || IsTileType(tile + offset + offset, MP_TREES)) - AI_DoCommand(tile + offset + offset, AiNew_GetRoadDirection(tile + offset, tile + offset + offset, tile + offset + offset + offset), 0, DC_EXEC | DC_NO_WATER, CMD_BUILD_ROAD); - } - } - } - - ret = AI_DoCommand(tile, DiagDirToRoadBits(dir3), 0, DC_EXEC | DC_NO_WATER, CMD_BUILD_ROAD); - if (CmdSucceeded(ret)) { - TileIndexDiff offset = TileOffsByDiagDir(dir3); - if (IsTileType(tile + offset, MP_CLEAR) || IsTileType(tile + offset, MP_TREES)) { - ret = AI_DoCommand(tile + offset, AiNew_GetRoadDirection(tile, tile + offset, tile + offset + offset), 0, DC_EXEC | DC_NO_WATER, CMD_BUILD_ROAD); - if (CmdSucceeded(ret)) { - if (IsTileType(tile + offset + offset, MP_CLEAR) || IsTileType(tile + offset + offset, MP_TREES)) - AI_DoCommand(tile + offset + offset, AiNew_GetRoadDirection(tile + offset, tile + offset + offset, tile + offset + offset + offset), 0, DC_EXEC | DC_NO_WATER, CMD_BUILD_ROAD); - } - } - } - } - } - - DEBUG(ai, 1, "Finished building path, cost: %" OTTD_PRINTF64 "d", (int64)_companies_ainew[c->index].new_cost); - _companies_ainew[c->index].state = AI_STATE_BUILD_DEPOT; - } -} - - -// Builds the depot -static void AiNew_State_BuildDepot(Company *c) -{ - CommandCost res; - assert(_companies_ainew[c->index].state == AI_STATE_BUILD_DEPOT); - - if (IsRoadDepotTile(_companies_ainew[c->index].depot_tile)) { - if (IsTileOwner(_companies_ainew[c->index].depot_tile, _current_company)) { - // The depot is already built - _companies_ainew[c->index].state = AI_STATE_BUILD_VEHICLE; - return; - } else { - // There is a depot, but not of our team! :( - _companies_ainew[c->index].state = AI_STATE_NOTHING; - return; - } - } - - // There is a bus on the tile we want to build road on... idle till he is gone! (BAD PERSON! :p) - if (!EnsureNoVehicleOnGround(_companies_ainew[c->index].depot_tile + TileOffsByDiagDir(_companies_ainew[c->index].depot_direction))) - return; - - res = AiNew_Build_Depot(c, _companies_ainew[c->index].depot_tile, _companies_ainew[c->index].depot_direction, DC_EXEC); - if (CmdFailed(res)) { - DEBUG(ai, 0, "[BuildDepot] depot could not be built (0x%X)", _companies_ainew[c->index].depot_tile); - _companies_ainew[c->index].state = AI_STATE_NOTHING; - return; - } - - _companies_ainew[c->index].state = AI_STATE_BUILD_VEHICLE; - _companies_ainew[c->index].idle = 10; - _companies_ainew[c->index].veh_main_id = INVALID_VEHICLE; -} - - -// Build vehicles -static void AiNew_State_BuildVehicle(Company *c) -{ - CommandCost res; - assert(_companies_ainew[c->index].state == AI_STATE_BUILD_VEHICLE); - - // Check if we need to build a vehicle - if (_companies_ainew[c->index].amount_veh == 0) { - // Nope, we are done! - // This means: we are all done! The route is open.. go back to NOTHING - // He will idle some time and it will all start over again.. :) - _companies_ainew[c->index].state = AI_STATE_ACTION_DONE; - return; - } - if (--_companies_ainew[c->index].idle != 0) return; - // It is realistic that the AI can only build 1 vehicle a day.. - // This makes sure of that! - _companies_ainew[c->index].idle = AI_BUILD_VEHICLE_TIME_BETWEEN; - - // Build the vehicle - res = AiNew_Build_Vehicle(c, _companies_ainew[c->index].depot_tile, DC_EXEC); - if (CmdFailed(res)) { - // This happens when the AI can't build any more vehicles! - _companies_ainew[c->index].state = AI_STATE_NOTHING; - return; - } - // Increase the current counter - _companies_ainew[c->index].cur_veh++; - // Decrease the total counter - _companies_ainew[c->index].amount_veh--; - // Go give some orders! - _companies_ainew[c->index].state = AI_STATE_WAIT_FOR_BUILD; -} - - -// Put the stations in the order list -static void AiNew_State_GiveOrders(Company *c) -{ - int idx; - Order order; - - assert(_companies_ainew[c->index].state == AI_STATE_GIVE_ORDERS); - - if (_companies_ainew[c->index].veh_main_id != INVALID_VEHICLE) { - AI_DoCommand(0, _companies_ainew[c->index].veh_id + (_companies_ainew[c->index].veh_main_id << 16), CO_SHARE, DC_EXEC, CMD_CLONE_ORDER); - - _companies_ainew[c->index].state = AI_STATE_START_VEHICLE; - return; - } else { - _companies_ainew[c->index].veh_main_id = _companies_ainew[c->index].veh_id; - } - - // Very handy for AI, goto depot.. but yeah, it needs to be activated ;) - if (_settings_game.order.gotodepot) { - idx = 0; - order.MakeGoToDepot(GetDepotByTile(_companies_ainew[c->index].depot_tile)->index, ODTFB_PART_OF_ORDERS); - AI_DoCommand(0, _companies_ainew[c->index].veh_id + (idx << 16), order.Pack(), DC_EXEC, CMD_INSERT_ORDER); - } - - idx = 0; - order.MakeGoToStation(GetStationIndex(_companies_ainew[c->index].to_tile)); - if (_companies_ainew[c->index].tbt == AI_TRUCK && _companies_ainew[c->index].to_deliver) order.SetLoadType(OLFB_FULL_LOAD); - AI_DoCommand(0, _companies_ainew[c->index].veh_id + (idx << 16), order.Pack(), DC_EXEC, CMD_INSERT_ORDER); - - idx = 0; - order.MakeGoToStation(GetStationIndex(_companies_ainew[c->index].from_tile)); - if (_companies_ainew[c->index].tbt == AI_TRUCK && _companies_ainew[c->index].from_deliver) order.SetLoadType(OLFB_FULL_LOAD); - AI_DoCommand(0, _companies_ainew[c->index].veh_id + (idx << 16), order.Pack(), DC_EXEC, CMD_INSERT_ORDER); - - // Start the engines! - _companies_ainew[c->index].state = AI_STATE_START_VEHICLE; -} - - -// Start the vehicle -static void AiNew_State_StartVehicle(Company *c) -{ - assert(_companies_ainew[c->index].state == AI_STATE_START_VEHICLE); - - // Skip the first order if it is a second vehicle - // This to make vehicles go different ways.. - if (_companies_ainew[c->index].cur_veh & 1) - AI_DoCommand(0, _companies_ainew[c->index].veh_id, 1, DC_EXEC, CMD_SKIP_TO_ORDER); - - // 3, 2, 1... go! (give START_STOP command ;)) - AI_DoCommand(0, _companies_ainew[c->index].veh_id, 0, DC_EXEC, CMD_START_STOP_VEHICLE); - // Try to build an other vehicle (that function will stop building when needed) - _companies_ainew[c->index].idle = 10; - _companies_ainew[c->index].state = AI_STATE_BUILD_VEHICLE; -} - - -// Repays money -static void AiNew_State_RepayMoney(Company *c) -{ - uint i; - - for (i = 0; i < AI_LOAN_REPAY; i++) { - AI_DoCommand(0, 0, 0, DC_EXEC, CMD_DECREASE_LOAN); - } - _companies_ainew[c->index].state = AI_STATE_ACTION_DONE; -} - - -static void AiNew_CheckVehicle(Company *c, Vehicle *v) -{ - // When a vehicle is under the 6 months, we don't check for anything - if (v->age < 180) return; - - // When a vehicle is older than 1 year, it should make money... - if (v->age > 360) { - // If both years together are not more than AI_MINIMUM_ROUTE_PROFIT, - // it is not worth the line I guess... - if (v->profit_last_year + v->profit_this_year < (Money)256 * AI_MINIMUM_ROUTE_PROFIT || - (v->reliability * 100 >> 16) < 40) { - // There is a possibility that the route is fucked up... - if (v->cargo.DaysInTransit() > AI_VEHICLE_LOST_DAYS) { - // The vehicle is lost.. check the route, or else, get the vehicle - // back to a depot - // TODO: make this piece of code - } - - - // We are already sending him back - if (AiNew_GetSpecialVehicleFlag(c, v) & AI_VEHICLEFLAG_SELL) { - if (v->type == VEH_ROAD && IsRoadDepotTile(v->tile) && - (v->vehstatus & VS_STOPPED)) { - // We are at the depot, sell the vehicle - AI_DoCommand(0, v->index, 0, DC_EXEC, CMD_SELL_ROAD_VEH); - } - return; - } - - if (!AiNew_SetSpecialVehicleFlag(c, v, AI_VEHICLEFLAG_SELL)) return; - { - CommandCost ret; - if (v->type == VEH_ROAD) - ret = AI_DoCommand(0, v->index, 0, DC_EXEC, CMD_SEND_ROADVEH_TO_DEPOT); - // This means we can not find a depot :s - // if (CmdFailed(ret)) - } - } - } -} - - -// Checks all vehicles if they are still valid and make money and stuff -static void AiNew_State_CheckAllVehicles(Company *c) -{ - Vehicle *v; - - FOR_ALL_VEHICLES(v) { - if (v->owner != c->index) continue; - // Currently, we only know how to handle road-vehicles - if (v->type != VEH_ROAD) continue; - - AiNew_CheckVehicle(c, v); - } - - _companies_ainew[c->index].state = AI_STATE_ACTION_DONE; -} - - -// Using the technique simular to the original AI -// Keeps things logical -// It really should be in the same order as the AI_STATE's are! -static AiNew_StateFunction * const _ainew_state[] = { - NULL, - AiNew_State_FirstTime, - AiNew_State_Nothing, - AiNew_State_WakeUp, - AiNew_State_LocateRoute, - AiNew_State_FindStation, - AiNew_State_FindPath, - AiNew_State_FindDepot, - AiNew_State_VerifyRoute, - AiNew_State_BuildStation, - AiNew_State_BuildPath, - AiNew_State_BuildDepot, - AiNew_State_BuildVehicle, - NULL, - AiNew_State_GiveOrders, - AiNew_State_StartVehicle, - AiNew_State_RepayMoney, - AiNew_State_CheckAllVehicles, - AiNew_State_ActionDone, - NULL, -}; - -static void AiNew_OnTick(Company *c) -{ - if (_ainew_state[_companies_ainew[c->index].state] != NULL) - _ainew_state[_companies_ainew[c->index].state](c); -} - - -void AiNewDoGameLoop(Company *c) -{ - if (_companies_ainew[c->index].state == AI_STATE_STARTUP) { - // The AI just got alive! - _companies_ainew[c->index].state = AI_STATE_FIRST_TIME; - _companies_ainew[c->index].tick = 0; - - // Only startup the AI - return; - } - - // We keep a ticker. We use it for competitor_speed - _companies_ainew[c->index].tick++; - - // If we come here, we can do a tick.. do so! - AiNew_OnTick(c); -} diff --git a/src/ai/trolly/trolly.h b/src/ai/trolly/trolly.h deleted file mode 100644 index 60180694c..000000000 --- a/src/ai/trolly/trolly.h +++ /dev/null @@ -1,344 +0,0 @@ -/* $Id$ */ - -/** @file trolly.h Functions/defines related to the trolly AI. */ - -#ifndef AI_TROLLY_H -#define AI_TROLLY_H - -#include "../../aystar.h" -#include "../../company_type.h" -#include "../../vehicle_type.h" -#include "../../date_type.h" -#include "../../engine_type.h" -#include "../../direction_type.h" - -/* - * These defines can be altered to change the behavoir of the AI - * - * WARNING: - * This can also alter the AI in a negative way. I will never claim these settings - * are perfect, but don't change them if you don't know what the effect is. - */ - -// How many times it the H multiplied. The higher, the more it will go straight to the -// end point. The lower, how more it will find the route with the lowest cost. -// also: the lower, the longer it takes before route is calculated.. -#define AI_PATHFINDER_H_MULTIPLER 100 - -// How many loops may AyStar do before it stops -// 0 = infinite -#define AI_PATHFINDER_LOOPS_PER_TICK 5 - -// How long may the AI search for one route? -// 0 = infinite -// This number is the number of tiles tested. -// It takes (AI_PATHFINDER_MAX_SEARCH_NODES / AI_PATHFINDER_LOOPS_PER_TICK) ticks -// to get here.. with 5000 / 10 = 500. 500 / 74 (one day) = 8 days till it aborts -// (that is: if the AI is on VERY FAST! :p -#define AI_PATHFINDER_MAX_SEARCH_NODES 5000 - -// If you enable this, the AI is not allowed to make 90degree turns -#define AI_PATHFINDER_NO_90DEGREES_TURN - -// Below are defines for the g-calculation - -// Standard penalty given to a tile -#define AI_PATHFINDER_PENALTY 150 -// The penalty given to a tile that is going up -#define AI_PATHFINDER_TILE_GOES_UP_PENALTY 450 -// The penalty given to a tile which would have to use fundation -#define AI_PATHFINDER_FOUNDATION_PENALTY 100 -// Changing direction is a penalty, to prevent curved ways (with that: slow ways) -#define AI_PATHFINDER_DIRECTION_CHANGE_PENALTY 200 -// Same penalty, only for when road already exists -#define AI_PATHFINDER_DIRECTION_CHANGE_ON_EXISTING_ROAD_PENALTY 50 -// A diagonal track cost the same as a straigh, but a diagonal is faster... so give -// a bonus for using diagonal track -#ifdef AI_PATHFINDER_NO_90DEGREES_TURN -#define AI_PATHFINDER_DIAGONAL_BONUS 95 -#else -#define AI_PATHFINDER_DIAGONAL_BONUS 75 -#endif -// If a roadblock already exists, it gets a bonus -#define AI_PATHFINDER_ROAD_ALREADY_EXISTS_BONUS 140 -// To prevent 3 direction changes in 3 tiles, this penalty is given in such situation -#define AI_PATHFINDER_CURVE_PENALTY 200 - -// Penalty a bridge gets per length -#define AI_PATHFINDER_BRIDGE_PENALTY 180 -// The penalty for a bridge going up -#define AI_PATHFINDER_BRIDGE_GOES_UP_PENALTY 1000 - -// Tunnels are expensive... -// Because of that, every tile the cost is increased with 1/8th of his value -// This is also true if you are building a tunnel yourself -#define AI_PATHFINDER_TUNNEL_PENALTY 350 - -/* - * Ai_New defines - */ - -// How long may we search cities and industry for a new route? -#define AI_LOCATE_ROUTE_MAX_COUNTER 200 - -// How many days must there be between building the first station and the second station -// within one city. This number is in days and should be more than 4 months. -#define AI_CHECKCITY_DATE_BETWEEN 180 - -// How many cargo is needed for one station in a city? -#define AI_CHECKCITY_CARGO_PER_STATION 60 -// How much cargo must there not be used in a city before we can build a new station? -#define AI_CHECKCITY_NEEDED_CARGO 50 -// When there is already a station which takes the same good and the rating of that -// city is higher than this numer, we are not going to attempt to build anything -// there -#define AI_CHECKCITY_CARGO_RATING 50 -// But, there is a chance of 1 out of this number, that we do ;) -#define AI_CHECKCITY_CARGO_RATING_CHANCE 5 -// If a city is too small to contain a station, there is a small chance -// that we still do so.. just to make the city bigger! -#define AI_CHECKCITY_CITY_CHANCE 5 - -// This number indicates for every unit of cargo, how many tiles two stations maybe be away -// from eachother. In other words: if we have 120 units of cargo in one station, and 120 units -// of the cargo in the other station, both stations can be 96 units away from eachother, if the -// next number is 0.4. -#define AI_LOCATEROUTE_BUS_CARGO_DISTANCE 0.4 -#define AI_LOCATEROUTE_TRUCK_CARGO_DISTANCE 0.7 -// In whole tiles, the minimum distance for a truck route -#define AI_LOCATEROUTE_TRUCK_MIN_DISTANCE 30 - -// The amount of tiles in a square from -X to +X that is scanned for a station spot -// (so if this number is 10, 20x20 = 400 tiles are scanned for _the_ perfect spot -// Safe values are between 15 and 5 -#define AI_FINDSTATION_TILE_RANGE 10 - -// Building on normal speed goes very fast. Idle this amount of ticks between every -// building part. It is calculated like this: (4 - competitor_speed) * num + 1 -// where competitor_speed is between 0 (very slow) to 4 (very fast) -#define AI_BUILDPATH_PAUSE 10 - -// Minimum % of reliabilty a vehicle has to have before the AI buys it -#define AI_VEHICLE_MIN_RELIABILTY 60 - -// The minimum amount of money a company should always have -#define AI_MINIMUM_MONEY 15000 - -// If the most cheap route is build, how much is it going to cost.. -// This is to prevent the AI from trying to build a route which can not be paid for -#define AI_MINIMUM_BUS_ROUTE_MONEY 25000 -#define AI_MINIMUM_TRUCK_ROUTE_MONEY 35000 - -// The minimum amount of money before we are going to repay any money -#define AI_MINIMUM_LOAN_REPAY_MONEY 40000 -// How many repays do we do if we have enough money to do so? -// Every repay is 10000 -#define AI_LOAN_REPAY 2 -// How much income must we have before paying back a loan? Month-based (and looked at the last month) -#define AI_MINIMUM_INCOME_FOR_LOAN 7000 - -// If there is <num> time as much cargo in the station then the vehicle can handle -// reuse the station instead of building a new one! -#define AI_STATION_REUSE_MULTIPLER 2 - -// No more than this amount of vehicles per station.. -#define AI_CHECK_MAX_VEHICLE_PER_STATION 10 - -// How many thick between building 2 vehicles -#define AI_BUILD_VEHICLE_TIME_BETWEEN DAY_TICKS - -// How many days must there between vehicle checks -// The more often, the less non-money-making lines there will be -// but the unfair it may seem to a human company -#define AI_DAYS_BETWEEN_VEHICLE_CHECKS 30 - -// How money profit does a vehicle needs to make to stay in order -// This is the profit of this year + profit of last year -// But also for vehicles that are just one year old. In other words: -// Vehicles of 2 years do easier meet this setting than vehicles -// of one year. This is a very good thing. New vehicles are filtered, -// while old vehicles stay longer, because we do get less in return. -#define AI_MINIMUM_ROUTE_PROFIT 1000 - -// A vehicle is considered lost when he his cargo is more than 180 days old -#define AI_VEHICLE_LOST_DAYS 180 - -// How many times may the AI try to find a route before it gives up -#define AI_MAX_TRIES_FOR_SAME_ROUTE 8 - -/* - * End of defines - */ - -// This stops 90degrees curves -static const byte _illegal_curves[6] = { - 255, 255, // Horz and vert, don't have the effect - 5, // upleft and upright are not valid - 4, // downright and downleft are not valid - 2, // downleft and upleft are not valid - 3, // upright and downright are not valid -}; - -enum { - AI_STATE_STARTUP = 0, - AI_STATE_FIRST_TIME, - AI_STATE_NOTHING, - AI_STATE_WAKE_UP, - AI_STATE_LOCATE_ROUTE, - AI_STATE_FIND_STATION, - AI_STATE_FIND_PATH, - AI_STATE_FIND_DEPOT, - AI_STATE_VERIFY_ROUTE, - AI_STATE_BUILD_STATION, - AI_STATE_BUILD_PATH, - AI_STATE_BUILD_DEPOT, - AI_STATE_BUILD_VEHICLE, - AI_STATE_WAIT_FOR_BUILD, - AI_STATE_GIVE_ORDERS, - AI_STATE_START_VEHICLE, - AI_STATE_REPAY_MONEY, - AI_STATE_CHECK_ALL_VEHICLES, - AI_STATE_ACTION_DONE, - AI_STATE_STOP, // Temporary function to stop the AI -}; - -// Used for tbt (train/bus/truck) -enum { - AI_TRAIN = 0, - AI_BUS, - AI_TRUCK, -}; - -enum { - AI_ACTION_NONE = 0, - AI_ACTION_BUS_ROUTE, - AI_ACTION_TRUCK_ROUTE, - AI_ACTION_REPAY_LOAN, - AI_ACTION_CHECK_ALL_VEHICLES, -}; - -// Used for from_type/to_type -enum { - AI_NO_TYPE = 0, - AI_CITY, - AI_INDUSTRY, -}; - -// Flags for in the vehicle -enum { - AI_VEHICLEFLAG_SELL = 1, - // Remember, flags must be in power of 2 -}; - -#define AI_NO_CARGO 0xFF // Means that there is no cargo defined yet (used for industry) -#define AI_NEED_CARGO 0xFE // Used when the AI needs to find out a cargo for the route -#define AI_STATION_RANGE TileXY(MapMaxX(), MapMaxY()) - -#define AI_PATHFINDER_NO_DIRECTION (byte)-1 - -// Flags used in user_data -#define AI_PATHFINDER_FLAG_BRIDGE 1 -#define AI_PATHFINDER_FLAG_TUNNEL 2 - -typedef void AiNew_StateFunction(Company *c); - -// ai_new.c -void AiNewDoGameLoop(Company *c); - -struct Ai_PathFinderInfo { - TileIndex start_tile_tl; ///< tl = top-left - TileIndex start_tile_br; ///< br = bottom-right - TileIndex end_tile_tl; ///< tl = top-left - TileIndex end_tile_br; ///< br = bottom-right - DiagDirection start_direction; ///< 0 to 3 or AI_PATHFINDER_NO_DIRECTION - DiagDirection end_direction; ///< 0 to 3 or AI_PATHFINDER_NO_DIRECTION - - TileIndex route[500]; - byte route_extra[500]; ///< Some extra information about the route like bridge/tunnel - int route_length; - int position; ///< Current position in the build-path, needed to build the path - - bool rail_or_road; ///< true = rail, false = road -}; - -// ai_pathfinder.c -AyStar *new_AyStar_AiPathFinder(int max_tiles_around, Ai_PathFinderInfo *PathFinderInfo); -void clean_AyStar_AiPathFinder(AyStar *aystar, Ai_PathFinderInfo *PathFinderInfo); - -// ai_shared.c -int AiNew_GetRailDirection(TileIndex tile_a, TileIndex tile_b, TileIndex tile_c); -int AiNew_GetRoadDirection(TileIndex tile_a, TileIndex tile_b, TileIndex tile_c); -DiagDirection AiNew_GetDirection(TileIndex tile_a, TileIndex tile_b); -bool AiNew_SetSpecialVehicleFlag(Company *c, Vehicle *v, uint flag); -uint AiNew_GetSpecialVehicleFlag(Company *c, Vehicle *v); - -// ai_build.c -bool AiNew_Build_CompanyHQ(Company *c, TileIndex tile); -CommandCost AiNew_Build_Station(Company *c, byte type, TileIndex tile, byte length, byte numtracks, byte direction, byte flag); -CommandCost AiNew_Build_Bridge(Company *c, TileIndex tile_a, TileIndex tile_b, byte flag); -CommandCost AiNew_Build_RoutePart(Company *c, Ai_PathFinderInfo *PathFinderInfo, byte flag); -EngineID AiNew_PickVehicle(Company *c); -CommandCost AiNew_Build_Vehicle(Company *c, TileIndex tile, byte flag); -CommandCost AiNew_Build_Depot(Company *c, TileIndex tile, DiagDirection direction, byte flag); - -/* The amount of memory reserved for the AI-special-vehicles */ -#define AI_MAX_SPECIAL_VEHICLES 100 - -struct Ai_SpecialVehicle { - VehicleID veh_id; - uint32 flag; -}; - -struct CompanyAiNew { - uint8 state; - uint tick; - uint idle; - - int temp; ///< A value used in more than one function, but it just temporary - ///< The use is pretty simple: with this we can 'think' about stuff - ///< in more than one tick, and more than one AI. A static will not - ///< do, because they are not saved. This way, the AI is almost human ;) - int counter; ///< For the same reason as temp, we have counter. It can count how - ///< long we are trying something, and just abort if it takes too long - - /* Pathfinder stuff */ - Ai_PathFinderInfo path_info; - AyStar *pathfinder; - - /* Route stuff */ - - CargoID cargo; - byte tbt; ///< train/bus/truck 0/1/2 AI_TRAIN/AI_BUS/AI_TRUCK - Money new_cost; - - byte action; - - int last_id; ///< here is stored the last id of the searched city/industry - Date last_vehiclecheck_date; // Used in CheckVehicle - Ai_SpecialVehicle special_vehicles[AI_MAX_SPECIAL_VEHICLES]; ///< Some vehicles have some special flags - - TileIndex from_tile; - TileIndex to_tile; - - DiagDirectionByte from_direction; - DiagDirectionByte to_direction; - - bool from_deliver; ///< True if this is the station that GIVES cargo - bool to_deliver; - - TileIndex depot_tile; - DiagDirectionByte depot_direction; - - byte amount_veh; ///< How many vehicles we are going to build in this route - byte cur_veh; ///< How many vehicles did we bought? - VehicleID veh_id; ///< Used when bought a vehicle - VehicleID veh_main_id; ///< The ID of the first vehicle, for shared copy - - int from_ic; ///< ic = industry/city. This is the ID of them - byte from_type; ///< AI_NO_TYPE/AI_CITY/AI_INDUSTRY - int to_ic; - byte to_type; -}; -extern CompanyAiNew _companies_ainew[MAX_COMPANIES]; - -#endif /* AI_TROLLY_H */ diff --git a/src/aircraft_cmd.cpp b/src/aircraft_cmd.cpp index 8a1c8470f..fd22460b2 100644 --- a/src/aircraft_cmd.cpp +++ b/src/aircraft_cmd.cpp @@ -32,6 +32,7 @@ #include "autoreplace_func.h" #include "autoreplace_gui.h" #include "gfx_func.h" +#include "ai/ai.hpp" #include "company_func.h" #include "settings_type.h" #include "order_func.h" @@ -1338,6 +1339,8 @@ static void CrashAirplane(Vehicle *v) newsitem = STR_A034_PLANE_CRASH_DIE_IN_FIREBALL; } + AI::NewEvent(v->owner, new AIEventVehicleCrashed(v->index, v->tile)); + AddNewsItem(newsitem, NS_ACCIDENT_VEHICLE, v->index, @@ -1388,6 +1391,7 @@ static void AircraftEntersTerminal(Vehicle *v) v->index, st->index ); + AI::NewEvent(v->owner, new AIEventStationFirstVehicle(st->index, v->index)); } v->BeginLoading(); diff --git a/src/autoslope.h b/src/autoslope.h index eb0478ccc..204536e87 100644 --- a/src/autoslope.h +++ b/src/autoslope.h @@ -32,14 +32,14 @@ static inline bool AutoslopeCheckForEntranceEdge(TileIndex tile, uint z_new, Slo /** * Tests if autoslope is enabled for _current_company. * - * Autoslope is disabled for town/industry construction and old ai companies. + * Autoslope is disabled for town/industry construction. * * @return true iff autoslope is enabled. */ static inline bool AutoslopeEnabled() { return (_settings_game.construction.autoslope && - ((_current_company < MAX_COMPANIES && !_is_old_ai_company) || + (_current_company < MAX_COMPANIES || (_current_company == OWNER_NONE && _game_mode == GM_EDITOR))); } diff --git a/src/callback_table.cpp b/src/callback_table.cpp index 24c65a8eb..0bab10759 100644 --- a/src/callback_table.cpp +++ b/src/callback_table.cpp @@ -54,6 +54,7 @@ CommandCallback CcBuildShip; CommandCallback CcBuildWagon; CommandCallback CcBuildLoco; +/* ai/ai_core.cpp */ CommandCallback CcAI; CommandCallback *_callback_table[] = { diff --git a/src/company_base.h b/src/company_base.h index ee93dd91a..37b5ba5c5 100644 --- a/src/company_base.h +++ b/src/company_base.h @@ -70,6 +70,10 @@ struct Company : PoolItem<Company, CompanyByte, &_Company_pool> { Money bankrupt_value; bool is_ai; + bool is_noai; ///< This is a NoAI player (for loading old savegames properly). Part of the NoAI 'hack' to retain savegame compatability with trunk + + class AIInstance *ai_instance; + class AIInfo *ai_info; Money yearly_expenses[3][EXPENSES_END]; CompanyEconomyEntry cur_economy; diff --git a/src/company_cmd.cpp b/src/company_cmd.cpp index 338abc88b..3c3ff9ff3 100644 --- a/src/company_cmd.cpp +++ b/src/company_cmd.cpp @@ -15,7 +15,7 @@ #include "network/network_func.h" #include "network/network_base.h" #include "variables.h" -#include "ai/ai.h" +#include "ai/ai.hpp" #include "company_manager_face.h" #include "group.h" #include "window_func.h" @@ -31,8 +31,6 @@ #include "autoreplace_func.h" #include "autoreplace_gui.h" #include "string_func.h" -#include "ai/default/default.h" -#include "ai/trolly/trolly.h" #include "road_func.h" #include "rail.h" #include "sprite.h" @@ -53,6 +51,7 @@ DEFINE_OLD_POOL_GENERIC(Company, Company) Company::Company(uint16 name_1, bool is_ai) : name_1(name_1), location_of_HQ(INVALID_TILE), is_ai(is_ai) { for (uint j = 0; j < 4; j++) this->share_owners[j] = COMPANY_SPECTATOR; + this->is_noai = true; } Company::~Company() @@ -293,6 +292,7 @@ set_name:; SetDParam(3, t->index); AddNewsItem(STR_02B6, NS_COMPANY_NEW, c->last_build_coordinate, 0, cni); } + AI::BroadcastNewEvent(new AIEventCompanyNew(c->index), c->index); return; } bad_town_name:; @@ -429,9 +429,6 @@ Company *DoStartupNewCompany(bool is_ai) Company *c = new Company(STR_SV_UNNAMED, is_ai); - memset(&_companies_ai[c->index], 0, sizeof(CompanyAI)); - memset(&_companies_ainew[c->index], 0, sizeof(CompanyAiNew)); - /* Make a color */ c->colour = GenerateCompanyColour(); ResetCompanyLivery(c); @@ -439,7 +436,6 @@ Company *DoStartupNewCompany(bool is_ai) c->money = c->current_loan = 100000; - _companies_ai[c->index].state = 5; // AIS_WANT_NEW_ROUTE c->share_owners[0] = c->share_owners[1] = c->share_owners[2] = c->share_owners[3] = INVALID_OWNER; c->avail_railtypes = GetCompanyRailtypes(c->index); @@ -460,8 +456,7 @@ Company *DoStartupNewCompany(bool is_ai) InvalidateWindow(WC_TOOLBAR_MENU, 0); InvalidateWindow(WC_CLIENT_LIST, 0); - if (is_ai && (!_networking || _network_server) && _ai.enabled) - AI_StartNewAI(c->index); + if (is_ai && (!_networking || _network_server)) AI::StartNew(c->index); c->num_engines = CallocT<uint16>(GetEnginePoolSize()); @@ -516,7 +511,7 @@ void OnTick_Companies() Company *c = GetCompany((CompanyID)_cur_company_tick_index); if (c->name_1 != 0) GenerateCompanyName(c); - if (AI_AllowNewAI() && _game_mode != GM_MENU && !--_next_competitor_start) { + if (AI::CanStartNew() && _game_mode != GM_MENU && !--_next_competitor_start) { MaybeStartNewCompany(); } } @@ -829,25 +824,24 @@ CommandCost CmdCompanyCtrl(TileIndex tile, uint32 flags, uint32 p1, uint32 p2, c c = GetCompany((CompanyID)p2); - /* Only allow removal of HUMAN companies */ - if (IsHumanCompany(c->index)) { - /* Delete any open window of the company */ - DeleteCompanyWindows(c->index); - - CompanyNewsInformation *cni = MallocT<CompanyNewsInformation>(1); - cni->FillData(c); + /* Delete any open window of the company */ + DeleteCompanyWindows(c->index); + CompanyNewsInformation *cni = MallocT<CompanyNewsInformation>(1); + cni->FillData(c); - /* Show the bankrupt news */ - SetDParam(0, STR_705C_BANKRUPT); - SetDParam(1, STR_705D_HAS_BEEN_CLOSED_DOWN_BY); - SetDParamStr(2, cni->company_name); - AddNewsItem(STR_02B6, NS_COMPANY_BANKRUPT, 0, 0, cni); + /* Show the bankrupt news */ + SetDParam(0, STR_705C_BANKRUPT); + SetDParam(1, STR_705D_HAS_BEEN_CLOSED_DOWN_BY); + SetDParamStr(2, cni->company_name); + AddNewsItem(STR_02B6, NS_COMPANY_BANKRUPT, 0, 0, cni); - /* Remove the company */ - ChangeOwnershipOfCompanyItems(c->index, INVALID_OWNER); + /* Remove the company */ + ChangeOwnershipOfCompanyItems(c->index, INVALID_OWNER); + if (!IsHumanCompany(c->index)) AI::Stop(c->index); - delete c; - } + CompanyID c_index = c->index; + delete c; + AI::BroadcastNewEvent(new AIEventCompanyBankrupt(c_index)); } break; case 3: { /* Merge a company (#1) into another company (#2), elimination company #1 */ diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index 5dbb888cd..19352684e 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -31,6 +31,8 @@ #include "company_base.h" #include "settings_type.h" #include "gamelog.h" +#include "ai/ai.hpp" +#include "ai/ai_config.hpp" #ifdef ENABLE_NETWORK #include "table/strings.h" @@ -832,6 +834,129 @@ DEF_CONSOLE_CMD(ConRestart) return true; } +DEF_CONSOLE_CMD(ConListAI) +{ + char buf[4096]; + char *p = &buf[0]; + p = AI::GetConsoleList(p, lastof(buf)); + + p = &buf[0]; + /* Print output line by line */ + for (char *p2 = &buf[0]; *p2 != '\0'; p2++) { + if (*p2 == '\n') { + *p2 = '\0'; + IConsolePrintF(CC_DEFAULT, "%s", p); + p = p2 + 1; + } + } + + return true; +} + +DEF_CONSOLE_CMD(ConStartAI) +{ + if (argc == 0 || argc > 3) { + IConsoleHelp("Start a new AI. Usage: 'start_ai [<AI>] [<settings>]'"); + IConsoleHelp("Start a new AI. If <AI> is given, it starts that specific AI (if found)."); + IConsoleHelp("If <settings> is given, it is parsed and the AI settings are set to that."); + return true; + } + + if (ActiveCompanyCount() == MAX_COMPANIES) { + IConsoleWarning("Can't start a new AI (no more free slots)."); + return true; + } + if (_networking && !_network_server) { + IConsoleWarning("Only the server can start a new AI."); + return true; + } + if (_networking && !_settings_game.ai.ai_in_multiplayer) { + IConsoleWarning("AIs are not allowed in multiplayer by configuration."); + IConsoleWarning("Switch AI -> AI in multiplayer to True."); + return true; + } + if (!AI::CanStartNew()) { + IConsoleWarning("Can't start a new AI."); + return true; + } + + int n = 0; + Company *c; + /* Find the next free slot */ + FOR_ALL_COMPANIES(c) { + if (c->index != n) break; + n++; + } + + AIConfig *config = AIConfig::GetConfig((CompanyID)n); + if (argc >= 2) { + class AIInfo *info = AI::GetCompanyInfo(argv[1]); + if (info == NULL) { + IConsoleWarning("Failed to load the specified AI"); + return true; + } + config->ChangeAI(argv[1]); + if (argc == 3) { + config->StringToSettings(argv[2]); + } + } else { + config->ChangeAI(NULL); + } + + /* Start a new AI company */ + DoCommandP(0, 1, 0, CMD_COMPANY_CTRL); + + return true; +} + +DEF_CONSOLE_CMD(ConStopAI) +{ + if (argc != 2) { + IConsoleHelp("Stop an AI. Usage: 'stop_ai <company-id>'"); + IConsoleHelp("Stop the AI with the given company id. For company-id's, see the list of companies from the dropdown menu. Company 1 is 1, etc."); + return true; + } + + if (_networking && !_network_server) { + IConsoleWarning("Only the server can stop an AI."); + return true; + } + + CompanyID company_id = (CompanyID)(atoi(argv[1]) - 1); + if (!IsValidCompanyID(company_id)) { + IConsolePrintF(CC_DEFAULT, "Unknown company. Company range is between 1 and %d.", MAX_COMPANIES); + return true; + } + + if (IsHumanCompany(company_id)) { + IConsoleWarning("Company is not controlled by an AI."); + return true; + } + + /* Now kill the company of the AI. */ + DoCommandP(0, 2, company_id, CMD_COMPANY_CTRL); + IConsolePrint(CC_DEFAULT, "AI stopped, company deleted."); + + return true; +} + +DEF_CONSOLE_CMD(ConRescanAI) +{ + if (argc == 0) { + IConsoleHelp("Rescan the AI dir for scripts. Usage: 'rescan_ai'"); + return true; + } + + if (_networking && !_network_server) { + IConsoleWarning("Only the server can rescan the AI dir for scripts."); + return true; + } + + AI::Rescan(); + + return true; +} + DEF_CONSOLE_CMD(ConGetSeed) { if (argc == 0) { @@ -1338,6 +1463,7 @@ void IConsoleStdLibRegister() IConsoleCmdRegister("help", ConHelp); IConsoleCmdRegister("info_cmd", ConInfoCmd); IConsoleCmdRegister("info_var", ConInfoVar); + IConsoleCmdRegister("list_ai", ConListAI); IConsoleCmdRegister("list_cmds", ConListCommands); IConsoleCmdRegister("list_vars", ConListVariables); IConsoleCmdRegister("list_aliases", ConListAliases); @@ -1346,6 +1472,7 @@ void IConsoleStdLibRegister() IConsoleCmdRegister("getseed", ConGetSeed); IConsoleCmdRegister("getdate", ConGetDate); IConsoleCmdRegister("quit", ConExit); + IConsoleCmdRegister("rescan_ai", ConRescanAI); IConsoleCmdRegister("resetengines", ConResetEngines); IConsoleCmdRegister("return", ConReturn); IConsoleCmdRegister("screenshot", ConScreenShot); @@ -1356,6 +1483,8 @@ void IConsoleStdLibRegister() IConsoleCmdRegister("rm", ConRemove); IConsoleCmdRegister("save", ConSave); IConsoleCmdRegister("saveconfig", ConSaveConfig); + IConsoleCmdRegister("start_ai", ConStartAI); + IConsoleCmdRegister("stop_ai", ConStopAI); IConsoleCmdRegister("ls", ConListFiles); IConsoleCmdRegister("cd", ConChangeDirectory); IConsoleCmdRegister("pwd", ConPrintWorkingDirectory); diff --git a/src/core/enum_type.hpp b/src/core/enum_type.hpp index 6e1da3dda..bd059212c 100644 --- a/src/core/enum_type.hpp +++ b/src/core/enum_type.hpp @@ -74,7 +74,8 @@ template <typename Tenum_t> struct TinyEnumT; /** The general declaration of TinyEnumT<> (above) */ template <typename Tenum_t> -struct TinyEnumT { +struct TinyEnumT +{ typedef Tenum_t enum_type; ///< expose our enumeration type (i.e. Trackdir) to outside typedef EnumPropsT<Tenum_t> Props; ///< make easier access to our enumeration propeties typedef typename Props::storage storage_type; ///< small storage type @@ -120,31 +121,4 @@ struct TinyEnumT { } }; - -/** Template of struct holding enum types (on most archs, enums are stored in an int32). No math operators are provided. */ -template <typename enum_type, typename storage_type> -struct SimpleTinyEnumT { - storage_type m_val; ///< here we hold the actual value in small (i.e. byte) form - - /** Cast operator - invoked then the value is assigned to the storage_type */ - FORCEINLINE operator enum_type () const - { - return (enum_type)this->m_val; - } - - /** Assignment operator (from enum_type) */ - FORCEINLINE SimpleTinyEnumT &operator = (enum_type e) - { - this->m_val = (storage_type)e; - return *this; - } - - /** Assignment operator (from general uint) */ - FORCEINLINE SimpleTinyEnumT &operator = (uint u) - { - this->m_val = (storage_type)u; - return *this; - } -}; - -#endif /* ENUM_TYPE_HPP */ +#endif /* HELPERS_HPP */ diff --git a/src/economy.cpp b/src/economy.cpp index d43be165b..a10b54e8f 100644 --- a/src/economy.cpp +++ b/src/economy.cpp @@ -17,7 +17,7 @@ #include "network/network_func.h" #include "variables.h" #include "vehicle_gui.h" -#include "ai/ai.h" +#include "ai/ai.hpp" #include "train.h" #include "roadveh.h" #include "aircraft.h" @@ -480,6 +480,7 @@ static void CompanyCheckBankrupt(Company *c) SetDParam(1, STR_7057_WILL_BE_SOLD_OFF_OR_DECLARED); SetDParamStr(2, cni->company_name); AddNewsItem(STR_02B6, NS_COMPANY_TROUBLE, 0, 0, cni); + AI::BroadcastNewEvent(new AIEventCompanyInTrouble(c->index)); break; case 3: { /* XXX - In multiplayer, should we ask other companies if it wants to take @@ -529,12 +530,11 @@ static void CompanyCheckBankrupt(Company *c) ChangeNetworkOwner(c->index, COMPANY_SPECTATOR); ChangeOwnershipOfCompanyItems(c->index, INVALID_OWNER); - /* Register the company as not-active */ - if (!IsHumanCompany(c->index) && (!_networking || _network_server) && _ai.enabled) { - AI_CompanyDied(c->index); - } + if (!IsHumanCompany(c->index)) AI::Stop(c->index); + CompanyID c_index = c->index; delete c; + AI::BroadcastNewEvent(new AIEventCompanyBankrupt(c_index)); } } @@ -1065,6 +1065,7 @@ static void SubsidyMonthlyHandler() AddNewsItem(STR_202E_OFFER_OF_SUBSIDY_EXPIRED, NS_SUBSIDIES, pair.a, pair.b); s->cargo_type = CT_INVALID; modified = true; + AI::BroadcastNewEvent(new AIEventSubsidyOfferExpired(s - _subsidies)); } else if (s->age == 2*12-1) { st = GetStation(s->to); if (st->owner == _local_company) { @@ -1073,6 +1074,7 @@ static void SubsidyMonthlyHandler() } s->cargo_type = CT_INVALID; modified = true; + AI::BroadcastNewEvent(new AIEventSubsidyExpired(s - _subsidies)); } else { s->age++; } @@ -1109,6 +1111,7 @@ static void SubsidyMonthlyHandler() s->age = 0; pair = SetupSubsidyDecodeParam(s, 0); AddNewsItem(STR_2030_SERVICE_SUBSIDY_OFFERED, NS_SUBSIDIES, pair.a, pair.b); + AI::BroadcastNewEvent(new AIEventSubsidyOffer(s - _subsidies)); modified = true; break; } @@ -1326,6 +1329,7 @@ static bool CheckSubsidised(Station *from, Station *to, CargoID cargo_type) NS_SUBSIDIES, pair.a, pair.b ); + AI::BroadcastNewEvent(new AIEventSubsidyAwarded(s - _subsidies)); InvalidateWindow(WC_SUBSIDIES_LIST, 0); return true; @@ -1817,6 +1821,7 @@ static void DoAcquireCompany(Company *c) Company *owner; int i; Money value; + CompanyID ci = c->index; CompanyNewsInformation *cni = MallocT<CompanyNewsInformation>(1); cni->FillData(c, GetCompany(_current_company)); @@ -1827,9 +1832,9 @@ static void DoAcquireCompany(Company *c) SetDParamStr(3, cni->other_company_name); SetDParam(4, c->bankrupt_value); AddNewsItem(STR_02B6, NS_COMPANY_MERGER, 0, 0, cni); + AI::BroadcastNewEvent(new AIEventCompanyMerger(ci, _current_company)); /* original code does this a little bit differently */ - CompanyID ci = c->index; ChangeNetworkOwner(ci, _current_company); ChangeOwnershipOfCompanyItems(ci, _current_company); @@ -1848,6 +1853,8 @@ static void DoAcquireCompany(Company *c) } _current_company = old_company; + if (!IsHumanCompany(c->index)) AI::Stop(c->index); + DeleteCompanyWindows(ci); InvalidateWindowClassesData(WC_TRAINS_LIST, 0); InvalidateWindowClassesData(WC_SHIPS_LIST, 0); diff --git a/src/engine.cpp b/src/engine.cpp index 7e7959e26..ffecb7bac 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -24,6 +24,7 @@ #include "string_func.h" #include "settings_type.h" #include "oldpool_func.h" +#include "ai/ai.hpp" #include "core/alloc_func.hpp" #include "vehicle_func.h" @@ -376,14 +377,10 @@ void EnginesDailyLoop() continue; } - if (!IsHumanCompany(best_company)) { - /* XXX - TTDBUG: TTD has a bug here ???? */ - AcceptEnginePreview(i, best_company); - } else { - e->flags |= ENGINE_OFFER_WINDOW_OPEN; - e->preview_wait = 20; - if (IsInteractiveCompany(best_company)) ShowEnginePreviewWindow(i); - } + e->flags |= ENGINE_OFFER_WINDOW_OPEN; + e->preview_wait = 20; + AI::NewEvent(best_company, new AIEventEnginePreview(i)); + if (IsInteractiveCompany(best_company)) ShowEnginePreviewWindow(i); } } } @@ -460,6 +457,8 @@ static void NewVehicleAvailable(Engine *e) FOR_ALL_COMPANIES(c) SetBit(c->avail_roadtypes, HasBit(e->info.misc_flags, EF_ROAD_TRAM) ? ROADTYPE_TRAM : ROADTYPE_ROAD); } + AI::BroadcastNewEvent(new AIEventEngineAvailable(index)); + SetDParam(0, GetEngineCategoryName(index)); SetDParam(1, index); AddNewsItem(STR_NEW_VEHICLE_NOW_AVAILABLE_WITH_TYPE, NS_NEW_VEHICLES, index, 0); diff --git a/src/fileio.cpp b/src/fileio.cpp index f4daaaa59..5f60bf4c7 100644 --- a/src/fileio.cpp +++ b/src/fileio.cpp @@ -216,7 +216,8 @@ const char *_subdirs[NUM_SUBDIRS] = { "scenario" PATHSEP "heightmap" PATHSEP, "gm" PATHSEP, "data" PATHSEP, - "lang" PATHSEP + "lang" PATHSEP, + "ai" PATHSEP, }; const char *_searchpaths[NUM_SEARCHPATHS]; @@ -359,7 +360,7 @@ FILE *FioFOpenFile(const char *filename, const char *mode, Subdirectory subdir, } /* We can only use .tar in case of data-dir, and read-mode */ - if (f == NULL && subdir == DATA_DIR && mode[0] == 'r') { + if (f == NULL && (subdir == DATA_DIR || subdir == AI_DIR) && mode[0] == 'r') { static const uint MAX_RESOLVED_LENGTH = 2 * (100 + 100 + 155) + 1; // Enough space to hold two filenames plus link. See 'TarHeader'. char resolved_name[MAX_RESOLVED_LENGTH]; @@ -466,6 +467,33 @@ char *BuildWithFullPath(const char *dir) return dest; } +const char *FioTarFirstDir(const char *tarname) +{ + TarList::iterator it = _tar_list.find(tarname); + if (it == _tar_list.end()) return NULL; + return (*it).second.dirname; +} + +static void TarAddLink(const std::string &src, const std::string &dest) +{ + TarFileList::iterator dest_file = _tar_filelist.find(dest); + if (dest_file != _tar_filelist.end()) { + /* Link to file. Process the link like the destination file. */ + _tar_filelist.insert(TarFileList::value_type(src, dest_file->second)); + } else { + /* Destination file not found. Assume 'link to directory' */ + /* Append PATHSEPCHAR to 'src' and 'dest' if needed */ + const std::string src_path = ((*src.rbegin() == PATHSEPCHAR) ? src : src + PATHSEPCHAR); + const std::string dst_path = (dest.length() == 0 ? "" : ((*dest.rbegin() == PATHSEPCHAR) ? dest : dest + PATHSEPCHAR)); + _tar_linklist.insert(TarLinkList::value_type(src_path, dst_path)); + } +} + +void FioTarAddLink(const char *src, const char *dest) +{ + TarAddLink(src, dest); +} + /** * Simplify filenames from tars. * Replace '/' by PATHSEPCHAR, and force 'name' to lowercase. @@ -515,6 +543,7 @@ static bool TarListAddFile(const char *filename) const char *dupped_filename = strdup(filename); _tar_list[filename].filename = dupped_filename; + _tar_list[filename].dirname = NULL; TarLinkList links; ///< Temporary list to collect links @@ -649,6 +678,15 @@ static bool TarListAddFile(const char *filename) break; } + case '5': // directory + /* Convert to lowercase and our PATHSEPCHAR */ + SimplifyFileName(name); + + /* Store the first directory name we detect */ + DEBUG(misc, 6, "Found dir in tar: %s", name); + if (_tar_list[filename].dirname == NULL) _tar_list[filename].dirname = strdup(name); + break; + default: /* Ignore other types */ break; @@ -675,18 +713,7 @@ static bool TarListAddFile(const char *filename) for (TarLinkList::iterator link = links.begin(); link != links.end(); link++) { const std::string &src = link->first; const std::string &dest = link->second; - - TarFileList::iterator dest_file = _tar_filelist.find(dest); - if (dest_file != _tar_filelist.end()) { - /* Link to file. Process the link like the destination file. */ - _tar_filelist.insert(TarFileList::value_type(src, dest_file->second)); - } else { - /* Destination file not found. Assume 'link to directory' */ - /* Append PATHSEPCHAR to 'src' and 'dest' */ - const std::string src_path = src + PATHSEPCHAR; - const std::string dst_path = (dest.length() == 0 ? "" : dest + PATHSEPCHAR); - _tar_linklist.insert(TarLinkList::value_type(src_path, dst_path)); - } + TarAddLink(src, dest); } return true; @@ -742,6 +769,8 @@ void ScanForTarFiles() FOR_ALL_SEARCHPATHS(sp) { FioAppendDirectory(path, MAX_PATH, sp, DATA_DIR); num += ScanPathForTarFiles(path, strlen(path)); + FioAppendDirectory(path, MAX_PATH, sp, AI_DIR); + num += ScanPathForTarFiles(path, strlen(path)); } DEBUG(misc, 1, "Scan complete, found %d files", num); } diff --git a/src/fileio_func.h b/src/fileio_func.h index c0e777a9d..352f7f7f2 100644 --- a/src/fileio_func.h +++ b/src/fileio_func.h @@ -61,6 +61,8 @@ void AppendPathSeparator(char *buf, size_t buflen); void DeterminePaths(const char *exe); void *ReadFileToMem(const char *filename, size_t *lenp, size_t maxsize); bool FileExists(const char *filename); +const char *FioTarFirstDir(const char *tarname); +void FioTarAddLink(const char *src, const char *dest); extern char *_personal_dir; ///< custom directory for personal settings, saves, newgrf, etc. diff --git a/src/fileio_type.h b/src/fileio_type.h index baef9c70d..534150616 100644 --- a/src/fileio_type.h +++ b/src/fileio_type.h @@ -19,6 +19,7 @@ enum Subdirectory { GM_DIR, ///< Subdirectory for all music DATA_DIR, ///< Subdirectory for all data (GRFs, sample.cat, intro game) LANG_DIR, ///< Subdirectory for all translation files + AI_DIR, ///< Subdirectory for all AI files NUM_SUBDIRS, ///< Number of subdirectories NO_DIRECTORY, ///< A path without any base directory }; diff --git a/src/genworld.cpp b/src/genworld.cpp index ef6c87151..2ff8f3674 100644 --- a/src/genworld.cpp +++ b/src/genworld.cpp @@ -25,6 +25,7 @@ #include "settings_type.h" #include "newgrf_storage.h" #include "water.h" +#include "blitter/factory.hpp" #include "tilehighlight_func.h" #include "saveload/saveload.h" @@ -298,7 +299,7 @@ void GenerateWorld(GenerateWorldMode mode, uint size_x, uint size_y) _gw.thread = NULL; } - if (_network_dedicated || + if (BlitterFactoryBase::GetCurrentBlitter()->GetScreenDepth() == 0 || (_gw.thread = ThreadObject::New(&_GenerateWorld, NULL)) == NULL) { DEBUG(misc, 1, "Cannot create genworld thread, reverting to single-threaded mode"); _gw.threaded = false; diff --git a/src/genworld_gui.cpp b/src/genworld_gui.cpp index b7347da42..259c4443b 100644 --- a/src/genworld_gui.cpp +++ b/src/genworld_gui.cpp @@ -45,6 +45,7 @@ enum glwp_modes { }; extern void SwitchMode(int new_mode); +extern void MakeNewgameSettingsLive(); static inline void SetNewLandscapeType(byte landscape) { @@ -230,7 +231,7 @@ void StartGeneratingLandscape(glwp_modes mode) DeleteAllNonVitalWindows(); /* Copy all XXX_newgame to XXX when coming from outside the editor */ - _settings_game = _settings_newgame; + MakeNewgameSettingsLive(); ResetGRFConfig(true); SndPlayFx(SND_15_BEEP); @@ -399,7 +400,7 @@ struct GenerateLandscapeWindow : public QueryStringBaseWindow { break; case GLAND_GENERATE_BUTTON: // Generate - _settings_game = _settings_newgame; + MakeNewgameSettingsLive(); if (_settings_game.economy.town_layout == TL_NO_ROADS) { ShowQuery( diff --git a/src/group.h b/src/group.h index 24f0b03a8..9c4d7ffec 100644 --- a/src/group.h +++ b/src/group.h @@ -92,4 +92,6 @@ void UpdateTrainGroupID(Vehicle *v); void RemoveVehicleFromGroup(const Vehicle *v); void RemoveAllGroupsForCompany(const CompanyID company); +extern GroupID _new_group_id; + #endif /* GROUP_H */ diff --git a/src/group_cmd.cpp b/src/group_cmd.cpp index cea7d33ac..dc32e2f5a 100644 --- a/src/group_cmd.cpp +++ b/src/group_cmd.cpp @@ -25,6 +25,8 @@ #include "table/strings.h" +GroupID _new_group_id; + /** * Update the num engines of a groupID. Decrease the old one and increase the new one * @note called in SetTrainGroupID and UpdateTrainGroupID @@ -91,6 +93,8 @@ CommandCost CmdCreateGroup(TileIndex tile, uint32 flags, uint32 p1, uint32 p2, c g->replace_protection = false; g->vehicle_type = vt; + _new_group_id = g->index; + InvalidateWindowData(GetWindowClassForVehicleType(vt), (vt << 11) | VLW_GROUP_LIST | _current_company); } diff --git a/src/industry_cmd.cpp b/src/industry_cmd.cpp index 49934af40..e752976fe 100644 --- a/src/industry_cmd.cpp +++ b/src/industry_cmd.cpp @@ -39,6 +39,7 @@ #include "oldpool_func.h" #include "animated_tile_func.h" #include "effectvehicle_func.h" +#include "ai/ai.hpp" #include "table/strings.h" #include "table/sprites.h" @@ -432,7 +433,10 @@ static CommandCost ClearTile_Industry(TileIndex tile, byte flags) return_cmd_error(STR_4800_IN_THE_WAY); } - if (flags & DC_EXEC) delete i; + if (flags & DC_EXEC) { + AI::BroadcastNewEvent(new AIEventIndustryClose(i->index)); + delete i; + } return CommandCost(EXPENSES_CONSTRUCTION, indspec->GetRemovalCost()); } @@ -1712,6 +1716,7 @@ CommandCost CmdBuildIndustry(TileIndex tile, uint32 flags, uint32 p1, uint32 p2, SetDParam(1, ind->town->index); } AddNewsItem(indspec->new_industry_text, NS_INDUSTRY_OPEN, ind->xy, 0); + AI::BroadcastNewEvent(new AIEventIndustryOpen(ind->index)); } return CommandCost(EXPENSES_OTHER, indspec->GetConstructionCost()); @@ -1912,8 +1917,8 @@ static void MaybeNewIndustry(void) } else { SetDParam(1, ind->town->index); } - AddNewsItem(ind_spc->new_industry_text, - NS_INDUSTRY_OPEN, ind->xy, 0); + AddNewsItem(ind_spc->new_industry_text, NS_INDUSTRY_OPEN, ind->xy, 0); + AI::BroadcastNewEvent(new AIEventIndustryOpen(ind->index)); } /** @@ -2232,6 +2237,7 @@ static void ChangeIndustryProduction(Industry *i, bool monthly) /* Compute news category */ if (closeit) { ns = NS_INDUSTRY_CLOSE; + AI::BroadcastNewEvent(new AIEventIndustryClose(i->index)); } else { switch (WhoCanServiceIndustry(i)) { case 0: ns = NS_INDUSTRY_NOBODY; break; diff --git a/src/lang/english.txt b/src/lang/english.txt index 2dbbb0d88..d3cdd5ed3 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -801,6 +801,7 @@ STR_TRANSPARENT_SIGNS :{SETX 12}Transp STR_02D5_LAND_BLOCK_INFO :Land area information STR_02D6 : STR_CONSOLE_SETTING :Toggle Console +STR_AI_DEBUG_MENU :AI Debug STR_02D7_SCREENSHOT_CTRL_S :Screenshot (Ctrl-S) STR_02D8_GIANT_SCREENSHOT_CTRL_G :Giant Screenshot (Ctrl-G) STR_02D9_ABOUT_OPENTTD :About 'OpenTTD' @@ -1155,6 +1156,7 @@ STR_CONFIG_PATCHES_AI_BUILDS_SHIPS :{LTBLUE}Disable STR_CONFIG_PATCHES_AINEW_ACTIVE :{LTBLUE}Enable new AI (alpha): {ORANGE}{STRING1} STR_CONFIG_PATCHES_AI_IN_MULTIPLAYER :{LTBLUE}Allow AIs in multiplayer (experimental): {ORANGE}{STRING1} +STR_CONFIG_PATCHES_AI_MAX_OPCODES :{LTBLUE}#opcodes before AI is suspended: {ORANGE}{STRING1} STR_CONFIG_PATCHES_SERVINT_TRAINS :{LTBLUE}Default service interval for trains: {ORANGE}{STRING1} days/% STR_CONFIG_PATCHES_SERVINT_TRAINS_DISABLED :{LTBLUE}Default service interval for trains: {ORANGE}disabled @@ -3677,6 +3679,14 @@ STR_OSK_KEYBOARD_LAYOUT :`1234567890-=\q STR_OSK_KEYBOARD_LAYOUT_CAPS :~!@#$%^&*()_+|QWERTYUIOP{{}}ASDFGHJKL:" ZXCVBNM<>? . ######## +############ AI GUI +STR_AI_DEBUG :{WHITE}AI Debug +STR_AI_DEBUG_NAME_TIP :{BLACK}Name of the AI +STR_AI_DEBUG_RELOAD :{BLACK}Reload AI +STR_AI_DEBUG_RELOAD_TIP :{BLACK}Kill the AI, reload the script, and restart the AI +STR_AI_DEBUG_SERVER_ONLY :{YELLOW}AI Debug window is only available for the server +######## + ############ town controlled noise level STR_CONFIG_PATCHES_NOISE_LEVEL :{LTBLUE}Allow town controlled noise level for airports: {ORANGE}{STRING} STR_NOISE_IN_TOWN :{BLACK}Noise limit in town: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA} diff --git a/src/misc.cpp b/src/misc.cpp index bccaf2c62..12ba72434 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -10,7 +10,7 @@ #include "vehicle_gui.h" #include "variables.h" #include "cheat_func.h" -#include "ai/ai.h" +#include "ai/ai.hpp" #include "newgrf_house.h" #include "cargotype.h" #include "group.h" @@ -26,6 +26,7 @@ #include "core/alloc_type.hpp" #include "gamelog.h" #include "animated_tile_func.h" +#include "settings_type.h" #include "tilehighlight_func.h" #include "core/bitmath_func.hpp" #include "network/network_func.h" @@ -34,6 +35,7 @@ #include "table/sprites.h" extern TileIndex _cur_tileloop_tile; +extern void MakeNewgameSettingsLive(); void InitializeVehicles(); void InitializeWaypoints(); @@ -68,7 +70,7 @@ void InitializeGame(uint size_x, uint size_y, bool reset_date) _realtime_tick = 0; _date_fract = 0; _cur_tileloop_tile = 0; - _settings_game = _settings_newgame; + MakeNewgameSettingsLive(); if (reset_date) { SetDate(ConvertYMDToDate(_settings_game.game_creation.starting_year, 0, 1)); @@ -101,7 +103,7 @@ void InitializeGame(uint size_x, uint size_y, bool reset_date) InitializeNPF(); InitializeCompanies(); - AI_Initialize(); + AI::Initialize(); InitializeCheats(); InitTextEffects(); diff --git a/src/misc_cmd.cpp b/src/misc_cmd.cpp index dadc73a6d..1ea5ef2c3 100644 --- a/src/misc_cmd.cpp +++ b/src/misc_cmd.cpp @@ -128,9 +128,10 @@ CommandCost CmdSetCompanyColor(TileIndex tile, uint32 flags, uint32 p1, uint32 p /** Increase the loan of your company. * @param tile unused * @param flags operation to perform - * @param p1 unused + * @param p1 amount to increase the loan with, multitude of LOAN_INTERVAL. Only used when p2 == 2. * @param p2 when 0: loans LOAN_INTERVAL * when 1: loans the maximum loan permitting money (press CTRL), + * when 2: loans the amount specified in p1 */ CommandCost CmdIncreaseLoan(TileIndex tile, uint32 flags, uint32 p1, uint32 p2, const char *text) { @@ -145,11 +146,15 @@ CommandCost CmdIncreaseLoan(TileIndex tile, uint32 flags, uint32 p1, uint32 p2, switch (p2) { default: return CMD_ERROR; // Invalid method case 0: // Take some extra loan - loan = (IsHumanCompany(_current_company) || _settings_game.ai.ainew_active) ? LOAN_INTERVAL : LOAN_INTERVAL_OLD_AI; + loan = LOAN_INTERVAL; break; case 1: // Take a loan as big as possible loan = _economy.max_loan - c->current_loan; break; + case 2: // Take the given amount of loan + if ((((int32)p1 < LOAN_INTERVAL) || c->current_loan + (int32)p1 > _economy.max_loan || (p1 % LOAN_INTERVAL) != 0)) return CMD_ERROR; + loan = p1; + break; } /* Overflow protection */ @@ -167,9 +172,10 @@ CommandCost CmdIncreaseLoan(TileIndex tile, uint32 flags, uint32 p1, uint32 p2, /** Decrease the loan of your company. * @param tile unused * @param flags operation to perform - * @param p1 unused + * @param p1 amount to decrease the loan with, multitude of LOAN_INTERVAL. Only used when p2 == 2. * @param p2 when 0: pays back LOAN_INTERVAL * when 1: pays back the maximum loan permitting money (press CTRL), + * when 2: pays back the amount specified in p1 */ CommandCost CmdDecreaseLoan(TileIndex tile, uint32 flags, uint32 p1, uint32 p2, const char *text) { @@ -181,12 +187,16 @@ CommandCost CmdDecreaseLoan(TileIndex tile, uint32 flags, uint32 p1, uint32 p2, switch (p2) { default: return CMD_ERROR; // Invalid method case 0: // Pay back one step - loan = min(c->current_loan, (Money)(IsHumanCompany(_current_company) || _settings_game.ai.ainew_active) ? LOAN_INTERVAL : LOAN_INTERVAL_OLD_AI); + loan = min(c->current_loan, (Money)LOAN_INTERVAL); break; case 1: // Pay back as much as possible loan = max(min(c->current_loan, c->money), (Money)LOAN_INTERVAL); loan -= loan % LOAN_INTERVAL; break; + case 2: // Repay the given amount of loan + if ((p1 % LOAN_INTERVAL != 0) || ((int32)p1 < LOAN_INTERVAL)) return CMD_ERROR; // Invalid amount to loan + loan = p1; + break; } if (c->money < loan) { diff --git a/src/network/network_client.cpp b/src/network/network_client.cpp index 7e44757a7..ab0ff9342 100644 --- a/src/network/network_client.cpp +++ b/src/network/network_client.cpp @@ -16,7 +16,6 @@ #include "../command_func.h" #include "../console_func.h" #include "../variables.h" -#include "../ai/ai.h" #include "../core/alloc_func.hpp" #include "../fileio_func.h" #include "../md5.h" @@ -26,6 +25,7 @@ #include "../company_func.h" #include "../company_base.h" #include "../company_gui.h" +#include "../settings_type.h" #include "../rev.h" #include "table/strings.h" diff --git a/src/network/network_gui.cpp b/src/network/network_gui.cpp index f9a4f6bfd..60d63083d 100644 --- a/src/network/network_gui.cpp +++ b/src/network/network_gui.cpp @@ -1170,8 +1170,8 @@ struct NetworkLobbyWindow : public Window { const NetworkGameInfo *gi = &this->server->info; int y = NET_PRC__OFFSET_TOP_WIDGET_COMPANY, pos; - /* Join button is disabled when no company is selected */ - this->SetWidgetDisabledState(NLWW_JOIN, this->company == INVALID_COMPANY); + /* Join button is disabled when no company is selected and for AI companies*/ + this->SetWidgetDisabledState(NLWW_JOIN, this->company == INVALID_COMPANY || GetLobbyCompanyInfo(this->company)->ai); /* Cannot start new company if there are too many */ this->SetWidgetDisabledState(NLWW_NEW, gi->companies_on >= gi->companies_max); /* Cannot spectate if there are too many spectators */ diff --git a/src/network/network_server.cpp b/src/network/network_server.cpp index 332364385..d7ed77ba0 100644 --- a/src/network/network_server.cpp +++ b/src/network/network_server.cpp @@ -664,7 +664,7 @@ DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_JOIN) } break; default: /* Join another company (companies 1-8 (index 0-7)) */ - if (!IsValidCompanyID(playas)) { + if (!IsValidCompanyID(playas) || !IsHumanCompany(playas)) { SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_COMPANY_MISMATCH); return; } diff --git a/src/network/network_type.h b/src/network/network_type.h index a65e66386..9104e2f0f 100644 --- a/src/network/network_type.h +++ b/src/network/network_type.h @@ -48,6 +48,7 @@ typedef uint8 ClientIndex; struct NetworkCompanyStats { uint16 num_vehicle[NETWORK_VEHICLE_TYPES]; ///< How many vehicles are there of this type? uint16 num_station[NETWORK_STATION_TYPES]; ///< How many stations are there of this type? + bool ai; ///< Is this company an AI }; /** Some state information of a company, especially for servers */ diff --git a/src/newgrf_cargo.h b/src/newgrf_cargo.h index d640c3876..7d7e9e537 100644 --- a/src/newgrf_cargo.h +++ b/src/newgrf_cargo.h @@ -9,7 +9,7 @@ #include "cargo_type.h" #include "gfx_type.h" -enum { +enum CargoClass { CC_NOAVAILABLE = 0, ///< No cargo class has been specified CC_PASSENGERS = 1 << 0, ///< Passengers CC_MAIL = 1 << 1, ///< Mail diff --git a/src/newgrf_station.cpp b/src/newgrf_station.cpp index a2ad21ec6..dd5473953 100644 --- a/src/newgrf_station.cpp +++ b/src/newgrf_station.cpp @@ -32,7 +32,7 @@ #include "table/sprites.h" #include "table/strings.h" -static StationClass _station_classes[STAT_CLASS_MAX]; +StationClass _station_classes[STAT_CLASS_MAX]; enum { MAX_SPECLIST = 255, diff --git a/src/openttd.cpp b/src/openttd.cpp index 66425cec1..8fd819aef 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -44,7 +44,8 @@ #include "signs_base.h" #include "signs_func.h" #include "waypoint.h" -#include "ai/ai.h" +#include "ai/ai.hpp" +#include "ai/ai_config.hpp" #include "train.h" #include "yapf/yapf.h" #include "settings_func.h" @@ -164,7 +165,7 @@ void CDECL ShowInfoF(const char *str, ...) */ static void ShowHelp() { - char buf[4096]; + char buf[8192]; char *p = buf; p += seprintf(p, lastof(buf), "OpenTTD %s\n", _openttd_revision); @@ -176,6 +177,7 @@ static void ShowHelp() " -s drv = Set sound driver (see below) (param bufsize,hz)\n" " -m drv = Set music driver (see below)\n" " -b drv = Set the blitter to use (see below)\n" + " -a ai = Force use of specific AI (see below)\n" " -r res = Set resolution (for instance 800x600)\n" " -h = Display this help text\n" " -t year = Set starting year\n" @@ -211,6 +213,11 @@ static void ShowHelp() /* List the blitters */ p = BlitterFactoryBase::GetBlittersInfo(p, lastof(buf)); + /* We need to initialize the AI, so it finds the AIs */ + AI::Initialize(); + p = AI::GetConsoleList(p, lastof(buf)); + AI::Uninitialize(true); + /* ShowInfo put output to stderr, but version information should go * to stdout; this is the only exception */ #if !defined(WIN32) && !defined(WIN64) @@ -321,7 +328,7 @@ static void InitializeDynamicVariables() static void ShutdownGame() { /* stop the AI */ - AI_Uninitialize(); + AI::Uninitialize(false); IConsoleFree(); @@ -382,6 +389,24 @@ static void LoadIntroGame() if (_music_driver->IsSongPlaying()) ResetMusic(); } +void MakeNewgameSettingsLive() +{ + for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) { + if (_settings_game.ai_config[c] != NULL) { + delete _settings_game.ai_config[c]; + } + } + + _settings_game = _settings_newgame; + + for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) { + _settings_game.ai_config[c] = NULL; + if (_settings_newgame.ai_config[c] != NULL) { + _settings_game.ai_config[c] = new AIConfig(_settings_newgame.ai_config[c]); + } + } +} + byte _savegame_sort_order; #if defined(UNIX) && !defined(__MORPHOS__) extern void DedicatedFork(); @@ -531,7 +556,9 @@ int ttd_main(int argc, char *argv[]) DedicatedFork(); #endif + AI::Initialize(); LoadFromConfig(); + AI::Uninitialize(true); CheckConfig(); LoadFromHighScore(); @@ -666,7 +693,7 @@ int ttd_main(int argc, char *argv[]) if (_settings_newgame.difficulty.diff_level == 9) SetDifficultyLevel(0, &_settings_newgame.difficulty); /* Make sure _settings is filled with _settings_newgame if we switch to a game directly */ - if (_switch_mode != SM_NONE) _settings_game = _settings_newgame; + if (_switch_mode != SM_NONE) MakeNewgameSettingsLive(); /* initialize the ingame console */ IConsoleInit(); @@ -750,14 +777,17 @@ static void MakeNewGameDone() SettingsDisableElrail(_settings_game.vehicle.disable_elrails); /* In a dedicated server, the server does not play */ - if (_network_dedicated) { + if (BlitterFactoryBase::GetCurrentBlitter()->GetScreenDepth() == 0) { SetLocalCompany(COMPANY_SPECTATOR); + IConsoleCmdExec("exec scripts/game_start.scr 0"); return; } /* Create a single company */ DoStartupNewCompany(false); + IConsoleCmdExec("exec scripts/game_start.scr 0"); + SetLocalCompany(COMPANY_FIRST); _current_company = _local_company; DoCommandP(0, (_settings_client.gui.autorenew << 15 ) | (_settings_client.gui.autorenew_months << 16) | 4, _settings_client.gui.autorenew_money, CMD_SET_AUTOREPLACE); @@ -909,7 +939,7 @@ void SwitchMode(int new_mode) /* check if we should reload the config */ if (_settings_client.network.reload_cfg) { LoadFromConfig(); - _settings_game = _settings_newgame; + MakeNewgameSettingsLive(); ResetGRFConfig(false); } NetworkServerStart(); @@ -920,6 +950,8 @@ void SwitchMode(int new_mode) } } #endif /* ENABLE_NETWORK */ + /* Make sure all AI controllers are gone at quiting game */ + if (new_mode != SM_SAVE) AI::KillAll(); switch (new_mode) { case SM_EDITOR: /* Switch to scenario editor */ @@ -959,6 +991,8 @@ void SwitchMode(int new_mode) /* Update the local company for a loaded game. It is either always * company #1 (eg 0) or in the case of a dedicated server a spectator */ SetLocalCompany(_network_dedicated ? COMPANY_SPECTATOR : COMPANY_FIRST); + /* Execute the game-start script */ + IConsoleCmdExec("exec scripts/game_start.scr 0"); /* Decrease pause counter (was increased from opening load dialog) */ DoCommandP(0, 0, 0, CMD_PAUSE); #ifdef ENABLE_NETWORK @@ -1112,7 +1146,7 @@ void StateGameLoop() CallLandscapeTick(); ClearStorageChanges(true); - AI_RunGameLoop(); + AI::GameLoop(); CallWindowTickEvent(); NewsLoop(); diff --git a/src/rail_cmd.cpp b/src/rail_cmd.cpp index 482306df5..e6be82bf8 100644 --- a/src/rail_cmd.cpp +++ b/src/rail_cmd.cpp @@ -308,7 +308,7 @@ static CommandCost CheckRailSlope(Slope tileh, TrackBits rail_bits, TrackBits ex /* check track/slope combination */ if ((f_new == FOUNDATION_INVALID) || - ((f_new != FOUNDATION_NONE) && (!_settings_game.construction.build_on_slopes || _is_old_ai_company))) { + ((f_new != FOUNDATION_NONE) && (!_settings_game.construction.build_on_slopes))) { return_cmd_error(STR_1000_LAND_SLOPED_IN_WRONG_DIRECTION); } @@ -790,7 +790,6 @@ CommandCost CmdBuildTrainDepot(TileIndex tile, uint32 flags, uint32 p1, uint32 p */ if (tileh != SLOPE_FLAT && ( - _is_old_ai_company || !_settings_game.construction.build_on_slopes || IsSteepSlope(tileh) || !CanBuildDepotByTileh(dir, tileh) diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp index 54ebd80e3..6ed96d653 100644 --- a/src/road_cmd.cpp +++ b/src/road_cmd.cpp @@ -925,10 +925,7 @@ static CommandCost ClearTile_Road(TileIndex tile, byte flags) /* Clear the road if only one piece is on the tile OR the AI tries * to clear town road OR we are not using the DC_AUTO flag */ - if ((CountBits(b) == 1 && GetRoadBits(tile, ROADTYPE_TRAM) == ROAD_NONE) || - ((flags & DC_AI_BUILDING) && GetOtherRoadBits(tile, ROADTYPE_ROAD) == ROAD_NONE && IsRoadOwner(tile, ROADTYPE_ROAD, OWNER_TOWN)) || - !(flags & DC_AUTO) - ) { + if ((CountBits(b) == 1 && GetRoadBits(tile, ROADTYPE_TRAM) == ROAD_NONE) || !(flags & DC_AUTO)) { RoadTypes rts = GetRoadTypes(tile); CommandCost ret(EXPENSES_CONSTRUCTION); for (RoadType rt = ROADTYPE_ROAD; rt < ROADTYPE_END; rt++) { diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index 1b98ef383..4a21b30aa 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -38,6 +38,7 @@ #include "variables.h" #include "autoreplace_gui.h" #include "gfx_func.h" +#include "ai/ai.hpp" #include "settings_type.h" #include "order_func.h" #include "depot_base.h" @@ -617,6 +618,8 @@ static void RoadVehCrash(Vehicle *v) InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, VVW_WIDGET_START_STOP_VEH); + AI::NewEvent(v->owner, new AIEventVehicleCrashed(v->index, v->tile)); + SetDParam(0, pass); AddNewsItem( (pass == 1) ? @@ -803,6 +806,7 @@ static void RoadVehArrivesAt(const Vehicle *v, Station *st) v->index, st->index ); + AI::NewEvent(v->owner, new AIEventStationFirstVehicle(st->index, v->index)); } } else { /* Check if station was ever visited before */ @@ -815,6 +819,7 @@ static void RoadVehArrivesAt(const Vehicle *v, Station *st) v->index, st->index ); + AI::NewEvent(v->owner, new AIEventStationFirstVehicle(st->index, v->index)); } } } diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 4aa4a2eee..13aeac2af 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -48,6 +48,7 @@ #include "../company_func.h" #include "../command_func.h" #include "../road_cmd.h" +#include "../ai/ai.hpp" #include "table/strings.h" @@ -468,6 +469,14 @@ bool AfterLoadGame() /* Update all vehicles */ AfterLoadVehicles(true); + /* Make sure there is an AI attached to an AI company */ + { + Company *c; + FOR_ALL_COMPANIES(c) { + if (c->is_ai && c->ai_instance == NULL) AI::StartNew(c->index); + } + } + /* Update all waypoints */ if (CheckSavegameVersion(12)) FixOldWaypoints(); diff --git a/src/saveload/ai_sl.cpp b/src/saveload/ai_sl.cpp index b4d16a6b5..5e444ea9f 100644 --- a/src/saveload/ai_sl.cpp +++ b/src/saveload/ai_sl.cpp @@ -1,79 +1,87 @@ /* $Id$ */ -/** @file ai_sl.cpp Code handling saving and loading of old AI + new AI initialisation after game load */ +/** @file ai_sl.cpp Handles the saveload part of the AIs */ #include "../stdafx.h" -#include "../ai/ai.h" -#include "../ai/default/default.h" - +#include "../openttd.h" +#include "../company_base.h" +#include "../company_func.h" +#include "../debug.h" #include "saveload.h" +#include "../settings_type.h" +#include "../string_func.h" +#include "../ai/ai.hpp" +#include "../ai/ai_config.hpp" -static const SaveLoad _company_ai_desc[] = { - SLE_VAR(CompanyAI, state, SLE_UINT8), - SLE_VAR(CompanyAI, tick, SLE_UINT8), - SLE_CONDVAR(CompanyAI, state_counter, SLE_FILE_U16 | SLE_VAR_U32, 0, 12), - SLE_CONDVAR(CompanyAI, state_counter, SLE_UINT32, 13, SL_MAX_VERSION), - SLE_VAR(CompanyAI, timeout_counter, SLE_UINT16), - - SLE_VAR(CompanyAI, state_mode, SLE_UINT8), - SLE_VAR(CompanyAI, banned_tile_count, SLE_UINT8), - SLE_VAR(CompanyAI, railtype_to_use, SLE_UINT8), - - SLE_VAR(CompanyAI, cargo_type, SLE_UINT8), - SLE_VAR(CompanyAI, num_wagons, SLE_UINT8), - SLE_VAR(CompanyAI, build_kind, SLE_UINT8), - SLE_VAR(CompanyAI, num_build_rec, SLE_UINT8), - SLE_VAR(CompanyAI, num_loco_to_build, SLE_UINT8), - SLE_VAR(CompanyAI, num_want_fullload, SLE_UINT8), - - SLE_VAR(CompanyAI, route_type_mask, SLE_UINT8), - - SLE_CONDVAR(CompanyAI, start_tile_a, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), - SLE_CONDVAR(CompanyAI, start_tile_a, SLE_UINT32, 6, SL_MAX_VERSION), - SLE_CONDVAR(CompanyAI, cur_tile_a, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), - SLE_CONDVAR(CompanyAI, cur_tile_a, SLE_UINT32, 6, SL_MAX_VERSION), - SLE_VAR(CompanyAI, start_dir_a, SLE_UINT8), - SLE_VAR(CompanyAI, cur_dir_a, SLE_UINT8), - - SLE_CONDVAR(CompanyAI, start_tile_b, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), - SLE_CONDVAR(CompanyAI, start_tile_b, SLE_UINT32, 6, SL_MAX_VERSION), - SLE_CONDVAR(CompanyAI, cur_tile_b, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), - SLE_CONDVAR(CompanyAI, cur_tile_b, SLE_UINT32, 6, SL_MAX_VERSION), - SLE_VAR(CompanyAI, start_dir_b, SLE_UINT8), - SLE_VAR(CompanyAI, cur_dir_b, SLE_UINT8), - - SLE_REF(CompanyAI, cur_veh, REF_VEHICLE), - - SLE_ARR(CompanyAI, wagon_list, SLE_UINT16, 9), - SLE_ARR(CompanyAI, order_list_blocks, SLE_UINT8, 20), - SLE_ARR(CompanyAI, banned_tiles, SLE_UINT16, 16), - - SLE_CONDNULL(64, 2, SL_MAX_VERSION), - SLE_END() -}; +static char _ai_saveload_ainame[64]; +static char _ai_company_convert_array[1024]; -static const SaveLoad _company_ai_build_rec_desc[] = { - SLE_CONDVAR(AiBuildRec, spec_tile, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), - SLE_CONDVAR(AiBuildRec, spec_tile, SLE_UINT32, 6, SL_MAX_VERSION), - SLE_CONDVAR(AiBuildRec, use_tile, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), - SLE_CONDVAR(AiBuildRec, use_tile, SLE_UINT32, 6, SL_MAX_VERSION), - SLE_VAR(AiBuildRec, rand_rng, SLE_UINT8), - SLE_VAR(AiBuildRec, cur_building_rule, SLE_UINT8), - SLE_VAR(AiBuildRec, unk6, SLE_UINT8), - SLE_VAR(AiBuildRec, unk7, SLE_UINT8), - SLE_VAR(AiBuildRec, buildcmd_a, SLE_UINT8), - SLE_VAR(AiBuildRec, buildcmd_b, SLE_UINT8), - SLE_VAR(AiBuildRec, direction, SLE_UINT8), - SLE_VAR(AiBuildRec, cargo, SLE_UINT8), +static const SaveLoad _ai_company[] = { + SLEG_STR(_ai_saveload_ainame, SLE_STRB), + SLEG_STR(_ai_company_convert_array, SLE_STRB), SLE_END() }; +static void SaveReal_AIPL(int *index_ptr) +{ + CompanyID index = (CompanyID)*index_ptr; + AIConfig *config = AIConfig::GetConfig(index); + + ttd_strlcpy(_ai_saveload_ainame, config->GetName(), lengthof(_ai_saveload_ainame)); + + _ai_company_convert_array[0] = '\0'; + config->SettingsToString(_ai_company_convert_array, lengthof(_ai_company_convert_array)); + + SlObject(NULL, _ai_company); + /* If the AI was active, store his data too */ + if (IsValidCompanyID(index) && !IsHumanCompany(index)) AI::Save(index); +} + +static void Load_AIPL() +{ + /* Free all current data */ + for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) { + AIConfig::GetConfig(c)->ChangeAI(NULL); + } + + CompanyID index; + while ((index = (CompanyID)SlIterateArray()) != (CompanyID)-1) { + AIConfig *config = AIConfig::GetConfig(index); + SlObject(NULL, _ai_company); + + if (_ai_saveload_ainame[0] == '\0' || AI::GetCompanyInfo(_ai_saveload_ainame) == NULL) { + if (strcmp(_ai_saveload_ainame, "%_dummy") != 0) { + DEBUG(ai, 0, "The savegame has an AI by the name '%s' which is no longer available.", _ai_saveload_ainame); + DEBUG(ai, 0, "A random other AI will be loaded in its place."); + } else { + DEBUG(ai, 0, "The savegame had no AIs available at the time of saving."); + DEBUG(ai, 0, "A random available AI will be loaded now."); + } + config->ChangeAI(NULL); + } else { + config->ChangeAI(_ai_saveload_ainame); + } + + config->StringToSettings(_ai_company_convert_array); + + /* Start the AI directly if it was active in the savegame */ + if (IsValidCompanyID(index) && !IsHumanCompany(index)) { + AI::StartNew(index); + AI::Load(index); + } + } +} -void SaveLoad_AI(CompanyID company) +static void Save_AIPL() { - CompanyAI *cai = &_companies_ai[company]; - SlObject(cai, _company_ai_desc); - for (int i = 0; i != cai->num_build_rec; i++) { - SlObject(&cai->src + i, _company_ai_build_rec_desc); + for (int i = COMPANY_FIRST; i < MAX_COMPANIES; i++) { + if (!AIConfig::GetConfig((CompanyID)i)->HasAI()) continue; + + SlSetArrayIndex(i); + SlAutolength((AutolengthProc *)SaveReal_AIPL, &i); } } + +extern const ChunkHandler _ai_chunk_handlers[] = { + { 'AIPL', Save_AIPL, Load_AIPL, CH_ARRAY | CH_LAST}, +}; diff --git a/src/saveload/company_sl.cpp b/src/saveload/company_sl.cpp index 933cc0bde..ead54be0d 100644 --- a/src/saveload/company_sl.cpp +++ b/src/saveload/company_sl.cpp @@ -6,8 +6,7 @@ #include "../company_base.h" #include "../company_func.h" #include "../network/network.h" -#include "../ai/ai.h" -#include "../ai/trolly/trolly.h" +#include "../ai/ai.hpp" #include "../company_manager_face.h" #include "saveload.h" @@ -130,7 +129,8 @@ static const SaveLoad _company_desc[] = { SLE_CONDARR(Company, yearly_expenses, SLE_FILE_I32 | SLE_VAR_I64, 3 * 13, 0, 1), SLE_CONDARR(Company, yearly_expenses, SLE_INT64, 3 * 13, 2, SL_MAX_VERSION), - SLE_CONDVAR(Company, is_ai, SLE_BOOL, 2, SL_MAX_VERSION), + SLE_CONDVAR(Company, is_ai, SLE_BOOL, 2, SL_MAX_VERSION), + SLE_CONDVAR(Company, is_noai, SLE_BOOL, 107, SL_MAX_VERSION), SLE_CONDNULL(1, 4, 99), /* Engine renewal settings */ @@ -141,7 +141,7 @@ static const SaveLoad _company_desc[] = { SLE_CONDVAR(Company, engine_renew_money, SLE_UINT32, 16, SL_MAX_VERSION), SLE_CONDVAR(Company, renew_keep_length, SLE_BOOL, 2, SL_MAX_VERSION), // added with 16.1, but was blank since 2 - /* reserve extra space in savegame here. (currently 63 bytes) */ + /* Reserve extra space in savegame here. (currently 63 bytes) */ SLE_CONDNULL(63, 2, SL_MAX_VERSION), SLE_END() @@ -162,6 +162,51 @@ static const SaveLoad _company_economy_desc[] = { SLE_END() }; +/* We do need to read this single value, as the bigger it gets, the more data is stored */ +struct CompanyOldAI { + uint8 num_build_rec; +}; + +static const SaveLoad _company_ai_desc[] = { + SLE_CONDNULL(2, 0, 106), + SLE_CONDNULL(2, 0, 12), + SLE_CONDNULL(4, 13, 106), + SLE_CONDNULL(8, 0, 106), + SLE_CONDVAR(CompanyOldAI, num_build_rec, SLE_UINT8, 0, 106), + SLE_CONDNULL(3, 0, 106), + + SLE_CONDNULL(2, 0, 5), + SLE_CONDNULL(4, 6, 106), + SLE_CONDNULL(2, 0, 5), + SLE_CONDNULL(4, 6, 106), + SLE_CONDNULL(2, 0, 106), + + SLE_CONDNULL(2, 0, 5), + SLE_CONDNULL(4, 6, 106), + SLE_CONDNULL(2, 0, 5), + SLE_CONDNULL(4, 6, 106), + SLE_CONDNULL(2, 0, 106), + + SLE_CONDNULL(2, 0, 68), + SLE_CONDNULL(4, 69, 106), + + SLE_CONDNULL(18, 0, 106), + SLE_CONDNULL(20, 0, 106), + SLE_CONDNULL(32, 0, 106), + + SLE_CONDNULL(64, 2, 106), + SLE_END() +}; + +static const SaveLoad _company_ai_build_rec_desc[] = { + SLE_CONDNULL(2, 0, 5), + SLE_CONDNULL(4, 6, 106), + SLE_CONDNULL(2, 0, 5), + SLE_CONDNULL(4, 6, 106), + SLE_CONDNULL(8, 0, 106), + SLE_END() +}; + static const SaveLoad _company_livery_desc[] = { SLE_CONDVAR(Livery, in_use, SLE_BOOL, 34, SL_MAX_VERSION), SLE_CONDVAR(Livery, colour1, SLE_UINT8, 34, SL_MAX_VERSION), @@ -175,10 +220,15 @@ static void SaveLoad_PLYR(Company *c) SlObject(c, _company_desc); - /* Write AI? */ - if (!IsHumanCompany(c->index)) { - extern void SaveLoad_AI(CompanyID company); - SaveLoad_AI(c->index); + /* Keep backwards compatible for savegames, so load the old AI block */ + if (!IsHumanCompany(c->index) && !c->is_noai) { + CompanyOldAI old_ai; + char nothing; + + SlObject(&old_ai, _company_ai_desc); + for (i = 0; i != old_ai.num_build_rec; i++) { + SlObject(¬hing, _company_ai_build_rec_desc); + } } /* Write economy */ @@ -225,13 +275,7 @@ static void Load_PLYR() Company *c = new (index) Company(); SaveLoad_PLYR(c); _company_colours[index] = c->colour; - - /* This is needed so an AI is attached to a loaded AI */ - if (c->is_ai && (!_networking || _network_server) && _ai.enabled) { - /* Clear the memory of the new AI, otherwise we might be doing wrong things. */ - memset(&_companies_ainew[index], 0, sizeof(CompanyAiNew)); - AI_StartNewAI(c->index); - } + c->is_noai = true; } } diff --git a/src/saveload/oldloader.cpp b/src/saveload/oldloader.cpp index 9e503077b..930e8fa2b 100644 --- a/src/saveload/oldloader.cpp +++ b/src/saveload/oldloader.cpp @@ -17,8 +17,7 @@ #include "../debug.h" #include "../depot_base.h" #include "../newgrf_config.h" -#include "../ai/ai.h" -#include "../ai/default/default.h" +#include "../ai/ai.hpp" #include "../zoom_func.h" #include "../functions.h" #include "../date_func.h" @@ -805,145 +804,6 @@ static bool OldCompanyEconomy(LoadgameState *ls, int num) return true; } -static const OldChunks _company_ai_build_rec_chunk[] = { - OCL_SVAR( OC_TILE, AiBuildRec, spec_tile ), - OCL_SVAR( OC_TILE, AiBuildRec, use_tile ), - OCL_SVAR( OC_UINT8, AiBuildRec, rand_rng ), - OCL_SVAR( OC_UINT8, AiBuildRec, cur_building_rule ), - OCL_SVAR( OC_UINT8, AiBuildRec, unk6 ), - OCL_SVAR( OC_UINT8, AiBuildRec, unk7 ), - OCL_SVAR( OC_UINT8, AiBuildRec, buildcmd_a ), - OCL_SVAR( OC_UINT8, AiBuildRec, buildcmd_b ), - OCL_SVAR( OC_UINT8, AiBuildRec, direction ), - OCL_SVAR( OC_UINT8, AiBuildRec, cargo ), - - OCL_NULL( 8 ), ///< Junk... - - OCL_END() -}; - -static bool OldLoadAIBuildRec(LoadgameState *ls, int num) -{ - Company *c = GetCompany(_current_company_id); - - switch (num) { - case 0: return LoadChunk(ls, &_companies_ai[c->index].src, _company_ai_build_rec_chunk); - case 1: return LoadChunk(ls, &_companies_ai[c->index].dst, _company_ai_build_rec_chunk); - case 2: return LoadChunk(ls, &_companies_ai[c->index].mid1, _company_ai_build_rec_chunk); - case 3: return LoadChunk(ls, &_companies_ai[c->index].mid2, _company_ai_build_rec_chunk); - } - - return false; -} -static const OldChunks _company_ai_chunk[] = { - OCL_SVAR( OC_UINT8, CompanyAI, state ), - OCL_NULL( 1 ), ///< Junk - OCL_SVAR( OC_UINT8, CompanyAI, state_mode ), - OCL_SVAR( OC_UINT16, CompanyAI, state_counter ), - OCL_SVAR( OC_UINT16, CompanyAI, timeout_counter ), - - OCL_CHUNK( 4, OldLoadAIBuildRec ), - - OCL_NULL( 20 ), ///< More junk - - OCL_SVAR( OC_UINT8, CompanyAI, cargo_type ), - OCL_SVAR( OC_UINT8, CompanyAI, num_wagons ), - OCL_SVAR( OC_UINT8, CompanyAI, build_kind ), - OCL_SVAR( OC_UINT8, CompanyAI, num_build_rec ), - OCL_SVAR( OC_UINT8, CompanyAI, num_loco_to_build ), - OCL_SVAR( OC_UINT8, CompanyAI, num_want_fullload ), - - OCL_NULL( 14 ), ///< Oh no more junk :| - - OCL_NULL( 2 ), ///< Loco-id, not used - - OCL_SVAR( OC_UINT16, CompanyAI, wagon_list[0] ), - OCL_SVAR( OC_UINT16, CompanyAI, wagon_list[1] ), - OCL_SVAR( OC_UINT16, CompanyAI, wagon_list[2] ), - OCL_SVAR( OC_UINT16, CompanyAI, wagon_list[3] ), - OCL_SVAR( OC_UINT16, CompanyAI, wagon_list[4] ), - OCL_SVAR( OC_UINT16, CompanyAI, wagon_list[5] ), - OCL_SVAR( OC_UINT16, CompanyAI, wagon_list[6] ), - OCL_SVAR( OC_UINT16, CompanyAI, wagon_list[7] ), - OCL_SVAR( OC_UINT16, CompanyAI, wagon_list[8] ), - OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[0] ), - OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[1] ), - OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[2] ), - OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[3] ), - OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[4] ), - OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[5] ), - OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[6] ), - OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[7] ), - OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[8] ), - OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[9] ), - OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[10] ), - OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[11] ), - OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[12] ), - OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[13] ), - OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[14] ), - OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[15] ), - OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[16] ), - OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[17] ), - OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[18] ), - OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[19] ), - - OCL_SVAR( OC_UINT16, CompanyAI, start_tile_a ), - OCL_SVAR( OC_UINT16, CompanyAI, start_tile_b ), - OCL_SVAR( OC_UINT16, CompanyAI, cur_tile_a ), - OCL_SVAR( OC_UINT16, CompanyAI, cur_tile_b ), - - OCL_SVAR( OC_UINT8, CompanyAI, start_dir_a ), - OCL_SVAR( OC_UINT8, CompanyAI, start_dir_b ), - OCL_SVAR( OC_UINT8, CompanyAI, cur_dir_a ), - OCL_SVAR( OC_UINT8, CompanyAI, cur_dir_b ), - - OCL_SVAR( OC_UINT8, CompanyAI, banned_tile_count ), - - OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[0] ), - OCL_SVAR( OC_UINT8, CompanyAI, banned_val[0] ), - OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[1] ), - OCL_SVAR( OC_UINT8, CompanyAI, banned_val[1] ), - OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[2] ), - OCL_SVAR( OC_UINT8, CompanyAI, banned_val[2] ), - OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[3] ), - OCL_SVAR( OC_UINT8, CompanyAI, banned_val[3] ), - OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[4] ), - OCL_SVAR( OC_UINT8, CompanyAI, banned_val[4] ), - OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[5] ), - OCL_SVAR( OC_UINT8, CompanyAI, banned_val[5] ), - OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[6] ), - OCL_SVAR( OC_UINT8, CompanyAI, banned_val[6] ), - OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[7] ), - OCL_SVAR( OC_UINT8, CompanyAI, banned_val[7] ), - OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[8] ), - OCL_SVAR( OC_UINT8, CompanyAI, banned_val[8] ), - OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[9] ), - OCL_SVAR( OC_UINT8, CompanyAI, banned_val[9] ), - OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[10] ), - OCL_SVAR( OC_UINT8, CompanyAI, banned_val[10] ), - OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[11] ), - OCL_SVAR( OC_UINT8, CompanyAI, banned_val[11] ), - OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[12] ), - OCL_SVAR( OC_UINT8, CompanyAI, banned_val[12] ), - OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[13] ), - OCL_SVAR( OC_UINT8, CompanyAI, banned_val[13] ), - OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[14] ), - OCL_SVAR( OC_UINT8, CompanyAI, banned_val[14] ), - OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[15] ), - OCL_SVAR( OC_UINT8, CompanyAI, banned_val[15] ), - - OCL_SVAR( OC_UINT8, CompanyAI, railtype_to_use ), - OCL_SVAR( OC_UINT8, CompanyAI, route_type_mask ), - - OCL_END() -}; - -static bool OldCompanyAI(LoadgameState *ls, int num) -{ - return LoadChunk(ls, &_companies_ai[_current_company_id], _company_ai_chunk); -} - -uint8 ai_tick; static const OldChunks _company_chunk[] = { OCL_VAR ( OC_UINT16, 1, &_old_string_id ), OCL_SVAR( OC_UINT32, Company, name_2 ), @@ -970,10 +830,10 @@ static const OldChunks _company_chunk[] = { OCL_SVAR( OC_TILE, Company, last_build_coordinate ), OCL_SVAR( OC_UINT8, Company, num_valid_stat_ent ), - OCL_CHUNK( 1, OldCompanyAI ), + OCL_NULL( 230 ), // Old AI OCL_SVAR( OC_UINT8, Company, block_preview ), - OCL_VAR( OC_UINT8, 1, &ai_tick ), + OCL_NULL( 1 ), // Old AI OCL_SVAR( OC_UINT8, Company, avail_railtypes ), OCL_SVAR( OC_TILE, Company, location_of_HQ ), OCL_SVAR( OC_UINT8, Company, share_owners[0] ), @@ -1001,7 +861,6 @@ static bool LoadOldCompany(LoadgameState *ls, int num) c->name_1 = RemapOldStringID(_old_string_id); c->president_name_1 = RemapOldStringID(_old_string_id_2); - _companies_ai[_current_company_id].tick = ai_tick; if (num == 0) { /* If the first company has no name, make sure we call it UNNAMED */ @@ -1023,15 +882,6 @@ static bool LoadOldCompany(LoadgameState *ls, int num) _company_colours[num] = c->colour; c->inaugurated_year -= ORIGINAL_BASE_YEAR; - /* State 20 for AI companies is sell vehicle. Since the AI struct is not - * really figured out as of now, _companies_ai[c->index].cur_veh; needed for 'sell vehicle' - * is NULL and the function will crash. To fix this, just change the state - * to some harmless state, like 'loop vehicle'; 1 */ - if (!IsHumanCompany((CompanyID)num) && _companies_ai[c->index].state == 20) _companies_ai[c->index].state = 1; - - if (c->is_ai && (!_networking || _network_server) && _ai.enabled) - AI_StartNewAI(c->index); - return true; } diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp index 904c7ea44..6e41314f3 100644 --- a/src/saveload/saveload.cpp +++ b/src/saveload/saveload.cpp @@ -42,7 +42,7 @@ #include <list> -extern const uint16 SAVEGAME_VERSION = 106; +extern const uint16 SAVEGAME_VERSION = 107; SavegameType _savegame_type; ///< type of savegame we are loading @@ -1318,6 +1318,7 @@ extern const ChunkHandler _station_chunk_handlers[]; extern const ChunkHandler _industry_chunk_handlers[]; extern const ChunkHandler _economy_chunk_handlers[]; extern const ChunkHandler _subsidy_chunk_handlers[]; +extern const ChunkHandler _ai_chunk_handlers[]; extern const ChunkHandler _animated_tile_chunk_handlers[]; extern const ChunkHandler _newgrf_chunk_handlers[]; extern const ChunkHandler _group_chunk_handlers[]; @@ -1343,6 +1344,7 @@ static const ChunkHandler * const _chunk_handlers[] = { _sign_chunk_handlers, _station_chunk_handlers, _company_chunk_handlers, + _ai_chunk_handlers, _animated_tile_chunk_handlers, _newgrf_chunk_handlers, _group_chunk_handlers, diff --git a/src/script/squirrel.cpp b/src/script/squirrel.cpp new file mode 100644 index 000000000..b75833f65 --- /dev/null +++ b/src/script/squirrel.cpp @@ -0,0 +1,444 @@ +/* $Id$ */ + +/** @file squirrel.cpp the implementation of the Squirrel class. It handles all Squirrel-stuff and gives a nice API back to work with. */ + +#include <squirrel.h> +#include <stdarg.h> +#include "../stdafx.h" +#include "../debug.h" +#include "squirrel.hpp" +#include "squirrel_std.hpp" +#include "../fileio_func.h" +#include <sqstdaux.h> +#include <../squirrel/sqpcheader.h> +#include <../squirrel/sqvm.h> + +void Squirrel::CompileError(HSQUIRRELVM vm, const SQChar *desc, const SQChar *source, SQInteger line, SQInteger column) +{ + SQChar buf[1024]; + +#ifdef _SQ64 + scsnprintf(buf, lengthof(buf), _SC("Error %s:%ld/%ld: %s"), source, line, column, desc); +#else + scsnprintf(buf, lengthof(buf), _SC("Error %s:%d/%d: %s"), source, line, column, desc); +#endif + + /* Check if we have a custom print function */ + SQPrintFunc *func = ((Squirrel *)sq_getforeignptr(vm))->print_func; + if (func == NULL) { + scfprintf(stderr, _SC("%s"), buf); + } else { + (*func)(true, buf); + } +} + +void Squirrel::ErrorPrintFunc(HSQUIRRELVM vm, const SQChar *s, ...) +{ + va_list arglist; + SQChar buf[1024]; + + va_start(arglist, s); + scvsnprintf(buf, lengthof(buf), s, arglist); + va_end(arglist); + + /* Check if we have a custom print function */ + SQPrintFunc *func = ((Squirrel *)sq_getforeignptr(vm))->print_func; + if (func == NULL) { + scfprintf(stderr, _SC("%s"), buf); + } else { + (*func)(true, buf); + } +} + +void Squirrel::RunError(HSQUIRRELVM vm, const SQChar *error) +{ + /* Set the print function to something that prints to stderr */ + SQPRINTFUNCTION pf = sq_getprintfunc(vm); + sq_setprintfunc(vm, &Squirrel::ErrorPrintFunc); + + /* Check if we have a custom print function */ + SQChar buf[1024]; + scsnprintf(buf, lengthof(buf), _SC("Your script made an error: %s\n"), error); + SQPrintFunc *func = ((Squirrel *)sq_getforeignptr(vm))->print_func; + if (func == NULL) { + scfprintf(stderr, _SC("%s"), buf); + } else { + (*func)(true, buf); + } + + /* Print below the error the stack, so the users knows what is happening */ + sqstd_printcallstack(vm); + /* Reset the old print function */ + sq_setprintfunc(vm, pf); +} + +SQInteger Squirrel::_RunError(HSQUIRRELVM vm) +{ + const SQChar *sErr = 0; + + if (sq_gettop(vm) >= 1) { + if (SQ_SUCCEEDED(sq_getstring(vm, -1, &sErr))) { + Squirrel::RunError(vm, sErr); + return 0; + } + } + + Squirrel::RunError(vm, _SC("unknown error")); + return 0; +} + +void Squirrel::PrintFunc(HSQUIRRELVM vm, const SQChar *s, ...) +{ + va_list arglist; + SQChar buf[1024]; + + va_start(arglist, s); + scvsnprintf(buf, lengthof(buf) - 2, s, arglist); + va_end(arglist); + scstrcat(buf, _SC("\n")); + + /* Check if we have a custom print function */ + SQPrintFunc *func = ((Squirrel *)sq_getforeignptr(vm))->print_func; + if (func == NULL) { + scprintf(_SC("%s"), buf); + } else { + (*func)(false, buf); + } +} + +void Squirrel::AddMethod(const char *method_name, SQFUNCTION proc, uint nparam, const char *params, void *userdata, int size) +{ + sq_pushstring(this->vm, OTTD2FS(method_name), -1); + + if (size != 0) { + void *ptr = sq_newuserdata(vm, size); + memcpy(ptr, userdata, size); + } + + sq_newclosure(this->vm, proc, size != 0 ? 1 : 0); + if (nparam != 0) sq_setparamscheck(this->vm, nparam, OTTD2FS(params)); + sq_setnativeclosurename(this->vm, -1, OTTD2FS(method_name)); + sq_newslot(this->vm, -3, SQFalse); +} + +void Squirrel::AddConst(const char *var_name, int value) +{ + sq_pushstring(this->vm, OTTD2FS(var_name), -1); + sq_pushinteger(this->vm, value); + sq_newslot(this->vm, -3, SQTrue); +} + +void Squirrel::AddClassBegin(const char *class_name) +{ + sq_pushroottable(this->vm); + sq_pushstring(this->vm, OTTD2FS(class_name), -1); + sq_newclass(this->vm, SQFalse); +} + +void Squirrel::AddClassBegin(const char *class_name, const char *parent_class) +{ + sq_pushroottable(this->vm); + sq_pushstring(this->vm, OTTD2FS(class_name), -1); + sq_pushstring(this->vm, OTTD2FS(parent_class), -1); + if (SQ_FAILED(sq_get(this->vm, -3))) { + DEBUG(misc, 0, "[squirrel] Failed to initialize class '%s' based on parent class '%s'", class_name, parent_class); + DEBUG(misc, 0, "[squirrel] Make sure that '%s' exists before trying to define '%s'", parent_class, class_name); + return; + } + sq_newclass(this->vm, SQTrue); +} + +void Squirrel::AddClassEnd() +{ + sq_newslot(vm, -3, SQFalse); + sq_pop(vm, 1); +} + +bool Squirrel::MethodExists(HSQOBJECT instance, const char *method_name) +{ + int top = sq_gettop(this->vm); + /* Go to the instance-root */ + sq_pushobject(this->vm, instance); + /* Find the function-name inside the script */ + sq_pushstring(this->vm, OTTD2FS(method_name), -1); + if (SQ_FAILED(sq_get(this->vm, -2))) { + sq_settop(this->vm, top); + return false; + } + sq_settop(this->vm, top); + return true; +} + +bool Squirrel::Resume(int suspend) +{ + sq_resumecatch(this->vm, suspend); + return this->vm->_suspended != 0; +} + +bool Squirrel::CallMethod(HSQOBJECT instance, const char *method_name, HSQOBJECT *ret, int suspend) +{ + /* Store the current top */ + int top = sq_gettop(this->vm); + /* Go to the instance-root */ + sq_pushobject(this->vm, instance); + /* Find the function-name inside the script */ + sq_pushstring(this->vm, OTTD2FS(method_name), -1); + if (SQ_FAILED(sq_get(this->vm, -2))) { + DEBUG(misc, 0, "[squirrel] Could not find '%s' in the class", method_name); + sq_settop(this->vm, top); + return false; + } + /* Call the method */ + sq_pushobject(this->vm, instance); + sq_call(this->vm, 1, ret == NULL ? SQFalse : SQTrue, SQTrue, suspend); + if (ret != NULL) sq_getstackobj(vm, -1, ret); + /* Reset the top */ + sq_settop(this->vm, top); + + return this->vm->_suspended != 0; +} + +/* static */ bool Squirrel::CreateClassInstanceVM(HSQUIRRELVM vm, const char *class_name, void *real_instance, HSQOBJECT *instance, SQRELEASEHOOK release_hook) +{ + int oldtop = sq_gettop(vm); + + /* First, find the class */ + sq_pushroottable(vm); + sq_pushstring(vm, OTTD2FS(class_name), -1); + if (SQ_FAILED(sq_get(vm, -2))) { + DEBUG(misc, 0, "[squirrel] Failed to find class by the name '%s'", class_name); + sq_settop(vm, oldtop); + return false; + } + + /* Create the instance */ + if (SQ_FAILED(sq_createinstance(vm, -1))) { + DEBUG(misc, 0, "[squirrel] Failed to create instance for class '%s'", class_name); + sq_settop(vm, oldtop); + return false; + } + + if (instance != NULL) { + /* Find our instance */ + sq_getstackobj(vm, -1, instance); + /* Add a reference to it, so it survives for ever */ + sq_addref(vm, instance); + } + sq_remove(vm, -2); // Class-name + sq_remove(vm, -2); // Root-table + + /* Store it in the class */ + sq_setinstanceup(vm, -1, real_instance); + if (release_hook != NULL) sq_setreleasehook(vm, -1, release_hook); + + if (instance != NULL) sq_settop(vm, oldtop); + + return true; +} + +bool Squirrel::CreateClassInstance(const char *class_name, void *real_instance, HSQOBJECT *instance) +{ + return Squirrel::CreateClassInstanceVM(this->vm, class_name, real_instance, instance, NULL); +} + +Squirrel::Squirrel() +{ + this->vm = sq_open(1024); + this->print_func = NULL; + this->global_pointer = NULL; + + /* Handle compile-errors ourself, so we can display it nicely */ + sq_setcompilererrorhandler(this->vm, &Squirrel::CompileError); + sq_notifyallexceptions(this->vm, SQTrue); + /* Set a good print-function */ + sq_setprintfunc(this->vm, &Squirrel::PrintFunc); + /* Handle runtime-errors ourself, so we can display it nicely */ + sq_newclosure(this->vm, &Squirrel::_RunError, 0); + sq_seterrorhandler(this->vm); + + /* Set the foreigh pointer, so we can always find this instance from within the VM */ + sq_setforeignptr(this->vm, this); + + sq_pushroottable(this->vm); + squirrel_register_global_std(this); +} + +class SQFile { +private: + FILE *file; + size_t size; + size_t pos; + +public: + SQFile(FILE *file, size_t size) : file(file), size(size), pos(0) {} + + size_t Read(void *buf, size_t elemsize, size_t count) + { + assert(elemsize != 0); + if (this->pos + (elemsize * count) > this->size) { + count = (this->size - this->pos) / elemsize; + } + if (count == 0) return 0; + size_t ret = fread(buf, elemsize, count, this->file); + this->pos += ret * elemsize; + return ret; + } +}; + +static SQInteger _io_file_lexfeed_ASCII(SQUserPointer file) +{ + char c; + if (((SQFile *)file)->Read(&c, sizeof(c), 1) > 0) return c; + return 0; +} + +static SQInteger _io_file_lexfeed_UTF8(SQUserPointer file) +{ + static const SQInteger utf8_lengths[16] = + { + 1, 1, 1, 1, 1, 1, 1, 1, /* 0000 to 0111 : 1 byte (plain ASCII) */ + 0, 0, 0, 0, /* 1000 to 1011 : not valid */ + 2, 2, /* 1100, 1101 : 2 bytes */ + 3, /* 1110 : 3 bytes */ + 4 /* 1111 : 4 bytes */ + }; + static unsigned char byte_masks[5] = {0, 0, 0x1F, 0x0F, 0x07}; + unsigned char inchar; + SQInteger c = 0; + if (((SQFile *)file)->Read(&inchar, sizeof(inchar), 1) != 1) return 0; + c = inchar; + + if (c >= 0x80) { + SQInteger tmp; + SQInteger codelen = utf8_lengths[c >> 4]; + if (codelen == 0) return 0; + + tmp = c & byte_masks[codelen]; + for (SQInteger n = 0; n < codelen - 1; n++) { + tmp <<= 6; + if (((SQFile *)file)->Read(&inchar, sizeof(inchar), 1) != 1) return 0; + tmp |= inchar & 0x3F; + } + c = tmp; + } + return c; +} + +static SQInteger _io_file_lexfeed_UCS2_LE(SQUserPointer file) +{ + wchar_t c; + if (((SQFile *)file)->Read(&c, sizeof(c), 1) > 0) return (SQChar)c; + return 0; +} + +static SQInteger _io_file_lexfeed_UCS2_BE(SQUserPointer file) +{ + unsigned short c; + if (((SQFile *)file)->Read(&c, sizeof(c), 1) > 0) { + c = ((c >> 8) & 0x00FF)| ((c << 8) & 0xFF00); + return (SQChar)c; + } + return 0; +} + +static SQInteger _io_file_read(SQUserPointer file, SQUserPointer buf, SQInteger size) +{ + SQInteger ret = ((SQFile *)file)->Read(buf, 1, size); + if (ret == 0) return -1; + return ret; +} + +/* static */ SQRESULT Squirrel::LoadFile(HSQUIRRELVM vm, const char *filename, SQBool printerror) +{ + size_t size; + FILE *file = FioFOpenFile(filename, "rb", AI_DIR, &size); + SQInteger ret; + unsigned short us; + unsigned char uc; + SQLEXREADFUNC func; + + if (file != NULL) { + SQFile f(file, size); + ret = fread(&us, 1, sizeof(us), file); + /* Most likely an empty file */ + if (ret != 2) us = 0; + + switch (us) { + case SQ_BYTECODE_STREAM_TAG: { // BYTECODE + fseek(file, -2, SEEK_CUR); + if (SQ_SUCCEEDED(sq_readclosure(vm, _io_file_read, &f))) { + FioFCloseFile(file); + return SQ_OK; + } + FioFCloseFile(file); + return sq_throwerror(vm, _SC("Couldn't read bytecode")); + } + case 0xFFFE: func = _io_file_lexfeed_UCS2_BE; break; // UTF-16 little endian + case 0xFEFF: func = _io_file_lexfeed_UCS2_LE; break; // UTF-16 big endian + case 0xBBEF: // UTF-8 + if (fread(&uc, 1, sizeof(uc), file) == 0) { + FioFCloseFile(file); + return sq_throwerror(vm, _SC("I/O error")); + } + if (uc != 0xBF) { + FioFCloseFile(file); + return sq_throwerror(vm, _SC("Unrecognized encoding")); + } + func = _io_file_lexfeed_UTF8; + break; + default: func = _io_file_lexfeed_ASCII; fseek(file, -2, SEEK_CUR); break; // ASCII + } + + if (SQ_SUCCEEDED(sq_compile(vm, func, &f, OTTD2FS(filename), printerror))) { + FioFCloseFile(file); + return SQ_OK; + } + FioFCloseFile(file); + return SQ_ERROR; + } + return sq_throwerror(vm, _SC("cannot open the file")); +} + +/* static */ bool Squirrel::LoadScript(HSQUIRRELVM vm, const char *script, bool in_root) +{ + /* Make sure we are always in the root-table */ + if (in_root) sq_pushroottable(vm); + + /* Load and run the script */ + if (SQ_SUCCEEDED(LoadFile(vm, script, SQTrue))) { + sq_push(vm, -2); + if (SQ_SUCCEEDED(sq_call(vm, 1, SQFalse, SQTrue))) { + sq_pop(vm, 1); + return true; + } + } + + DEBUG(misc, 0, "[squirrel] Failed to compile '%s'", script); + return false; +} + +bool Squirrel::LoadScript(const char *script) +{ + return LoadScript(this->vm, script); +} + +Squirrel::~Squirrel() +{ + /* Clean up the stuff */ + sq_pop(this->vm, 1); + sq_close(this->vm); +} + +void Squirrel::InsertResult(bool result) +{ + sq_pushbool(this->vm, result); + vm->GetAt(vm->_stackbase + vm->_suspended_target) = vm->GetUp(-1); + vm->Pop(); +} + +void Squirrel::InsertResult(int result) +{ + sq_pushinteger(this->vm, result); + vm->GetAt(vm->_stackbase + vm->_suspended_target) = vm->GetUp(-1); + vm->Pop(); +} diff --git a/src/script/squirrel.hpp b/src/script/squirrel.hpp new file mode 100644 index 000000000..bd9a10cfc --- /dev/null +++ b/src/script/squirrel.hpp @@ -0,0 +1,185 @@ +/* $Id$ */ + +/** @file squirrel.hpp defines the Squirrel class */ + +#ifndef SQUIRREL_HPP +#define SQUIRREL_HPP + +class Squirrel { +private: + typedef void (SQPrintFunc)(bool error_msg, const SQChar *message); + + HSQUIRRELVM vm; ///< The VirtualMachine instnace for squirrel + void *global_pointer; ///< Can be set by who ever initializes Squirrel + SQPrintFunc *print_func; ///< Points to either NULL, or a custom print handler + + /** + * The internal RunError handler. It looks up the real error and calls RunError with it. + */ + static SQInteger _RunError(HSQUIRRELVM vm); + + /** + * Get the squirrel VM. Try to avoid using this. + */ + HSQUIRRELVM GetVM() { return this->vm; } + +protected: + /** + * The CompileError handler. + */ + static void CompileError(HSQUIRRELVM vm, const SQChar *desc, const SQChar *source, SQInteger line, SQInteger column); + + /** + * The RunError handler. + */ + static void RunError(HSQUIRRELVM vm, const SQChar *error); + + /** + * If a user runs 'print' inside a script, this function gets the params. + */ + static void PrintFunc(HSQUIRRELVM vm, const SQChar *s, ...); + + /** + * If an error has to be print, this function is called. + */ + static void ErrorPrintFunc(HSQUIRRELVM vm, const SQChar *s, ...); + +public: + friend class AIController; + friend class AIScanner; + friend class AIInstance; + + Squirrel(); + ~Squirrel(); + + /** + * Load a script. + * @param script The full script-name to load. + * @return False if loading failed. + */ + bool LoadScript(const char *script); + static bool LoadScript(HSQUIRRELVM vm, const char *script, bool in_root = true); + + /** + * Load a file to a given VM. + */ + static SQRESULT LoadFile(HSQUIRRELVM vm, const char *filename, SQBool printerror); + + /** + * Adds a function to the stack. Depending on the current state this means + * either a method or a global function. + */ + void AddMethod(const char *method_name, SQFUNCTION proc, uint nparam = 0, const char *params = NULL, void *userdata = NULL, int size = 0); + + /** + * Adds a const to the stack. Depending on the current state this means + * either a const to a class or to the global space. + */ + void AddConst(const char *var_name, int value); + + /** + * Adds a class to the global scope. Make sure to call AddClassEnd when you + * are done adding methods. + */ + void AddClassBegin(const char *class_name); + + /** + * Adds a class to the global scope, extending 'parent_class'. + * Make sure to call AddClassEnd when you are done adding methods. + */ + void AddClassBegin(const char *class_name, const char *parent_class); + + /** + * Finishes adding a class to the global scope. If this isn't called, no + * class is really created. + */ + void AddClassEnd(); + + /** + * Resume a VM when it was suspended via a throw. + */ + bool Resume(int suspend = -1); + + void InsertResult(bool result); + void InsertResult(int result); + + /** + * Call a method of an instance, in various flavors. + */ + bool CallMethod(HSQOBJECT instance, const char *method_name, HSQOBJECT *ret, int suspend = -1); + bool CallMethod(HSQOBJECT instance, const char *method_name, int suspend = -1) { return this->CallMethod(instance, method_name, NULL, suspend); } + const char *CallStringMethodStrdup(HSQOBJECT instance, const char *method_name, int suspend = -1) { HSQOBJECT ret; this->CallMethod(instance, method_name, &ret, suspend); return strdup(ObjectToString(&ret)); } + int CallIntegerMethod(HSQOBJECT instance, const char *method_name, int suspend = -1) { HSQOBJECT ret; this->CallMethod(instance, method_name, &ret, suspend); return ObjectToInteger(&ret); } + + /** + * Check if a method exists in an instance. + */ + bool MethodExists(HSQOBJECT instance, const char *method_name); + + /** + * Creates a class instance. + * @param class_name The name of the class of which we create an instance. + * @param real_instance The instance to the real class, if it represents a real class. + * @param instance Returning value with the pointer to the instance. + * @param release_hook Optional param to give a release hook. + * @return False if creating failed. + */ + static bool CreateClassInstanceVM(HSQUIRRELVM vm, const char *class_name, void *real_instance, HSQOBJECT *instance, SQRELEASEHOOK release_hook); + + /** + * Exactly the same as CreateClassInstanceVM, only callable without instance of Squirrel. + */ + bool CreateClassInstance(const char *class_name, void *real_instance, HSQOBJECT *instance); + + /** + * Get the real-instance pointer. + * @note This will only work just after a function-call from within Squirrel + * to your C++ function. + */ + static bool GetRealInstance(HSQUIRRELVM vm, SQUserPointer *ptr) { return SQ_SUCCEEDED(sq_getinstanceup(vm, 1, ptr, 0)); } + + /** + * Get the Squirrel-instance pointer. + * @note This will only work just after a function-call from within Squirrel + * to your C++ function. + */ + static bool GetInstance(HSQUIRRELVM vm, HSQOBJECT *ptr, int pos = 1) { sq_getclass(vm, pos); sq_getstackobj(vm, pos, ptr); sq_pop(vm, 1); return true; } + + /** + * Convert a Squirrel-object to a string. + */ + static const char *ObjectToString(HSQOBJECT *ptr) { return FS2OTTD(sq_objtostring(ptr)); } + + /** + * Convert a Squirrel-object to an integer. + */ + static int ObjectToInteger(HSQOBJECT *ptr) { return sq_objtointeger(ptr); } + + /** + * Sets a pointer in the VM that is reachable from where ever you are in SQ. + * Useful to keep track of the main instance. + */ + void SetGlobalPointer(void *ptr) { this->global_pointer = ptr; } + + /** + * Get the pointer as set by SetGlobalPointer. + */ + static void *GetGlobalPointer(HSQUIRRELVM vm) { return ((Squirrel *)sq_getforeignptr(vm))->global_pointer; } + + /** + * Set a custom print function, so you can handle outputs from SQ yourself. + */ + void SetPrintFunction(SQPrintFunc *func) { this->print_func = func; } + + /** + * Throw a Squirrel error that will be nicely displayed to the user. + */ + void ThrowError(const char *error) { sq_throwerror(this->vm, OTTD2FS(error)); } + + /** + * Release a SQ object. + */ + void ReleaseObject(HSQOBJECT *ptr) { sq_release(this->vm, ptr); } +}; + +#endif /* SQUIRREL_HPP */ diff --git a/src/script/squirrel_class.hpp b/src/script/squirrel_class.hpp new file mode 100644 index 000000000..20e838089 --- /dev/null +++ b/src/script/squirrel_class.hpp @@ -0,0 +1,116 @@ +/* $Id$ */ + +/** @file squirrel_class.cpp defines templates for converting C++ classes to Squirrel classes */ + +#ifndef SQUIRREL_CLASS_HPP +#define SQUIRREL_CLASS_HPP + +#if (__GNUC__ == 2) +/* GCC 2.95 doesn't like to have SQConvert::DefSQStaticCallback inside a + * template (it gives an internal error 373). Above that, it doesn't listen + * to 'using namespace' inside a function of a template. So for GCC 2.95 we + * do it in the global space to avoid compiler errors. */ +using namespace SQConvert; +#endif /* __GNUC__ == 2 */ + +/** + * The template to define classes in Squirrel. It takes care of the creation + * and calling of such classes, to make the AI Layer cleaner while having a + * powerful script as possible AI language. + */ +template <class CL> +class DefSQClass { +private: + const char *classname; + +public: + DefSQClass(const char *_classname) : + classname(_classname) + {} + + /** + * This defines a method inside a class for Squirrel. + */ + template <typename Func> + void DefSQMethod(Squirrel *engine, Func function_proc, const char *function_name) + { + using namespace SQConvert; + engine->AddMethod(function_name, DefSQNonStaticCallback<CL, Func>, 0, NULL, &function_proc, sizeof(function_proc)); + } + + /** + * This defines a method inside a class for Squirrel, which has access to the 'engine' (experts only!). + */ + template <typename Func> + void DefSQAdvancedMethod(Squirrel *engine, Func function_proc, const char *function_name) + { + using namespace SQConvert; + engine->AddMethod(function_name, DefSQAdvancedNonStaticCallback<CL, Func>, 0, NULL, &function_proc, sizeof(function_proc)); + } + + /** + * This defines a method inside a class for Squirrel with defined params. + * @note If you define nparam, make sure that he first param is always 'x', + * which is the 'this' inside the function. This is hidden from the rest + * of the code, but without it calling your function will fail! + */ + template <typename Func> + void DefSQMethod(Squirrel *engine, Func function_proc, const char *function_name, int nparam, const char *params) + { + using namespace SQConvert; + engine->AddMethod(function_name, DefSQNonStaticCallback<CL, Func>, nparam, params, &function_proc, sizeof(function_proc)); + } + + /** + * This defines a static method inside a class for Squirrel. + */ + template <typename Func> + void DefSQStaticMethod(Squirrel *engine, Func function_proc, const char *function_name) + { + using namespace SQConvert; + engine->AddMethod(function_name, DefSQStaticCallback<CL, Func>, 0, NULL, &function_proc, sizeof(function_proc)); + } + + /** + * This defines a static method inside a class for Squirrel with defined params. + * @note If you define nparam, make sure that he first param is always 'x', + * which is the 'this' inside the function. This is hidden from the rest + * of the code, but without it calling your function will fail! + */ + template <typename Func> + void DefSQStaticMethod(Squirrel *engine, Func function_proc, const char *function_name, int nparam, const char *params) + { + using namespace SQConvert; + engine->AddMethod(function_name, DefSQStaticCallback<CL, Func>, nparam, params, &function_proc, sizeof(function_proc)); + } + + template <typename Var> + void DefSQConst(Squirrel *engine, Var value, const char *var_name) + { + engine->AddConst(var_name, value); + } + + void PreRegister(Squirrel *engine) + { + engine->AddClassBegin(this->classname); + } + + void PreRegister(Squirrel *engine, const char *parent_class) + { + engine->AddClassBegin(this->classname, parent_class); + } + + template <typename Func, int Tnparam> + void AddConstructor(Squirrel *engine, const char *params) + { + using namespace SQConvert; + engine->AddMethod("constructor", DefSQConstructorCallback<CL, Func, Tnparam>, Tnparam, params); + } + + void PostRegister(Squirrel *engine) + { + engine->AddClassEnd(); + } +}; + +#endif /* SQUIRREL_CLASS_HPP */ diff --git a/src/script/squirrel_helper.hpp b/src/script/squirrel_helper.hpp new file mode 100644 index 000000000..67baf7c51 --- /dev/null +++ b/src/script/squirrel_helper.hpp @@ -0,0 +1,870 @@ +/* $Id$ */ + +/** @file squirrel_helper.hpp declarations and parts of the implementation of the class for convert code */ + +#ifndef SQUIRREL_HELPER_HPP +#define SQUIRREL_HELPER_HPP + +#include <squirrel.h> +#include "../core/math_func.hpp" +#include "../core/smallvec_type.hpp" +#include "../economy_type.h" +#include "squirrel_helper_type.hpp" + +/** + * The Squirrel convert routines + */ +namespace SQConvert { + /** + * Pointers assigned to this class will be free'd when this instance + * comes out of scope. Useful to make sure you can use strdup(), + * without leaking memory. + */ + struct SQAutoFreePointers : SmallVector<void *, 1> { + ~SQAutoFreePointers() + { + for (uint i = 0; i < this->items; i++) free(this->data[i]); + } + }; + + template <bool Y> struct YesT { + static const bool Yes = Y; + static const bool No = !Y; + }; + + /** + * Helper class to recognize if the given type is void. Usage: 'IsVoidT<T>::Yes' + */ + template <typename T> struct IsVoidT : YesT<false> {}; + template <> struct IsVoidT<void> : YesT<true> {}; + + /** + * Helper class to recognize if the function/method return type is void. + */ + template <typename Tfunc> struct HasVoidReturnT; + /* functions */ + template <typename Tretval> struct HasVoidReturnT<Tretval (*)()> : IsVoidT<Tretval> {}; + template <typename Tretval, typename Targ1> struct HasVoidReturnT<Tretval (*)(Targ1)> : IsVoidT<Tretval> {}; + template <typename Tretval, typename Targ1, typename Targ2> struct HasVoidReturnT<Tretval (*)(Targ1, Targ2)> : IsVoidT<Tretval> {}; + template <typename Tretval, typename Targ1, typename Targ2, typename Targ3> struct HasVoidReturnT<Tretval (*)(Targ1, Targ2, Targ3)> : IsVoidT<Tretval> {}; + template <typename Tretval, typename Targ1, typename Targ2, typename Targ3, typename Targ4> struct HasVoidReturnT<Tretval (*)(Targ1, Targ2, Targ3, Targ4)> : IsVoidT<Tretval> {}; + template <typename Tretval, typename Targ1, typename Targ2, typename Targ3, typename Targ4, typename Targ5> struct HasVoidReturnT<Tretval (*)(Targ1, Targ2, Targ3, Targ4, Targ5)> : IsVoidT<Tretval> {}; + template <typename Tretval, typename Targ1, typename Targ2, typename Targ3, typename Targ4, typename Targ5, typename Targ6, typename Targ7, typename Targ8, typename Targ9, typename Targ10> struct HasVoidReturnT<Tretval (*)(Targ1, Targ2, Targ3, Targ4, Targ5, Targ6, Targ7, Targ8, Targ9, Targ10)> : IsVoidT<Tretval> {}; + /* methods */ + template <class Tcls, typename Tretval> struct HasVoidReturnT<Tretval (Tcls::*)()> : IsVoidT<Tretval> {}; + template <class Tcls, typename Tretval, typename Targ1> struct HasVoidReturnT<Tretval (Tcls::*)(Targ1)> : IsVoidT<Tretval> {}; + template <class Tcls, typename Tretval, typename Targ1, typename Targ2> struct HasVoidReturnT<Tretval (Tcls::*)(Targ1, Targ2)> : IsVoidT<Tretval> {}; + template <class Tcls, typename Tretval, typename Targ1, typename Targ2, typename Targ3> struct HasVoidReturnT<Tretval (Tcls::*)(Targ1, Targ2, Targ3)> : IsVoidT<Tretval> {}; + template <class Tcls, typename Tretval, typename Targ1, typename Targ2, typename Targ3, typename Targ4> struct HasVoidReturnT<Tretval (Tcls::*)(Targ1, Targ2, Targ3, Targ4)> : IsVoidT<Tretval> {}; + template <class Tcls, typename Tretval, typename Targ1, typename Targ2, typename Targ3, typename Targ4, typename Targ5> struct HasVoidReturnT<Tretval (Tcls::*)(Targ1, Targ2, Targ3, Targ4, Targ5)> : IsVoidT<Tretval> {}; + template <class Tcls, typename Tretval, typename Targ1, typename Targ2, typename Targ3, typename Targ4, typename Targ5, typename Targ6, typename Targ7, typename Targ8, typename Targ9, typename Targ10> struct HasVoidReturnT<Tretval (Tcls::*)(Targ1, Targ2, Targ3, Targ4, Targ5, Targ6, Targ7, Targ8, Targ9, Targ10)> : IsVoidT<Tretval> {}; + + + /** + * Special class to make it possible for the compiler to pick the correct GetParam(). + */ + template <typename T> class ForceType { }; + + /** + * To return a value to squirrel, we call this function. It converts to the right format. + */ + template <typename T> static int Return(HSQUIRRELVM vm, T t); + + template <> inline int Return<uint8> (HSQUIRRELVM vm, uint8 res) { sq_pushinteger(vm, (int32)res); return 1; } + template <> inline int Return<uint16> (HSQUIRRELVM vm, uint16 res) { sq_pushinteger(vm, (int32)res); return 1; } + template <> inline int Return<uint32> (HSQUIRRELVM vm, uint32 res) { sq_pushinteger(vm, (int32)res); return 1; } + template <> inline int Return<int8> (HSQUIRRELVM vm, int8 res) { sq_pushinteger(vm, res); return 1; } + template <> inline int Return<int16> (HSQUIRRELVM vm, int16 res) { sq_pushinteger(vm, res); return 1; } + template <> inline int Return<int32> (HSQUIRRELVM vm, int32 res) { sq_pushinteger(vm, res); return 1; } + template <> inline int Return<int64> (HSQUIRRELVM vm, int64 res) { sq_pushinteger(vm, ClampToI32(res)); return 1; } + template <> inline int Return<Money> (HSQUIRRELVM vm, Money res) { sq_pushinteger(vm, ClampToI32(res)); return 1; } + template <> inline int Return<bool> (HSQUIRRELVM vm, bool res) { sq_pushbool (vm, res); return 1; } + template <> inline int Return<char *> (HSQUIRRELVM vm, char *res) { if (res == NULL) sq_pushnull(vm); else sq_pushstring (vm, OTTD2FS(res), strlen(res)); free(res); return 1; } + template <> inline int Return<const char *>(HSQUIRRELVM vm, const char *res) { if (res == NULL) sq_pushnull(vm); else sq_pushstring (vm, OTTD2FS(res), strlen(res)); return 1; } + template <> inline int Return<void *> (HSQUIRRELVM vm, void *res) { sq_pushuserpointer(vm, res); return 1; } + + /** + * To get a param from squirrel, we call this function. It converts to the right format. + */ + template <typename T> static T GetParam(ForceType<T>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr); + + template <> inline uint8 GetParam(ForceType<uint8> , HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger (vm, index, &tmp); return tmp; } + template <> inline uint16 GetParam(ForceType<uint16> , HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger (vm, index, &tmp); return tmp; } + template <> inline uint32 GetParam(ForceType<uint32> , HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger (vm, index, &tmp); return tmp; } + template <> inline int8 GetParam(ForceType<int8> , HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger (vm, index, &tmp); return tmp; } + template <> inline int16 GetParam(ForceType<int16> , HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger (vm, index, &tmp); return tmp; } + template <> inline int32 GetParam(ForceType<int32> , HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger (vm, index, &tmp); return tmp; } + template <> inline bool GetParam(ForceType<bool> , HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQBool tmp; sq_getbool (vm, index, &tmp); return tmp != 0; } + template <> inline const char *GetParam(ForceType<const char *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { const SQChar *tmp; sq_getstring (vm, index, &tmp); char *tmp_str = strdup(FS2OTTD(tmp)); *ptr->Append() = (void *)tmp_str; return tmp_str; } + template <> inline void *GetParam(ForceType<void *> , HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer tmp; sq_getuserpointer(vm, index, &tmp); return tmp; } + + template <> inline Array *GetParam(ForceType<Array *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) + { + SQObject obj; + sq_getstackobj(vm, index, &obj); + sq_pushobject(vm, obj); + sq_pushnull(vm); + + SmallVector<int32, 2> data; + + while (SQ_SUCCEEDED(sq_next(vm, -2))) { + SQInteger tmp; + if (SQ_SUCCEEDED(sq_getinteger(vm, -1, &tmp))) { + *data.Append() = (int32)tmp; + } else { + sq_pop(vm, 4); + throw sq_throwerror(vm, _SC("a member of an array used as parameter to a function is not numeric")); + } + + sq_pop(vm, 2); + } + sq_pop(vm, 2); + + Array *arr = (Array*)MallocT<byte>(sizeof(Array) + sizeof(int32) * data.Length()); + arr->size = data.Length(); + memcpy(arr->array, data.Begin(), sizeof(int32) * data.Length()); + + *ptr->Append() = arr; + return arr; + } + + /** + * Helper class to recognize the function type (retval type, args) and use the proper specialization + * for SQ callback. The partial specializations for the second arg (Tis_void_retval) are not possible + * on the function. Therefore the class is used instead. + */ + template <typename Tfunc, bool Tis_void_retval = HasVoidReturnT<Tfunc>::Yes> struct HelperT; + + /** + * The real C++ caller for function with return value and 0 params. + */ + template <typename Tretval> + struct HelperT<Tretval (*)(), false> { + static int SQCall(void *instance, Tretval (*func)(), HSQUIRRELVM vm) + { + return Return(vm, (*func)()); + } + }; + + /** + * The real C++ caller for function with no return value and 0 params. + */ + template <typename Tretval> + struct HelperT<Tretval (*)(), true> { + static int SQCall(void *instance, Tretval (*func)(), HSQUIRRELVM vm) + { + (*func)(); + return 0; + } + }; + + /** + * The real C++ caller for method with return value and 0 params. + */ + template <class Tcls, typename Tretval> + struct HelperT<Tretval (Tcls::*)(), false> { + static int SQCall(Tcls *instance, Tretval (Tcls::*func)(), HSQUIRRELVM vm) + { + return Return(vm, (instance->*func)()); + } + }; + + /** + * The real C++ caller for method with no return value and 0 params. + */ + template <class Tcls, typename Tretval> + struct HelperT<Tretval (Tcls::*)(), true> { + static int SQCall(Tcls *instance, Tretval (Tcls::*func)(), HSQUIRRELVM vm) + { + (instance->*func)(); + return 0; + } + + static Tcls *SQConstruct(Tcls *instance, Tretval (Tcls::*func)(), HSQUIRRELVM vm) + { + return new Tcls(); + } + }; + + /** + * The real C++ caller for function with return value and 1 param. + */ + template <typename Tretval, typename Targ1> + struct HelperT<Tretval (*)(Targ1), false> { + static int SQCall(void *instance, Tretval (*func)(Targ1), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + Tretval ret = (*func)( + GetParam(ForceType<Targ1>(), vm, 2, &ptr) + ); + sq_pop(vm, 1); + return Return(vm, ret); + } + }; + + /** + * The real C++ caller for function with no return value and 1 param. + */ + template <typename Tretval, typename Targ1> + struct HelperT<Tretval (*)(Targ1), true> { + static int SQCall(void *instance, Tretval (*func)(Targ1), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + (*func)( + GetParam(ForceType<Targ1>(), vm, 2, &ptr) + ); + sq_pop(vm, 1); + return 0; + } + }; + + /** + * The real C++ caller for method with return value and 1 param. + */ + template <class Tcls, typename Tretval, typename Targ1> + struct HelperT<Tretval (Tcls::*)(Targ1), false> { + static int SQCall(Tcls *instance, Tretval (Tcls::*func)(Targ1), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + Tretval ret = (instance->*func)( + GetParam(ForceType<Targ1>(), vm, 2, &ptr) + ); + sq_pop(vm, 1); + return Return(vm, ret); + } + }; + + /** + * The real C++ caller for method with no return value and 1 param. + */ + template <class Tcls, typename Tretval, typename Targ1> + struct HelperT<Tretval (Tcls::*)(Targ1), true> { + static int SQCall(Tcls *instance, Tretval (Tcls::*func)(Targ1), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + (instance->*func)( + GetParam(ForceType<Targ1>(), vm, 2, &ptr) + ); + sq_pop(vm, 1); + return 0; + } + + static Tcls *SQConstruct(Tcls *instance, Tretval (Tcls::*func)(Targ1), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + Tcls *inst = new Tcls( + GetParam(ForceType<Targ1>(), vm, 2, &ptr) + ); + + return inst; + } + }; + + /** + * The real C++ caller for function with return value and 2 params. + */ + template <typename Tretval, typename Targ1, typename Targ2> + struct HelperT<Tretval (*)(Targ1, Targ2), false> { + static int SQCall(void *instance, Tretval (*func)(Targ1, Targ2), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + Tretval ret = (*func)( + GetParam(ForceType<Targ1>(), vm, 2, &ptr), + GetParam(ForceType<Targ2>(), vm, 3, &ptr) + ); + sq_pop(vm, 2); + return Return(vm, ret); + } + }; + + /** + * The real C++ caller for function with no return value and 2 params. + */ + template <typename Tretval, typename Targ1, typename Targ2> + struct HelperT<Tretval (*)(Targ1, Targ2), true> { + static int SQCall(void *instance, Tretval (*func)(Targ1, Targ2), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + (*func)( + GetParam(ForceType<Targ1>(), vm, 2, &ptr), + GetParam(ForceType<Targ2>(), vm, 3, &ptr) + ); + sq_pop(vm, 2); + return 0; + } + }; + + /** + * The real C++ caller for method with return value and 2 params. + */ + template <class Tcls, typename Tretval, typename Targ1, typename Targ2> + struct HelperT<Tretval (Tcls::*)(Targ1, Targ2), false> { + static int SQCall(Tcls *instance, Tretval (Tcls::*func)(Targ1, Targ2), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + Tretval ret = (instance->*func)( + GetParam(ForceType<Targ1>(), vm, 2, &ptr), + GetParam(ForceType<Targ2>(), vm, 3, &ptr) + ); + sq_pop(vm, 2); + return Return(vm, ret); + } + }; + + /** + * The real C++ caller for method with no return value and 2 params. + */ + template <class Tcls, typename Tretval, typename Targ1, typename Targ2> + struct HelperT<Tretval (Tcls::*)(Targ1, Targ2), true> { + static int SQCall(Tcls *instance, Tretval (Tcls::*func)(Targ1, Targ2), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + (instance->*func)( + GetParam(ForceType<Targ1>(), vm, 2, &ptr), + GetParam(ForceType<Targ2>(), vm, 3, &ptr) + ); + sq_pop(vm, 2); + return 0; + } + + static Tcls *SQConstruct(Tcls *instance, Tretval (Tcls::*func)(Targ1, Targ2), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + Tcls *inst = new Tcls( + GetParam(ForceType<Targ1>(), vm, 2, &ptr), + GetParam(ForceType<Targ2>(), vm, 3, &ptr) + ); + + return inst; + } + }; + + /** + * The real C++ caller for function with return value and 3 params. + */ + template <typename Tretval, typename Targ1, typename Targ2, typename Targ3> + struct HelperT<Tretval (*)(Targ1, Targ2, Targ3), false> { + static int SQCall(void *instance, Tretval (*func)(Targ1, Targ2, Targ3), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + Tretval ret = (*func)( + GetParam(ForceType<Targ1>(), vm, 2, &ptr), + GetParam(ForceType<Targ2>(), vm, 3, &ptr), + GetParam(ForceType<Targ3>(), vm, 4, &ptr) + ); + sq_pop(vm, 3); + return Return(vm, ret); + } + }; + + /** + * The real C++ caller for function with no return value and 3 params. + */ + template <typename Tretval, typename Targ1, typename Targ2, typename Targ3> + struct HelperT<Tretval (*)(Targ1, Targ2, Targ3), true> { + static int SQCall(void *instance, Tretval (*func)(Targ1, Targ2, Targ3), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + (*func)( + GetParam(ForceType<Targ1>(), vm, 2, &ptr), + GetParam(ForceType<Targ2>(), vm, 3, &ptr), + GetParam(ForceType<Targ3>(), vm, 4, &ptr) + ); + sq_pop(vm, 3); + return 0; + } + }; + + /** + * The real C++ caller for method with return value and 3 params. + */ + template <class Tcls, typename Tretval, typename Targ1, typename Targ2, typename Targ3> + struct HelperT<Tretval (Tcls::*)(Targ1, Targ2, Targ3), false> { + static int SQCall(Tcls *instance, Tretval (Tcls::*func)(Targ1, Targ2, Targ3), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + Tretval ret = (instance->*func)( + GetParam(ForceType<Targ1>(), vm, 2, &ptr), + GetParam(ForceType<Targ2>(), vm, 3, &ptr), + GetParam(ForceType<Targ3>(), vm, 4, &ptr) + ); + sq_pop(vm, 3); + return Return(vm, ret); + } + }; + + /** + * The real C++ caller for method with no return value and 3 params. + */ + template <class Tcls, typename Tretval, typename Targ1, typename Targ2, typename Targ3> + struct HelperT<Tretval (Tcls::*)(Targ1, Targ2, Targ3), true> { + static int SQCall(Tcls *instance, Tretval (Tcls::*func)(Targ1, Targ2, Targ3), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + (instance->*func)( + GetParam(ForceType<Targ1>(), vm, 2, &ptr), + GetParam(ForceType<Targ2>(), vm, 3, &ptr), + GetParam(ForceType<Targ3>(), vm, 4, &ptr) + ); + sq_pop(vm, 3); + return 0; + } + + static Tcls *SQConstruct(Tcls *instance, Tretval (Tcls::*func)(Targ1, Targ2, Targ3), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + Tcls *inst = new Tcls( + GetParam(ForceType<Targ1>(), vm, 2, &ptr), + GetParam(ForceType<Targ2>(), vm, 3, &ptr), + GetParam(ForceType<Targ3>(), vm, 4, &ptr) + ); + + return inst; + } + }; + + /** + * The real C++ caller for function with return value and 4 params. + */ + template <typename Tretval, typename Targ1, typename Targ2, typename Targ3, typename Targ4> + struct HelperT<Tretval (*)(Targ1, Targ2, Targ3, Targ4), false> { + static int SQCall(void *instance, Tretval (*func)(Targ1, Targ2, Targ3, Targ4), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + Tretval ret = (*func)( + GetParam(ForceType<Targ1>(), vm, 2, &ptr), + GetParam(ForceType<Targ2>(), vm, 3, &ptr), + GetParam(ForceType<Targ3>(), vm, 4, &ptr), + GetParam(ForceType<Targ4>(), vm, 5, &ptr) + ); + sq_pop(vm, 4); + return Return(vm, ret); + } + }; + + /** + * The real C++ caller for function with no return value and 4 params. + */ + template <typename Tretval, typename Targ1, typename Targ2, typename Targ3, typename Targ4> + struct HelperT<Tretval (*)(Targ1, Targ2, Targ3, Targ4), true> { + static int SQCall(void *instance, Tretval (*func)(Targ1, Targ2, Targ3, Targ4), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + (*func)( + GetParam(ForceType<Targ1>(), vm, 2, &ptr), + GetParam(ForceType<Targ2>(), vm, 3, &ptr), + GetParam(ForceType<Targ3>(), vm, 4, &ptr), + GetParam(ForceType<Targ4>(), vm, 5, &ptr) + ); + sq_pop(vm, 4); + return 0; + } + }; + + /** + * The real C++ caller for method with return value and 4 params. + */ + template <class Tcls, typename Tretval, typename Targ1, typename Targ2, typename Targ3, typename Targ4> + struct HelperT<Tretval (Tcls::*)(Targ1, Targ2, Targ3, Targ4), false> { + static int SQCall(Tcls *instance, Tretval (Tcls::*func)(Targ1, Targ2, Targ3, Targ4), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + Tretval ret = (instance->*func)( + GetParam(ForceType<Targ1>(), vm, 2, &ptr), + GetParam(ForceType<Targ2>(), vm, 3, &ptr), + GetParam(ForceType<Targ3>(), vm, 4, &ptr), + GetParam(ForceType<Targ4>(), vm, 5, &ptr) + ); + sq_pop(vm, 4); + return Return(vm, ret); + } + }; + + /** + * The real C++ caller for method with no return value and 4 params. + */ + template <class Tcls, typename Tretval, typename Targ1, typename Targ2, typename Targ3, typename Targ4> + struct HelperT<Tretval (Tcls::*)(Targ1, Targ2, Targ3, Targ4), true> { + static int SQCall(Tcls *instance, Tretval (Tcls::*func)(Targ1, Targ2, Targ3, Targ4), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + (instance->*func)( + GetParam(ForceType<Targ1>(), vm, 2, &ptr), + GetParam(ForceType<Targ2>(), vm, 3, &ptr), + GetParam(ForceType<Targ3>(), vm, 4, &ptr), + GetParam(ForceType<Targ4>(), vm, 5, &ptr) + ); + sq_pop(vm, 4); + return 0; + } + + static Tcls *SQConstruct(Tcls *instance, Tretval (Tcls::*func)(Targ1, Targ2, Targ3, Targ4), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + Tcls *inst = new Tcls( + GetParam(ForceType<Targ1>(), vm, 2, &ptr), + GetParam(ForceType<Targ2>(), vm, 3, &ptr), + GetParam(ForceType<Targ3>(), vm, 4, &ptr), + GetParam(ForceType<Targ4>(), vm, 5, &ptr) + ); + + return inst; + } + }; + + /** + * The real C++ caller for function with return value and 5 params. + */ + template <typename Tretval, typename Targ1, typename Targ2, typename Targ3, typename Targ4, typename Targ5> + struct HelperT<Tretval (*)(Targ1, Targ2, Targ3, Targ4, Targ5), false> { + static int SQCall(void *instance, Tretval (*func)(Targ1, Targ2, Targ3, Targ4, Targ5), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + Tretval ret = (*func)( + GetParam(ForceType<Targ1>(), vm, 2, &ptr), + GetParam(ForceType<Targ2>(), vm, 3, &ptr), + GetParam(ForceType<Targ3>(), vm, 4, &ptr), + GetParam(ForceType<Targ4>(), vm, 5, &ptr), + GetParam(ForceType<Targ5>(), vm, 6, &ptr) + ); + sq_pop(vm, 5); + return Return(vm, ret); + } + }; + + /** + * The real C++ caller for function with no return value and 5 params. + */ + template <typename Tretval, typename Targ1, typename Targ2, typename Targ3, typename Targ4, typename Targ5> + struct HelperT<Tretval (*)(Targ1, Targ2, Targ3, Targ4, Targ5), true> { + static int SQCall(void *instance, Tretval (*func)(Targ1, Targ2, Targ3, Targ4, Targ5), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + (*func)( + GetParam(ForceType<Targ1>(), vm, 2, &ptr), + GetParam(ForceType<Targ2>(), vm, 3, &ptr), + GetParam(ForceType<Targ3>(), vm, 4, &ptr), + GetParam(ForceType<Targ4>(), vm, 5, &ptr), + GetParam(ForceType<Targ5>(), vm, 6, &ptr) + ); + sq_pop(vm, 5); + return 0; + } + }; + + /** + * The real C++ caller for method with return value and 5 params. + */ + template <class Tcls, typename Tretval, typename Targ1, typename Targ2, typename Targ3, typename Targ4, typename Targ5> + struct HelperT<Tretval (Tcls::*)(Targ1, Targ2, Targ3, Targ4, Targ5), false> { + static int SQCall(Tcls *instance, Tretval (Tcls::*func)(Targ1, Targ2, Targ3, Targ4, Targ5), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + Tretval ret = (instance->*func)( + GetParam(ForceType<Targ1>(), vm, 2, &ptr), + GetParam(ForceType<Targ2>(), vm, 3, &ptr), + GetParam(ForceType<Targ3>(), vm, 4, &ptr), + GetParam(ForceType<Targ4>(), vm, 5, &ptr), + GetParam(ForceType<Targ5>(), vm, 6, &ptr) + ); + sq_pop(vm, 5); + return Return(vm, ret); + } + }; + + /** + * The real C++ caller for method with no return value and 5 params. + */ + template <class Tcls, typename Tretval, typename Targ1, typename Targ2, typename Targ3, typename Targ4, typename Targ5> + struct HelperT<Tretval (Tcls::*)(Targ1, Targ2, Targ3, Targ4, Targ5), true> { + static int SQCall(Tcls *instance, Tretval (Tcls::*func)(Targ1, Targ2, Targ3, Targ4, Targ5), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + (instance->*func)( + GetParam(ForceType<Targ1>(), vm, 2, &ptr), + GetParam(ForceType<Targ2>(), vm, 3, &ptr), + GetParam(ForceType<Targ3>(), vm, 4, &ptr), + GetParam(ForceType<Targ4>(), vm, 5, &ptr), + GetParam(ForceType<Targ5>(), vm, 6, &ptr) + ); + sq_pop(vm, 5); + return 0; + } + + static Tcls *SQConstruct(Tcls *instance, Tretval (Tcls::*func)(Targ1, Targ2, Targ3, Targ4, Targ5), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + Tcls *inst = new Tcls( + GetParam(ForceType<Targ1>(), vm, 2, &ptr), + GetParam(ForceType<Targ2>(), vm, 3, &ptr), + GetParam(ForceType<Targ3>(), vm, 4, &ptr), + GetParam(ForceType<Targ4>(), vm, 5, &ptr), + GetParam(ForceType<Targ5>(), vm, 6, &ptr) + ); + + return inst; + } + }; + + /** + * The real C++ caller for function with return value and 10 params. + */ + template <typename Tretval, typename Targ1, typename Targ2, typename Targ3, typename Targ4, typename Targ5, typename Targ6, typename Targ7, typename Targ8, typename Targ9, typename Targ10> + struct HelperT<Tretval (*)(Targ1, Targ2, Targ3, Targ4, Targ5, Targ6, Targ7, Targ8, Targ9, Targ10), false> { + static int SQCall(void *instance, Tretval (*func)(Targ1, Targ2, Targ3, Targ4, Targ5, Targ6, Targ7, Targ8, Targ9, Targ10), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + Tretval ret = (*func)( + GetParam(ForceType<Targ1>(), vm, 2, &ptr), + GetParam(ForceType<Targ2>(), vm, 3, &ptr), + GetParam(ForceType<Targ3>(), vm, 4, &ptr), + GetParam(ForceType<Targ4>(), vm, 5, &ptr), + GetParam(ForceType<Targ5>(), vm, 6, &ptr), + GetParam(ForceType<Targ6>(), vm, 7, &ptr), + GetParam(ForceType<Targ7>(), vm, 8, &ptr), + GetParam(ForceType<Targ8>(), vm, 9, &ptr), + GetParam(ForceType<Targ9>(), vm, 10, &ptr), + GetParam(ForceType<Targ10>(), vm, 11, &ptr) + ); + sq_pop(vm, 10); + return Return(vm, ret); + } + }; + + /** + * The real C++ caller for function with no return value and 10 params. + */ + template <typename Tretval, typename Targ1, typename Targ2, typename Targ3, typename Targ4, typename Targ5, typename Targ6, typename Targ7, typename Targ8, typename Targ9, typename Targ10> + struct HelperT<Tretval (*)(Targ1, Targ2, Targ3, Targ4, Targ5, Targ6, Targ7, Targ8, Targ9, Targ10), true> { + static int SQCall(void *instance, Tretval (*func)(Targ1, Targ2, Targ3, Targ4, Targ5, Targ6, Targ7, Targ8, Targ9, Targ10), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + (*func)( + GetParam(ForceType<Targ1>(), vm, 2, &ptr), + GetParam(ForceType<Targ2>(), vm, 3, &ptr), + GetParam(ForceType<Targ3>(), vm, 4, &ptr), + GetParam(ForceType<Targ4>(), vm, 5, &ptr), + GetParam(ForceType<Targ5>(), vm, 6, &ptr), + GetParam(ForceType<Targ6>(), vm, 7, &ptr), + GetParam(ForceType<Targ7>(), vm, 8, &ptr), + GetParam(ForceType<Targ8>(), vm, 9, &ptr), + GetParam(ForceType<Targ9>(), vm, 10, &ptr), + GetParam(ForceType<Targ10>(), vm, 11, &ptr) + ); + sq_pop(vm, 10); + return 0; + } + }; + + /** + * The real C++ caller for method with return value and 10 params. + */ + template <class Tcls, typename Tretval, typename Targ1, typename Targ2, typename Targ3, typename Targ4, typename Targ5, typename Targ6, typename Targ7, typename Targ8, typename Targ9, typename Targ10> + struct HelperT<Tretval (Tcls::*)(Targ1, Targ2, Targ3, Targ4, Targ5, Targ6, Targ7, Targ8, Targ9, Targ10), false> { + static int SQCall(Tcls *instance, Tretval (Tcls::*func)(Targ1, Targ2, Targ3, Targ4, Targ5, Targ6, Targ7, Targ8, Targ9, Targ10), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + Tretval ret = (instance->*func)( + GetParam(ForceType<Targ1>(), vm, 2, &ptr), + GetParam(ForceType<Targ2>(), vm, 3, &ptr), + GetParam(ForceType<Targ3>(), vm, 4, &ptr), + GetParam(ForceType<Targ4>(), vm, 5, &ptr), + GetParam(ForceType<Targ5>(), vm, 6, &ptr), + GetParam(ForceType<Targ6>(), vm, 7, &ptr), + GetParam(ForceType<Targ7>(), vm, 8, &ptr), + GetParam(ForceType<Targ8>(), vm, 9, &ptr), + GetParam(ForceType<Targ9>(), vm, 10, &ptr), + GetParam(ForceType<Targ10>(), vm, 11, &ptr) + ); + sq_pop(vm, 10); + return Return(vm, ret); + } + }; + + /** + * The real C++ caller for method with no return value and 10 params. + */ + template <class Tcls, typename Tretval, typename Targ1, typename Targ2, typename Targ3, typename Targ4, typename Targ5, typename Targ6, typename Targ7, typename Targ8, typename Targ9, typename Targ10> + struct HelperT<Tretval (Tcls::*)(Targ1, Targ2, Targ3, Targ4, Targ5, Targ6, Targ7, Targ8, Targ9, Targ10), true> { + static int SQCall(Tcls *instance, Tretval (Tcls::*func)(Targ1, Targ2, Targ3, Targ4, Targ5, Targ6, Targ7, Targ8, Targ9, Targ10), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + (instance->*func)( + GetParam(ForceType<Targ1>(), vm, 2, &ptr), + GetParam(ForceType<Targ2>(), vm, 3, &ptr), + GetParam(ForceType<Targ3>(), vm, 4, &ptr), + GetParam(ForceType<Targ4>(), vm, 5, &ptr), + GetParam(ForceType<Targ5>(), vm, 6, &ptr), + GetParam(ForceType<Targ6>(), vm, 7, &ptr), + GetParam(ForceType<Targ7>(), vm, 8, &ptr), + GetParam(ForceType<Targ8>(), vm, 9, &ptr), + GetParam(ForceType<Targ9>(), vm, 10, &ptr), + GetParam(ForceType<Targ10>(), vm, 11, &ptr) + ); + sq_pop(vm, 10); + return 0; + } + + static Tcls *SQConstruct(Tcls *instance, Tretval (Tcls::*func)(Targ1, Targ2, Targ3, Targ4, Targ5, Targ6, Targ7, Targ8, Targ9, Targ10), HSQUIRRELVM vm) + { + SQAutoFreePointers ptr; + Tcls *inst = new Tcls( + GetParam(ForceType<Targ1>(), vm, 2, &ptr), + GetParam(ForceType<Targ2>(), vm, 3, &ptr), + GetParam(ForceType<Targ3>(), vm, 4, &ptr), + GetParam(ForceType<Targ4>(), vm, 5, &ptr), + GetParam(ForceType<Targ5>(), vm, 6, &ptr), + GetParam(ForceType<Targ6>(), vm, 7, &ptr), + GetParam(ForceType<Targ7>(), vm, 8, &ptr), + GetParam(ForceType<Targ8>(), vm, 9, &ptr), + GetParam(ForceType<Targ9>(), vm, 10, &ptr), + GetParam(ForceType<Targ10>(), vm, 11, &ptr) + ); + + return inst; + } + }; + + + /** + * A general template for all non-static method callbacks from Squirrel. + * In here the function_proc is recovered, and the SQCall is called that + * can handle this exact amount of params. + */ + template <typename Tcls, typename Tmethod> + inline SQInteger DefSQNonStaticCallback(HSQUIRRELVM vm) + { + /* Find the amount of params we got */ + int nparam = sq_gettop(vm); + SQUserPointer ptr = NULL; + SQUserPointer real_instance = NULL; + HSQOBJECT instance; + + /* Get the 'SQ' instance of this class */ + Squirrel::GetInstance(vm, &instance); + + /* Protect against calls to a non-static method in a static way */ + sq_pushroottable(vm); + sq_pushstring(vm, OTTD2FS(Tcls::GetClassName()), -1); + sq_get(vm, -2); + sq_pushobject(vm, instance); + if (sq_instanceof(vm) != SQTrue) return sq_throwerror(vm, _SC("class method is non-static")); + sq_pop(vm, 3); + + /* Get the 'real' instance of this class */ + sq_getinstanceup(vm, 1, &real_instance, 0); + /* Get the real function pointer */ + sq_getuserdata(vm, nparam, &ptr, 0); + if (real_instance == NULL) return sq_throwerror(vm, _SC("couldn't detect real instance of class for non-static call")); + /* Remove the userdata from the stack */ + sq_pop(vm, 1); + + try { + /* Delegate it to a template that can handle this specific function */ + return HelperT<Tmethod>::SQCall((Tcls *)real_instance, *(Tmethod *)ptr, vm); + } catch (SQInteger e) { + sq_pop(vm, nparam); + return e; + } + } + + /** + * A general template for all non-static advanced method callbacks from Squirrel. + * In here the function_proc is recovered, and the SQCall is called that + * can handle this exact amount of params. + */ + template <typename Tcls, typename Tmethod> + inline SQInteger DefSQAdvancedNonStaticCallback(HSQUIRRELVM vm) + { + /* Find the amount of params we got */ + int nparam = sq_gettop(vm); + SQUserPointer ptr = NULL; + SQUserPointer real_instance = NULL; + HSQOBJECT instance; + + /* Get the 'SQ' instance of this class */ + Squirrel::GetInstance(vm, &instance); + + /* Protect against calls to a non-static method in a static way */ + sq_pushroottable(vm); + sq_pushstring(vm, OTTD2FS(Tcls::GetClassName()), -1); + sq_get(vm, -2); + sq_pushobject(vm, instance); + if (sq_instanceof(vm) != SQTrue) return sq_throwerror(vm, _SC("class method is non-static")); + sq_pop(vm, 3); + + /* Get the 'real' instance of this class */ + sq_getinstanceup(vm, 1, &real_instance, 0); + /* Get the real function pointer */ + sq_getuserdata(vm, nparam, &ptr, 0); + if (real_instance == NULL) return sq_throwerror(vm, _SC("couldn't detect real instance of class for non-static call")); + /* Remove the userdata from the stack */ + sq_pop(vm, 1); + + /* Call the function, which its only param is always the VM */ + return (SQInteger)(((Tcls *)real_instance)->*(*(Tmethod *)ptr))(vm); + } + + /** + * A general template for all function/static method callbacks from Squirrel. + * In here the function_proc is recovered, and the SQCall is called that + * can handle this exact amount of params. + */ + template <typename Tcls, typename Tmethod> + inline SQInteger DefSQStaticCallback(HSQUIRRELVM vm) + { + /* Find the amount of params we got */ + int nparam = sq_gettop(vm); + SQUserPointer ptr = NULL; + + /* Get the real function pointer */ + sq_getuserdata(vm, nparam, &ptr, 0); + + try { + /* Delegate it to a template that can handle this specific function */ + return HelperT<Tmethod>::SQCall((Tcls *)NULL, *(Tmethod *)ptr, vm); + } catch (SQInteger e) { + sq_pop(vm, nparam); + return e; + } + } + + /** + * A general template for the destructor of SQ instances. This is needed + * here as it has to be in the same scope as DefSQConstructorCallback. + */ + template <typename Tcls> + static SQInteger DefSQDestructorCallback(SQUserPointer p, SQInteger size) + { + /* Remove the real instance too */ + if (p != NULL) ((Tcls *)p)->Release(); + return 0; + } + + /** + * A general template to handle creating of instance with any amount of + * params. It creates the instance in C++, and it sets all the needed + * settings in SQ to register the instance. + */ + template <typename Tcls, typename Tmethod, int Tnparam> + inline SQInteger DefSQConstructorCallback(HSQUIRRELVM vm) + { + /* Find the amount of params we got */ + int nparam = sq_gettop(vm); + + try { + /* Create the real instance */ + Tcls *instance = HelperT<Tmethod>::SQConstruct((Tcls *)NULL, (Tmethod)NULL, vm); + sq_setinstanceup(vm, -Tnparam, instance); + sq_setreleasehook(vm, -Tnparam, DefSQDestructorCallback<Tcls>); + instance->AddRef(); + return 0; + } catch (SQInteger e) { + sq_pop(vm, nparam); + return e; + } + } + +}; // namespace SQConvert + +#endif /* SQUIRREL_HELPER_HPP */ diff --git a/src/script/squirrel_helper_type.hpp b/src/script/squirrel_helper_type.hpp new file mode 100644 index 000000000..c886d6700 --- /dev/null +++ b/src/script/squirrel_helper_type.hpp @@ -0,0 +1,13 @@ +/* $Id$ */ + +/** @file squirrel_helper_type.hpp Helper structs for converting Squirrel data structures to C++. */ + +#ifndef SQUIRREL_HELPER_TYPE_HPP +#define SQUIRREL_HELPER_TYPE_HPP + +struct Array { + int32 size; + int32 array[VARARRAY_SIZE]; +}; + +#endif /* SQUIRREL_HELPER_TYPE_HPP */ diff --git a/src/script/squirrel_std.cpp b/src/script/squirrel_std.cpp new file mode 100644 index 000000000..3850b7b54 --- /dev/null +++ b/src/script/squirrel_std.cpp @@ -0,0 +1,114 @@ + +#include <squirrel.h> +#include "../stdafx.h" +#include "../debug.h" +#include "squirrel.hpp" +#include "squirrel_std.hpp" +#include "../core/alloc_func.hpp" +#include "../core/math_func.hpp" + +/* abs() is normally defined to myabs(), which we don't want in this file */ +#undef abs + +SQInteger SquirrelStd::abs(HSQUIRRELVM vm) +{ + SQInteger tmp; + + sq_getinteger(vm, 2, &tmp); + sq_pushinteger(vm, ::abs(tmp)); + return 1; +} + +SQInteger SquirrelStd::min(HSQUIRRELVM vm) +{ + SQInteger tmp1, tmp2; + + sq_getinteger(vm, 2, &tmp1); + sq_getinteger(vm, 3, &tmp2); + sq_pushinteger(vm, ::min(tmp1, tmp2)); + return 1; +} + +SQInteger SquirrelStd::max(HSQUIRRELVM vm) +{ + SQInteger tmp1, tmp2; + + sq_getinteger(vm, 2, &tmp1); + sq_getinteger(vm, 3, &tmp2); + sq_pushinteger(vm, ::max(tmp1, tmp2)); + return 1; +} + +SQInteger SquirrelStd::require(HSQUIRRELVM vm) +{ + SQInteger top = sq_gettop(vm); + const SQChar *filename; + SQChar *real_filename; + + sq_getstring(vm, 2, &filename); + + /* Get the script-name of the current file, so we can work relative from it */ + SQStackInfos si; + sq_stackinfos(vm, 1, &si); + if (si.source == NULL) { + DEBUG(misc, 0, "[squirrel] Couldn't detect the script-name of the 'require'-caller; this should never happen!"); + return SQ_ERROR; + } + real_filename = scstrdup(si.source); + /* Keep the dir, remove the rest */ + SQChar *s = scstrrchr(real_filename, PATHSEPCHAR); + if (s != NULL) { + /* Keep the PATHSEPCHAR there, remove the rest */ + *s++; + *s = '\0'; + } + /* And now we concat, so we are relative from the current script + * First, we have to make sure we have enough space for the full path */ + real_filename = ReallocT(real_filename, scstrlen(real_filename) + scstrlen(filename) + 1); + scstrcat(real_filename, filename); + /* Tars dislike opening files with '/' on Windows.. so convert it to '\\' ;) */ + char *filen = strdup(FS2OTTD(real_filename)); +#if (PATHSEPCHAR != '/') + for (char *n = filen; *n != '\0'; n++) if (*n == '/') *n = PATHSEPCHAR; +#endif + + bool ret = Squirrel::LoadScript(vm, filen); + + /* Reset the top, so the stack stays correct */ + sq_settop(vm, top); + free(real_filename); + free(filen); + + return ret ? 0: SQ_ERROR; +} + +SQInteger SquirrelStd::notifyallexceptions(HSQUIRRELVM vm) +{ + SQBool b; + + if (sq_gettop(vm) >= 1) { + if (SQ_SUCCEEDED(sq_getbool(vm, -1, &b))) { + sq_notifyallexceptions(vm, b); + return 0; + } + } + + return SQ_ERROR; +} + +void squirrel_register_global_std(Squirrel *engine) +{ + /* We don't use squirrel_helper here, as we want to register to the global + * scope and not to a class. */ + engine->AddMethod("require", &SquirrelStd::require, 2, "?s"); + engine->AddMethod("notifyallexceptions", &SquirrelStd::notifyallexceptions, 2, "?b"); +} + +void squirrel_register_std(Squirrel *engine) +{ + /* We don't use squirrel_helper here, as we want to register to the global + * scope and not to a class. */ + engine->AddMethod("abs", &SquirrelStd::abs, 2, "?i"); + engine->AddMethod("min", &SquirrelStd::min, 3, "?ii"); + engine->AddMethod("max", &SquirrelStd::max, 3, "?ii"); +} diff --git a/src/script/squirrel_std.hpp b/src/script/squirrel_std.hpp new file mode 100644 index 000000000..93d82995a --- /dev/null +++ b/src/script/squirrel_std.hpp @@ -0,0 +1,60 @@ +/* $Id$ */ + +/** @file squirrel_std.hpp defines the Squirrel Standard Function class */ + +#ifndef SQUIRREL_STD_HPP +#define SQUIRREL_STD_HPP + +#if defined(__APPLE__) +/* Which idiotic system makes 'require' a macro? :s Oh well.... */ +#undef require +#endif /* __APPLE__ */ + +/** + * By default we want to give a set of standard commands to a SQ script. + * Most of them are easy wrappers around internal functions. Of course we + * could just as easy include things like the stdmath of SQ, but of those + * functions we are sure they work on all our supported targets. + */ +class SquirrelStd { +public: + /** + * Make an integer absolute. + */ + static SQInteger abs(HSQUIRRELVM vm); + + /** + * Get the lowest of two integers. + */ + static SQInteger min(HSQUIRRELVM vm); + + /** + * Get the highest of two integers. + */ + static SQInteger max(HSQUIRRELVM vm); + + /** + * Load an other file on runtime. + * @note This is always loaded on the root-level, no matter where you call this. + * @note The filename is always relative from the script it is called from. Absolute calls are NOT allowed! + */ + static SQInteger require(HSQUIRRELVM vm); + + /** + * Enable/disable stack trace showing for handled exceptions. + */ + static SQInteger notifyallexceptions(HSQUIRRELVM vm); +}; + +/** + * Register all standard functions we want to give to a script. + */ +void squirrel_register_std(Squirrel *engine); + +/** + * Register all standard functions that are available on first startup. + * @note this set is very limited, and is only ment to load other scripts and things like that. + */ +void squirrel_register_global_std(Squirrel *engine); + +#endif /* SQUIRREL_STD_HPP */ diff --git a/src/settings.cpp b/src/settings.cpp index 12f835c43..e38e01598 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -64,6 +64,9 @@ #include "station_func.h" #include "settings_func.h" #include "ini_type.h" +#include "ai/ai.hpp" +#include "ai/ai_config.hpp" +#include "ai/ai_info.hpp" #include "table/strings.h" @@ -827,21 +830,6 @@ static int32 v_PositionMainToolbar(int32 p1) return 0; } -static int32 AiNew_PatchActive_Warning(int32 p1) -{ - if (p1 == 1) ShowErrorMessage(INVALID_STRING_ID, TEMP_AI_ACTIVATED, 0, 0); - return 0; -} - -static int32 Ai_In_Multiplayer_Warning(int32 p1) -{ - if (p1 == 1) { - ShowErrorMessage(INVALID_STRING_ID, TEMP_AI_MULTIPLAYER, 0, 0); - _settings_game.ai.ainew_active = true; - } - return 0; -} - static int32 PopulationInLabelActive(int32 p1) { Town *t; @@ -1330,12 +1318,13 @@ const SettingDesc _patch_settings[] = { SDT_CONDVAR(GameSettings, economy.initial_city_size, SLE_UINT8, 56, SL_MAX_VERSION, 0, 0, 2, 1, 10, 1, STR_CONFIG_PATCHES_CITY_SIZE_MULTIPLIER, NULL), SDT_CONDBOOL(GameSettings, economy.mod_road_rebuild, 77, SL_MAX_VERSION, 0, 0, false, STR_CONFIG_MODIFIED_ROAD_REBUILD, NULL), - SDT_BOOL(GameSettings, ai.ainew_active, 0, 0, false, STR_CONFIG_PATCHES_AINEW_ACTIVE, AiNew_PatchActive_Warning), - SDT_BOOL(GameSettings, ai.ai_in_multiplayer, 0, 0, false, STR_CONFIG_PATCHES_AI_IN_MULTIPLAYER, Ai_In_Multiplayer_Warning), + SDT_CONDNULL(1, 0, 106), // previously ai-new setting. + SDT_BOOL(GameSettings, ai.ai_in_multiplayer, 0, 0, true, STR_CONFIG_PATCHES_AI_IN_MULTIPLAYER, NULL), SDT_BOOL(GameSettings, ai.ai_disable_veh_train, 0, 0, false, STR_CONFIG_PATCHES_AI_BUILDS_TRAINS, NULL), SDT_BOOL(GameSettings, ai.ai_disable_veh_roadveh, 0, 0, false, STR_CONFIG_PATCHES_AI_BUILDS_ROADVEH, NULL), SDT_BOOL(GameSettings, ai.ai_disable_veh_aircraft, 0, 0, false, STR_CONFIG_PATCHES_AI_BUILDS_AIRCRAFT, NULL), SDT_BOOL(GameSettings, ai.ai_disable_veh_ship, 0, 0, false, STR_CONFIG_PATCHES_AI_BUILDS_SHIPS, NULL), + SDT_CONDVAR(GameSettings, ai.ai_max_opcode_till_suspend, SLE_UINT32,107, SL_MAX_VERSION, 0, NG, 10000, 5000,250000,2500, STR_CONFIG_PATCHES_AI_MAX_OPCODES, NULL), SDT_VAR(GameSettings, vehicle.extend_vehicle_life, SLE_UINT8, 0, 0, 0, 0, 100, 0, STR_NULL, NULL), SDT_VAR(GameSettings, economy.dist_local_authority, SLE_UINT8, 0, 0, 20, 5, 60, 0, STR_NULL, NULL), @@ -1635,6 +1624,32 @@ static void NewsDisplayLoadConfig(IniFile *ini, const char *grpname) } } +static void AILoadConfig(IniFile *ini, const char *grpname) +{ + IniGroup *group = ini->GetGroup(grpname); + IniItem *item; + + /* Clean any configured AI */ + for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) { + AIConfig::GetConfig(c, true)->ChangeAI(NULL); + } + + /* If no group exists, return */ + if (group == NULL) return; + + CompanyID c = COMPANY_FIRST; + for (item = group->item; c < MAX_COMPANIES && item != NULL; c++, item = item->next) { + AIConfig *config = AIConfig::GetConfig(c, true); + + config->ChangeAI(item->name); + if (!config->HasAI()) { + if (strcmp(item->name, "none") != 0) DEBUG(ai, 0, "The AI by the name '%s' was no longer found, and removed from the list.", item->name); + continue; + } + config->StringToSettings(item->value); + } +} + /* Load a GRF configuration from the given group name */ static GRFConfig *GRFLoadConfig(IniFile *ini, const char *grpname, bool is_static) { @@ -1702,6 +1717,34 @@ static void NewsDisplaySaveConfig(IniFile *ini, const char *grpname) } } +static void AISaveConfig(IniFile *ini, const char *grpname) +{ + IniGroup *group = ini->GetGroup(grpname); + IniItem **item; + + if (group == NULL) return; + group->item = NULL; + item = &group->item; + + for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) { + AIConfig *config = AIConfig::GetConfig(c, true); + const char *name; + char value[1024]; + + if (config->HasAI()) { + config->SettingsToString(value, lengthof(value)); + name = config->GetName(); + } else { + value[0] = '\0'; + name = "none"; + } + + *item = new IniItem(group, name); + (*item)->value = strdup(value); + item = &(*item)->next; + } +} + /** * Save the version of OpenTTD to the ini file. * @param ini the ini to write to @@ -1777,6 +1820,7 @@ void LoadFromConfig() _grfconfig_newgame = GRFLoadConfig(ini, "newgrf", false); _grfconfig_static = GRFLoadConfig(ini, "newgrf-static", true); NewsDisplayLoadConfig(ini, "news_display"); + AILoadConfig(ini, "ai_players"); CheckDifficultyLevels(); delete ini; } @@ -1795,6 +1839,7 @@ void SaveToConfig() GRFSaveConfig(ini, "newgrf", _grfconfig_newgame); GRFSaveConfig(ini, "newgrf-static", _grfconfig_static); NewsDisplaySaveConfig(ini, "news_display"); + AISaveConfig(ini, "ai_players"); SaveVersionInConfig(ini); ini->SaveToDisk(_config_file); delete ini; diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index 47fb11b93..349ee5c29 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -1117,12 +1117,12 @@ static PatchEntry _patches_economy[] = { static PatchPage _patches_economy_page = {_patches_economy, lengthof(_patches_economy)}; static PatchEntry _patches_ai_npc[] = { - PatchEntry("ai.ainew_active"), PatchEntry("ai.ai_in_multiplayer"), PatchEntry("ai.ai_disable_veh_train"), PatchEntry("ai.ai_disable_veh_roadveh"), PatchEntry("ai.ai_disable_veh_aircraft"), PatchEntry("ai.ai_disable_veh_ship"), + PatchEntry("ai.ai_max_opcode_till_suspend"), }; /** Computer players sub-page */ static PatchPage _patches_ai_npc_page = {_patches_ai_npc, lengthof(_patches_ai_npc)}; diff --git a/src/settings_type.h b/src/settings_type.h index 9c0bf1ac7..6dd91201c 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -9,6 +9,7 @@ #include "town_type.h" #include "transport_type.h" #include "network/core/config.h" +#include "company_type.h" /** Settings related to the difficulty of the game */ struct DifficultySettings { @@ -170,12 +171,12 @@ struct ConstructionSettings { /** Settings related to the AI. */ struct AISettings { - bool ainew_active; ///< is the new AI active? bool ai_in_multiplayer; ///< so we allow AIs in multiplayer bool ai_disable_veh_train; ///< disable types for AI bool ai_disable_veh_roadveh; ///< disable types for AI bool ai_disable_veh_aircraft; ///< disable types for AI bool ai_disable_veh_ship; ///< disable types for AI + uint32 ai_max_opcode_till_suspend; ///< max opcode calls till AI will suspend }; /** Settings related to the old pathfinder. */ @@ -339,6 +340,7 @@ struct GameSettings { GameCreationSettings game_creation; ///< settings used during the creation of a game (map) ConstructionSettings construction; ///< construction of things in-game AISettings ai; ///< what may the AI do? + class AIConfig *ai_config[MAX_COMPANIES]; ///< settings per company PathfinderSettings pf; ///< settings for all pathfinders OrderSettings order; ///< settings related to orders VehicleSettings vehicle; ///< options for vehicles diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp index 1fe14870c..a927dd542 100644 --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -41,6 +41,7 @@ #include "settings_type.h" #include "order_func.h" #include "effectvehicle_func.h" +#include "ai/ai.hpp" #include "table/strings.h" @@ -353,6 +354,7 @@ static void ShipArrivesAt(const Vehicle *v, Station *st) v->index, st->index ); + AI::NewEvent(v->owner, new AIEventStationFirstVehicle(st->index, v->index)); } } diff --git a/src/signs.cpp b/src/signs.cpp index 7866783c7..22fe2a421 100644 --- a/src/signs.cpp +++ b/src/signs.cpp @@ -97,6 +97,9 @@ CommandCost CmdPlaceSign(TileIndex tile, uint32 flags, uint32 p1, uint32 p2, con /* Try to locate a new sign */ if (!Sign::CanAllocateItem()) return_cmd_error(STR_2808_TOO_MANY_SIGNS); + /* Check sign text length if any */ + if (!StrEmpty(text) && strlen(text) >= MAX_LENGTH_SIGN_NAME_BYTES) return CMD_ERROR; + /* When we execute, really make the sign */ if (flags & DC_EXEC) { Sign *si = new Sign(_current_company); @@ -106,6 +109,9 @@ CommandCost CmdPlaceSign(TileIndex tile, uint32 flags, uint32 p1, uint32 p2, con si->x = x; si->y = y; si->z = GetSlopeZ(x, y); + if (!StrEmpty(text)) { + si->name = strdup(text); + } UpdateSignVirtCoords(si); MarkSignDirty(si); InvalidateWindowData(WC_SIGN_LIST, 0, 0); diff --git a/src/spritecache.cpp b/src/spritecache.cpp index e26029a1b..3e270ea3b 100644 --- a/src/spritecache.cpp +++ b/src/spritecache.cpp @@ -17,14 +17,12 @@ #endif /* WITH_PNG */ #include "blitter/factory.hpp" #include "core/math_func.hpp" -#include "core/enum_type.hpp" #include "table/sprites.h" /* Default of 4MB spritecache */ uint _sprite_cache_size = 4; -typedef SimpleTinyEnumT<SpriteType, byte> SpriteTypeByte; struct SpriteCache { void *ptr; @@ -32,8 +30,8 @@ struct SpriteCache { uint32 id; uint16 file_slot; int16 lru; - SpriteTypeByte type; ///< In some cases a single sprite is misused by two NewGRFs. Once as real sprite and once as recolour sprite. If the recolour sprite gets into the cache it might be drawn as real sprite which causes enormous trouble. - bool warned; ///< True iff the user has been warned about incorrect use of this sprite + SpriteType type; ///< In some cases a single sprite is misused by two NewGRFs. Once as real sprite and once as recolour sprite. If the recolour sprite gets into the cache it might be drawn as real sprite which causes enormous trouble. + bool warned; ///< True iff the user has been warned about incorrect use of this sprite }; diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index e7a792c9a..f7cda6a77 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -746,7 +746,7 @@ CommandCost CheckFlatLandBelow(TileIndex tile, uint w, uint h, uint flags, uint * b) the build_on_slopes switch is disabled */ if (IsSteepSlope(tileh) || - ((_is_old_ai_company || !_settings_game.construction.build_on_slopes) && tileh != SLOPE_FLAT)) { + ((!_settings_game.construction.build_on_slopes) && tileh != SLOPE_FLAT)) { return_cmd_error(STR_0007_FLAT_LAND_REQUIRED); } @@ -1001,7 +1001,7 @@ CommandCost CmdBuildRailroadStation(TileIndex tile_org, uint32 flags, uint32 p1, if (st->train_tile != INVALID_TILE) { /* check if we want to expanding an already existing station? */ - if (_is_old_ai_company || !_settings_game.station.join_stations) + if (!_settings_game.station.join_stations) return_cmd_error(STR_3005_TOO_CLOSE_TO_ANOTHER_RAILROAD); if (!CanExpandRailroadStation(st, finalvalues, axis)) return CMD_ERROR; @@ -1764,7 +1764,7 @@ static const byte * const _airport_sections[] = { * @param tile TileIndex of northern tile of an airport (present or to-be-built), NOT the station tile * @return the noise that will be generated, according to distance */ -static uint8 GetAirportNoiseLevelForTown(const AirportFTAClass *afc, TileIndex town_tile, TileIndex tile) +uint8 GetAirportNoiseLevelForTown(const AirportFTAClass *afc, TileIndex town_tile, TileIndex tile) { struct TileIndexDistance { TileIndex index; diff --git a/src/tar_type.h b/src/tar_type.h index 2cfbc4fb6..4dda00359 100644 --- a/src/tar_type.h +++ b/src/tar_type.h @@ -11,12 +11,13 @@ /** The define of a TarList. */ struct TarListEntry { const char *filename; + const char *dirname; /* MSVC goes copying around this struct after initialisation, so it tries * to free filename, which isn't set at that moment... but because it * initializes the variable with garbage, it's going to segfault. */ - TarListEntry() : filename(NULL) {} - ~TarListEntry() { free((void*)this->filename); } + TarListEntry() : filename(NULL), dirname(NULL) {} + ~TarListEntry() { free((void*)this->filename); free((void*)this->dirname); } }; struct TarFileListEntry { diff --git a/src/toolbar_gui.cpp b/src/toolbar_gui.cpp index 685e1c17d..dcea1efc9 100644 --- a/src/toolbar_gui.cpp +++ b/src/toolbar_gui.cpp @@ -37,6 +37,7 @@ #include "functions.h" #include "console_gui.h" #include "news_gui.h" +#include "ai/ai_gui.hpp" #include "tilehighlight_func.h" #include "rail.h" #include "widgets/dropdown_func.h" @@ -702,7 +703,7 @@ static void MenuClickNewspaper(int index) static void ToolbarHelpClick(Window *w) { - PopupMainToolbMenu(w, TBN_HELP, STR_02D5_LAND_BLOCK_INFO, 6); + PopupMainToolbMenu(w, TBN_HELP, STR_02D5_LAND_BLOCK_INFO, 7); } static void MenuClickSmallScreenshot() @@ -720,9 +721,10 @@ static void MenuClickHelp(int index) switch (index) { case 0: PlaceLandBlockInfo(); break; case 2: IConsoleSwitch(); break; - case 3: MenuClickSmallScreenshot(); break; - case 4: MenuClickWorldScreenshot(); break; - case 5: ShowAboutWindow(); break; + case 3: ShowAIDebugWindow(); break; + case 4: MenuClickSmallScreenshot(); break; + case 5: MenuClickWorldScreenshot(); break; + case 6: ShowAboutWindow(); break; } } diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp index 429aeec50..739e51b2d 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -529,7 +529,7 @@ static bool ClickTile_Town(TileIndex tile) static CommandCost ClearTile_Town(TileIndex tile, byte flags) { - if ((flags & DC_AUTO) && !(flags & DC_AI_BUILDING)) return_cmd_error(STR_2004_BUILDING_MUST_BE_DEMOLISHED); + if (flags & DC_AUTO) return_cmd_error(STR_2004_BUILDING_MUST_BE_DEMOLISHED); if (!CanDeleteHouse(tile)) return CMD_ERROR; const HouseSpec *hs = GetHouseSpecs(GetHouseType(tile)); diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index bd7f71eb9..9877fad96 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -48,6 +48,7 @@ #include "variables.h" #include "autoreplace_gui.h" #include "gfx_func.h" +#include "ai/ai.hpp" #include "settings_type.h" #include "order_func.h" #include "newgrf_station.h" @@ -1376,15 +1377,13 @@ CommandCost CmdMoveRailVehicle(TileIndex tile, uint32 flags, uint32 p1, uint32 p * - p2 = 0: only sell the single dragged wagon/engine (and any belonging rear-engines) * - p2 = 1: sell the vehicle and all vehicles following it in the chain * if the wagon is dragged, don't delete the possibly belonging rear-engine to some front - * - p2 = 2: when selling attached locos, rearrange all vehicles after it to separate lines; - * all wagons of the same type will go on the same line. Used by the AI currently */ CommandCost CmdSellRailWagon(TileIndex tile, uint32 flags, uint32 p1, uint32 p2, const char *text) { /* Check if we deleted a vehicle window */ Window *w = NULL; - if (!IsValidVehicleID(p1) || p2 > 2) return CMD_ERROR; + if (!IsValidVehicleID(p1) || p2 > 1) return CMD_ERROR; Vehicle *v = GetVehicle(p1); @@ -1416,9 +1415,8 @@ CommandCost CmdSellRailWagon(TileIndex tile, uint32 flags, uint32 p1, uint32 p2, CommandCost cost(EXPENSES_NEW_VEHICLES); switch (p2) { - case 0: case 2: { /* Delete given wagon */ + case 0: { /* Delete given wagon */ bool switch_engine = false; // update second wagon to engine? - byte ori_subtype = v->subtype; // backup subtype of deleted wagon in case DeleteVehicle() changes /* 1. Delete the engine, if it is dualheaded also delete the matching * rear engine of the loco (from the point of deletion onwards) */ @@ -1490,18 +1488,6 @@ CommandCost CmdSellRailWagon(TileIndex tile, uint32 flags, uint32 p1, uint32 p2, if (IsFrontEngine(first)) InvalidateWindow(WC_VEHICLE_REFIT, first->index); } - - /* (6.) Borked AI. If it sells an engine it expects all wagons lined - * up on a new line to be added to the newly built loco. Replace it is. - * Totally braindead cause building a new engine adds all loco-less - * engines to its train anyways */ - if (p2 == 2 && HasBit(ori_subtype, TS_FRONT)) { - for (v = first; v != NULL;) { - Vehicle *tmp = GetNextVehicle(v); - DoCommand(v->tile, v->index | INVALID_VEHICLE << 16, 0, DC_EXEC, CMD_MOVE_RAIL_VEHICLE); - v = tmp; - } - } } } break; case 1: { /* Delete wagon and all wagons after it given certain criteria */ @@ -2729,6 +2715,7 @@ static PBSTileInfo ExtendTrainReservation(const Vehicle *v, TrackBits *new_track if (KillFirstBit(ft.m_new_td_bits) == TRACKDIR_BIT_NONE) { /* Possible signal tile. */ if (HasOnewaySignalBlockingTrackdir(ft.m_new_tile, FindFirstTrackdir(ft.m_new_td_bits))) break; + AI::NewEvent(v->owner, new AIEventVehicleLost(v->index)); } if (no_90deg_turns) { @@ -3352,6 +3339,7 @@ static void TrainEnterStation(Vehicle *v, StationID station) v->index, st->index ); + AI::NewEvent(v->owner, new AIEventStationFirstVehicle(st->index, v->index)); } v->BeginLoading(); @@ -3627,6 +3615,7 @@ static bool CheckTrainCollision(Vehicle *v) /* any dead -> no crash */ if (tcc.num == 0) return false; + AI::NewEvent(v->owner, new AIEventVehicleCrashed(v->index, v->tile)); SetDParam(0, tcc.num); AddNewsItem(STR_8868_TRAIN_CRASH_DIE_IN_FIREBALL, NS_ACCIDENT_VEHICLE, diff --git a/src/tunnelbridge_cmd.cpp b/src/tunnelbridge_cmd.cpp index 263ec8ca1..7d33f62f7 100644 --- a/src/tunnelbridge_cmd.cpp +++ b/src/tunnelbridge_cmd.cpp @@ -318,7 +318,7 @@ CommandCost CmdBuildBridge(TileIndex end_tile, uint32 flags, uint32 p1, uint32 p } else { /* Build a new bridge. */ - bool allow_on_slopes = (!_is_old_ai_company && _settings_game.construction.build_on_slopes && transport_type != TRANSPORT_WATER); + bool allow_on_slopes = (_settings_game.construction.build_on_slopes && transport_type != TRANSPORT_WATER); /* Try and clear the start landscape */ ret = DoCommand(tile_start, 0, 0, flags, CMD_LANDSCAPE_CLEAR); @@ -454,7 +454,7 @@ not_valid_below:; if (!(flags & DC_QUERY_COST) || (IsValidCompanyID(_current_company) && GetCompany(_current_company)->is_ai)) { bridge_len += 2; // begin and end tiles/ramps - if (IsValidCompanyID(_current_company) && !_is_old_ai_company) + if (IsValidCompanyID(_current_company)) bridge_len = CalcBridgeLenCostFactor(bridge_len); cost.AddCost((int64)bridge_len * _price.build_bridge * GetBridgeSpec(bridge_type)->price >> 8); diff --git a/src/variables.h b/src/variables.h index 968864214..febccf1eb 100644 --- a/src/variables.h +++ b/src/variables.h @@ -38,8 +38,6 @@ VARDEF int _palette_animation_counter; VARDEF uint32 _realtime_tick; -VARDEF bool _is_old_ai_company; // current company is an oldAI company? (enables a lot of cheats..) - VARDEF bool _do_autosave; VARDEF int _autosave_ctr; diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 86b07c7ff..f160a3280 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -51,6 +51,7 @@ #include "depot_map.h" #include "animated_tile_func.h" #include "effectvehicle_base.h" +#include "ai/ai.hpp" #include "core/alloc_func.hpp" #include "core/smallmap_type.hpp" #include "vehiclelist.h" @@ -1518,6 +1519,7 @@ void VehicleEnterDepot(Vehicle *v) SetDParam(0, v->index); AddNewsItem(string, NS_ADVICE, v->index, 0); } + AI::NewEvent(v->owner, new AIEventVehicleWaitingInDepot(v->index)); } } } @@ -2213,9 +2215,9 @@ void VehiclesYearlyLoop() FOR_ALL_VEHICLES(v) { if (v->IsPrimaryVehicle()) { /* show warning if vehicle is not generating enough income last 2 years (corresponds to a red icon in the vehicle list) */ - if (_settings_client.gui.vehicle_income_warn && v->owner == _local_company && v->age >= 730) { - Money profit = v->GetDisplayProfitThisYear(); - if (profit < 0) { + Money profit = v->GetDisplayProfitThisYear(); + if (v->age >= 730 && profit < 0) { + if (_settings_client.gui.vehicle_income_warn && v->owner == _local_company) { SetDParam(0, v->index); SetDParam(1, profit); AddNewsItem( @@ -2224,6 +2226,7 @@ void VehiclesYearlyLoop() v->index, 0); } + AI::NewEvent(v->owner, new AIEventVehicleUnprofitable(v->index)); } v->profit_last_year = v->profit_this_year; diff --git a/src/window_type.h b/src/window_type.h index 338031cec..cf4b3f603 100644 --- a/src/window_type.h +++ b/src/window_type.h @@ -96,6 +96,7 @@ enum WindowClass { WC_OSK, WC_WAYPOINT_VIEW, WC_SELECT_STATION, + WC_AI_DEBUG, WC_INVALID = 0xFFFF }; |