# HG changeset patch # User truebrain # Date 2011-11-29 23:26:35 # Node ID 9d33e9643f803cc7b59807eba6fd601eecef8630 # Parent 33a32d9bf9198f63b0795c8ad44cd079fdbd7228 (svn r23364) -Codechange: refactor AIConfig, moving it mostly to Scriptconfig diff --git a/projects/openttd_vs100.vcxproj b/projects/openttd_vs100.vcxproj --- a/projects/openttd_vs100.vcxproj +++ b/projects/openttd_vs100.vcxproj @@ -788,6 +788,8 @@ + + diff --git a/projects/openttd_vs100.vcxproj.filters b/projects/openttd_vs100.vcxproj.filters --- a/projects/openttd_vs100.vcxproj.filters +++ b/projects/openttd_vs100.vcxproj.filters @@ -1587,6 +1587,12 @@ MD5 + + Script + + + Script + Script diff --git a/projects/openttd_vs80.vcproj b/projects/openttd_vs80.vcproj --- a/projects/openttd_vs80.vcproj +++ b/projects/openttd_vs80.vcproj @@ -2459,6 +2459,14 @@ Name="Script" > + + + + diff --git a/projects/openttd_vs90.vcproj b/projects/openttd_vs90.vcproj --- a/projects/openttd_vs90.vcproj +++ b/projects/openttd_vs90.vcproj @@ -2456,6 +2456,14 @@ Name="Script" > + + + + diff --git a/source.list b/source.list --- a/source.list +++ b/source.list @@ -553,6 +553,8 @@ 3rdparty/md5/md5.h #if AI # Script +script/script_config.cpp +script/script_config.hpp script/script_fatalerror.hpp script/script_info.cpp script/script_info.hpp diff --git a/src/ai/ai_config.cpp b/src/ai/ai_config.cpp --- a/src/ai/ai_config.cpp +++ b/src/ai/ai_config.cpp @@ -14,87 +14,28 @@ #include "../core/random_func.hpp" #include "ai.hpp" #include "ai_config.hpp" - -void AIConfig::ChangeAI(const char *name, int version, bool force_exact_match, bool is_random_ai) -{ - free(this->name); - this->name = (name == NULL) ? NULL : strdup(name); - this->info = (name == NULL) ? NULL : AI::FindInfo(this->name, version, force_exact_match); - this->version = (info == NULL) ? -1 : info->GetVersion(); - this->is_random_ai = is_random_ai; - if (this->config_list != NULL) delete this->config_list; - this->config_list = (info == NULL) ? NULL : new AIConfigItemList(); - if (this->config_list != NULL) this->config_list->push_back(_start_date_config); - - /* The special casing for start_date is here to ensure that the - * start_date setting won't change even if you chose another AI. */ - int start_date = this->GetSetting("start_date"); - - for (SettingValueList::iterator it = this->settings.begin(); it != this->settings.end(); it++) { - free((*it).first); - } - this->settings.clear(); - - this->SetSetting("start_date", start_date); - - if (_game_mode == GM_NORMAL && this->info != NULL) { - /* If we're in an existing game and the AI is changed, set all settings - * for the AI that have the random flag to a random value. */ - for (AIConfigItemList::const_iterator it = this->info->GetConfigList()->begin(); it != this->info->GetConfigList()->end(); it++) { - if ((*it).flags & AICONFIG_RANDOM) { - this->SetSetting((*it).name, InteractiveRandomRange((*it).max_value - (*it).min_value) + (*it).min_value); - } - } - this->AddRandomDeviation(); - } -} +#include "ai_info.hpp" -AIConfig::AIConfig(const AIConfig *config) -{ - this->name = (config->name == NULL) ? NULL : strdup(config->name); - this->info = config->info; - this->version = config->version; - this->config_list = NULL; - this->is_random_ai = config->is_random_ai; - - for (SettingValueList::const_iterator it = config->settings.begin(); it != config->settings.end(); it++) { - this->settings[strdup((*it).first)] = (*it).second; - } - this->AddRandomDeviation(); -} - -AIConfig::~AIConfig() -{ - free(this->name); - this->ResetSettings(); - if (this->config_list != NULL) delete this->config_list; -} +/** Configuration for AI start date, every AI has this setting. */ +ScriptConfigItem _start_date_config = { + "start_date", + "Number of days to start this AI after the previous one (give or take)", + AI::START_NEXT_MIN, + AI::START_NEXT_MAX, + AI::START_NEXT_MEDIUM, + AI::START_NEXT_EASY, + AI::START_NEXT_MEDIUM, + AI::START_NEXT_HARD, + AI::START_NEXT_DEVIATION, + 30, + SCRIPTCONFIG_NONE, + NULL +}; -AIInfo *AIConfig::GetInfo() const -{ - return this->info; -} - -bool AIConfig::ResetInfo(bool force_exact_match) -{ - this->info = AI::FindInfo(this->name, force_exact_match ? this->version : -1, force_exact_match); - return this->info != NULL; -} - -const AIConfigItemList *AIConfig::GetConfigList() -{ - if (this->info != NULL) return this->info->GetConfigList(); - if (this->config_list == NULL) { - this->config_list = new AIConfigItemList(); - this->config_list->push_back(_start_date_config); - } - return this->config_list; -} - -AIConfig *AIConfig::GetConfig(CompanyID company, AISettingSource source) +/* static */ AIConfig *AIConfig::GetConfig(CompanyID company, ScriptSettingSource source) { AIConfig **config; - if (source == AISS_FORCE_NEWGAME || (source == AISS_DEFAULT && _game_mode == GM_MENU)) { + if (source == SSS_FORCE_NEWGAME || (source == SSS_DEFAULT && _game_mode == GM_MENU)) { config = &_settings_newgame.ai_config[company]; } else { config = &_settings_game.ai_config[company]; @@ -103,12 +44,43 @@ AIConfig *AIConfig::GetConfig(CompanyID return *config; } +class AIInfo *AIConfig::GetInfo() const +{ + return static_cast(ScriptConfig::GetInfo()); +} + +ScriptInfo *AIConfig::FindInfo(const char *name, int version, bool force_exact_match) +{ + return static_cast(AI::FindInfo(name, version, force_exact_match)); +} + +bool AIConfig::ResetInfo(bool force_exact_match) +{ + this->info = (ScriptInfo *)AI::FindInfo(this->name, force_exact_match ? this->version : -1, force_exact_match); + return this->info != NULL; +} + +void AIConfig::PushExtraConfigList() +{ + this->config_list->push_back(_start_date_config); +} + +void AIConfig::ClearConfigList() +{ + /* The special casing for start_date is here to ensure that the + * start_date setting won't change even if you chose another Script. */ + int start_date = this->GetSetting("start_date"); + + ScriptConfig::ClearConfigList(); + + this->SetSetting("start_date", start_date); +} + int AIConfig::GetSetting(const char *name) const { - SettingValueList::const_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() || GetGameSettings().difficulty.diff_level != 3) { - if (this->info == NULL) { + if (this->info == NULL) { + SettingValueList::const_iterator it = this->settings.find(name); + if (it == this->settings.end() || GetGameSettings().difficulty.diff_level != 3) { assert(strcmp("start_date", name) == 0); switch (GetGameSettings().difficulty.diff_level) { case 0: return AI::START_NEXT_EASY; @@ -118,115 +90,28 @@ int AIConfig::GetSetting(const char *nam default: NOT_REACHED(); } } - return this->info->GetSettingDefaultValue(name); + + return (*it).second; } - return (*it).second; + + return ScriptConfig::GetSetting(name); } void AIConfig::SetSetting(const char *name, int value) { - /* You can only set ai specific settings if an AI is selected. */ - if (this->info == NULL && strcmp("start_date", name) != 0) return; - - if (this->info == NULL && strcmp("start_date", name) == 0) { + if (this->info == NULL) { + if (strcmp("start_date", name) != 0) return; value = Clamp(value, AI::START_NEXT_MIN, AI::START_NEXT_MAX); - } else { - const AIConfigItem *config_item = this->info->GetConfigItem(name); - if (config_item == NULL) return; - value = Clamp(value, config_item->min_value, config_item->max_value); + SettingValueList::iterator it = this->settings.find(name); + if (it != this->settings.end()) { + (*it).second = value; + } else { + this->settings[strdup(name)] = value; + } + + return; } - SettingValueList::iterator it = this->settings.find(name); - if (it != this->settings.end()) { - (*it).second = value; - } else { - this->settings[strdup(name)] = value; - } -} - -void AIConfig::ResetSettings() -{ - for (SettingValueList::iterator it = this->settings.begin(); it != this->settings.end(); it++) { - free((*it).first); - } - this->settings.clear(); -} - -void AIConfig::AddRandomDeviation() -{ - for (AIConfigItemList::const_iterator it = this->GetConfigList()->begin(); it != this->GetConfigList()->end(); it++) { - if ((*it).random_deviation != 0) { - this->SetSetting((*it).name, InteractiveRandomRange((*it).random_deviation * 2) - (*it).random_deviation + this->GetSetting((*it).name)); - } - } -} - -bool AIConfig::HasAI() const -{ - return this->info != NULL; -} - -bool AIConfig::IsRandomAI() const -{ - return this->is_random_ai; -} - -const char *AIConfig::GetName() const -{ - return this->name; -} - -int AIConfig::GetVersion() const -{ - return this->version; + ScriptConfig::SetSetting(name, value); } - -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, size_t size) const -{ - string[0] = '\0'; - for (SettingValueList::const_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_t needed_size = strlen((*it).first) + 1 + strlen(no) + 1; - /* If it doesn't fit, skip the next settings */ - if (size <= needed_size) break; - size -= needed_size; - - strcat(string, (*it).first); - strcat(string, "="); - strcat(string, no); - strcat(string, ","); - } - /* Remove the last ',', but only if at least one setting was saved. */ - size_t len = strlen(string); - if (len > 0) string[len - 1] = '\0'; -} diff --git a/src/ai/ai_config.hpp b/src/ai/ai_config.hpp --- a/src/ai/ai_config.hpp +++ b/src/ai/ai_config.hpp @@ -11,48 +11,28 @@ #ifndef AI_CONFIG_HPP #define AI_CONFIG_HPP -#ifdef ENABLE_AI -#include -#include "ai_info.hpp" -#include "../core/string_compare_type.hpp" -#include "../company_type.h" +#include "../script/script_config.hpp" -/** - * AI settings for one company slot. - */ -class AIConfig { -private: - /** List with name=>value pairs of all AI-specific settings */ - typedef std::map SettingValueList; +class AIConfig : public ScriptConfig { +public: + /** + * Get the config of a company. + */ + static AIConfig *GetConfig(CompanyID company, ScriptSettingSource source = SSS_DEFAULT); -public: AIConfig() : - name(NULL), - version(-1), - info(NULL), - config_list(NULL), - is_random_ai(false) + ScriptConfig() {} - /** - * Create a new AI config that is a copy of an existing config. - * @param config The object to copy. - */ - AIConfig(const AIConfig *config); - - /** Delete an AI configuration. */ - ~AIConfig(); + AIConfig(const AIConfig *config) : + ScriptConfig(config) + {} - /** - * Set another AI to be loaded in this slot. - * @param name The name of the AI. - * @param version The version of the AI to load, or -1 of latest. - * @param force_exact_match If true try to find the exact same version - * as specified. If false any compatible version is ok. - * @param is_random Is the AI chosen randomly? - */ - void ChangeAI(const char *name, int version = -1, bool force_exact_match = false, bool is_random = false); + class AIInfo *GetInfo() const; + + /* virtual */ int GetSetting(const char *name) const; + /* virtual */ void SetSetting(const char *name, int value); /** * When ever the AI Scanner is reloaded, all infos become invalid. This @@ -64,95 +44,10 @@ public: */ bool ResetInfo(bool force_exact_match); - /** - * Get the AIInfo linked to this AIConfig. - */ - class AIInfo *GetInfo() const; - - /** - * Get the config list for this AIConfig. - */ - const AIConfigItemList *GetConfigList(); - - /** - * Where to get the config from, either default (depends on current game - * mode) or force either newgame or normal - */ - enum AISettingSource { - AISS_DEFAULT, ///< Get the AI config from the current game mode - AISS_FORCE_NEWGAME, ///< Get the newgame AI config - AISS_FORCE_GAME, ///< Get the AI config from the current game - }; - - /** - * Get the config of a company. - */ - static AIConfig *GetConfig(CompanyID company, AISettingSource source = AISS_DEFAULT); - - /** - * 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) const; - - /** - * Set the value of a setting for this config. - */ - void SetSetting(const char *name, int value); - - /** - * Reset all settings to their default value. - */ - void ResetSettings(); - - /** - * Randomize all settings the AI requested to be randomized. - */ - void AddRandomDeviation(); - - /** - * Is this config attached to an AI? - */ - bool HasAI() const; - - /** - * Is the current AI a randomly chosen AI? - */ - bool IsRandomAI() const; - - /** - * Get the name of the AI. - */ - const char *GetName() const; - - /** - * Get the version of the AI. - */ - int GetVersion() const; - - /** - * 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, size_t size) const; - -private: - const char *name; ///< Name of the AI - int version; ///< Version of the AI - class AIInfo *info; ///< AIInfo object for related to this AI version - SettingValueList settings; ///< List with all setting=>value pairs that are configure for this AI - AIConfigItemList *config_list; ///< List with all settings defined by this AI - bool is_random_ai; ///< True if the AI in this slot was randomly chosen. +protected: + /* virtual */ void PushExtraConfigList(); + /* virtual */ void ClearConfigList(); + /* virtual */ ScriptInfo *FindInfo(const char *name, int version, bool force_exact_match); }; -#endif /* ENABLE_AI */ #endif /* AI_CONFIG_HPP */ diff --git a/src/ai/ai_core.cpp b/src/ai/ai_core.cpp --- a/src/ai/ai_core.cpp +++ b/src/ai/ai_core.cpp @@ -21,6 +21,7 @@ #include "ai_scanner.hpp" #include "ai_instance.hpp" #include "ai_config.hpp" +#include "ai_info.hpp" #include "ai.hpp" #include "../script/api/script_error.hpp" @@ -43,11 +44,11 @@ AIConfig *config = AIConfig::GetConfig(company); AIInfo *info = config->GetInfo(); - if (info == NULL || (rerandomise_ai && config->IsRandomAI())) { + if (info == NULL || (rerandomise_ai && config->IsRandom())) { info = AI::scanner_info->SelectRandomAI(); assert(info != NULL); /* Load default data and store the name in the settings */ - config->ChangeAI(info->GetName(), -1, false, true); + config->Change(info->GetName(), -1, false, true); } Backup cur_company(_current_company, company, FILE_LINE); @@ -181,10 +182,10 @@ * 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] != NULL && _settings_game.ai_config[c]->HasScript()) { if (!_settings_game.ai_config[c]->ResetInfo(true)) { 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); + _settings_game.ai_config[c]->Change(NULL); if (Company::IsValidAiID(c)) { /* The code belonging to an already running AI was deleted. We can only do * one thing here to keep everything sane and that is kill the AI. After @@ -198,10 +199,10 @@ Company::Get(c)->ai_info = _settings_game.ai_config[c]->GetInfo(); } } - if (_settings_newgame.ai_config[c] != NULL && _settings_newgame.ai_config[c]->HasAI()) { + if (_settings_newgame.ai_config[c] != NULL && _settings_newgame.ai_config[c]->HasScript()) { if (!_settings_newgame.ai_config[c]->ResetInfo(false)) { 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); + _settings_newgame.ai_config[c]->Change(NULL); } } } diff --git a/src/ai/ai_gui.cpp b/src/ai/ai_gui.cpp --- a/src/ai/ai_gui.cpp +++ b/src/ai/ai_gui.cpp @@ -28,6 +28,7 @@ #include "ai.hpp" #include "../script/api/script_log.hpp" #include "ai_config.hpp" +#include "ai_info.hpp" #include "ai_instance.hpp" #include "table/strings.h" @@ -71,7 +72,7 @@ struct AIListWindow : public Window { /* Try if we can find the currently selected AI */ this->selected = -1; - if (AIConfig::GetConfig(slot)->HasAI()) { + if (AIConfig::GetConfig(slot)->HasScript()) { AIInfo *info = AIConfig::GetConfig(slot)->GetInfo(); int i = 0; for (ScriptInfoList::const_iterator it = this->ai_info_list->begin(); it != this->ai_info_list->end(); it++, i++) { @@ -148,11 +149,11 @@ struct AIListWindow : public Window { void ChangeAI() { if (this->selected == -1) { - AIConfig::GetConfig(slot)->ChangeAI(NULL); + AIConfig::GetConfig(slot)->Change(NULL); } else { ScriptInfoList::const_iterator it = this->ai_info_list->begin(); for (int i = 0; i < this->selected; i++) it++; - AIConfig::GetConfig(slot)->ChangeAI((*it).second->GetName(), (*it).second->GetVersion()); + AIConfig::GetConfig(slot)->Change((*it).second->GetName(), (*it).second->GetVersion()); } SetWindowDirty(WC_GAME_OPTIONS, 0); } @@ -272,7 +273,7 @@ struct AISettingsWindow : public Window int clicked_row; ///< The clicked row of settings. int line_height; ///< Height of a row in the matrix widget. Scrollbar *vscroll; ///< Cache of the vertical scrollbar. - typedef std::vector VisibleSettingsList; + typedef std::vector VisibleSettingsList; VisibleSettingsList visible_settings; ///< List of visible AI settings /** @@ -306,9 +307,9 @@ struct AISettingsWindow : public Window { visible_settings.clear(); - AIConfigItemList::const_iterator it = this->ai_config->GetConfigList()->begin(); + ScriptConfigItemList::const_iterator it = this->ai_config->GetConfigList()->begin(); for (; it != this->ai_config->GetConfigList()->end(); it++) { - bool no_hide = (it->flags & AICONFIG_AI_DEVELOPER) == 0; + bool no_hide = (it->flags & SCRIPTCONFIG_DEVELOPER) == 0; if (no_hide || _settings_client.gui.ai_developer_tools) { visible_settings.push_back(&(*it)); } @@ -343,9 +344,9 @@ struct AISettingsWindow : public Window int y = r.top; for (; this->vscroll->IsVisible(i) && it != visible_settings.end(); i++, it++) { - const AIConfigItem &config_item = **it; + const ScriptConfigItem &config_item = **it; int current_value = config->GetSetting((config_item).name); - bool editable = _game_mode == GM_MENU || !Company::IsValidID(this->slot) || (config_item.flags & AICONFIG_INGAME) != 0; + bool editable = _game_mode == GM_MENU || !Company::IsValidID(this->slot) || (config_item.flags & SCRIPTCONFIG_INGAME) != 0; StringID str; TextColour colour; @@ -359,7 +360,7 @@ struct AISettingsWindow : public Window SetDParamStr(idx++, config_item.description); } - if ((config_item.flags & AICONFIG_BOOLEAN) != 0) { + if ((config_item.flags & SCRIPTCONFIG_BOOLEAN) != 0) { DrawFrameRect(buttons_left, y + 2, buttons_left + 19, y + 10, (current_value != 0) ? COLOUR_GREEN : COLOUR_RED, (current_value != 0) ? FR_LOWERED : FR_NONE); SetDParam(idx++, current_value == 0 ? STR_CONFIG_SETTING_OFF : STR_CONFIG_SETTING_ON); } else { @@ -403,10 +404,10 @@ struct AISettingsWindow : public Window VisibleSettingsList::const_iterator it = this->visible_settings.begin(); for (int i = 0; i < num; i++) it++; - const AIConfigItem config_item = **it; - if (_game_mode == GM_NORMAL && Company::IsValidID(this->slot) && (config_item.flags & AICONFIG_INGAME) == 0) return; + const ScriptConfigItem config_item = **it; + if (_game_mode == GM_NORMAL && Company::IsValidID(this->slot) && (config_item.flags & SCRIPTCONFIG_INGAME) == 0) return; - bool bool_item = (config_item.flags & AICONFIG_BOOLEAN) != 0; + bool bool_item = (config_item.flags & SCRIPTCONFIG_BOOLEAN) != 0; int x = pt.x - wid->pos_x; if (_current_text_dir == TD_RTL) x = wid->current_x - x; @@ -462,9 +463,9 @@ struct AISettingsWindow : public Window virtual void OnQueryTextFinished(char *str) { if (StrEmpty(str)) return; - AIConfigItemList::const_iterator it = this->ai_config->GetConfigList()->begin(); + ScriptConfigItemList::const_iterator it = this->ai_config->GetConfigList()->begin(); for (int i = 0; i < this->clicked_row; i++) it++; - if (_game_mode == GM_NORMAL && Company::IsValidID(this->slot) && (it->flags & AICONFIG_INGAME) == 0) return; + if (_game_mode == GM_NORMAL && Company::IsValidID(this->slot) && (it->flags & SCRIPTCONFIG_INGAME) == 0) return; int32 value = atoi(str); this->ai_config->SetSetting((*it).name, value); this->CheckDifficultyLevel(); diff --git a/src/ai/ai_info.cpp b/src/ai/ai_info.cpp --- a/src/ai/ai_info.cpp +++ b/src/ai/ai_info.cpp @@ -23,22 +23,6 @@ /** Maximum number of operations allowed for getting a particular setting. */ static const int MAX_GET_SETTING_OPS = 100000; -/** Configuration for AI start date, every AI has this setting. */ -AIConfigItem _start_date_config = { - "start_date", - "Number of days to start this AI after the previous one (give or take)", - AI::START_NEXT_MIN, - AI::START_NEXT_MAX, - AI::START_NEXT_MEDIUM, - AI::START_NEXT_EASY, - AI::START_NEXT_MEDIUM, - AI::START_NEXT_HARD, - AI::START_NEXT_DEVIATION, - 30, - AICONFIG_NONE, - NULL -}; - /** * Check if the API version provided by the AI is supported. * @param api_version The API version as provided by the AI. @@ -61,11 +45,18 @@ template <> const char *GetClassName(engine, "x"); SQAIInfo.DefSQAdvancedMethod(engine, &AIInfo::AddSetting, "AddSetting"); SQAIInfo.DefSQAdvancedMethod(engine, &AIInfo::AddLabels, "AddLabels"); - SQAIInfo.DefSQConst(engine, AICONFIG_NONE, "AICONFIG_NONE"); - SQAIInfo.DefSQConst(engine, AICONFIG_RANDOM, "AICONFIG_RANDOM"); - SQAIInfo.DefSQConst(engine, AICONFIG_BOOLEAN, "AICONFIG_BOOLEAN"); - SQAIInfo.DefSQConst(engine, AICONFIG_INGAME, "AICONFIG_INGAME"); - SQAIInfo.DefSQConst(engine, AICONFIG_AI_DEVELOPER, "AICONFIG_AI_DEVELOPER"); + SQAIInfo.DefSQConst(engine, SCRIPTCONFIG_NONE, "CONFIG_NONE"); + SQAIInfo.DefSQConst(engine, SCRIPTCONFIG_RANDOM, "CONFIG_RANDOM"); + SQAIInfo.DefSQConst(engine, SCRIPTCONFIG_BOOLEAN, "CONFIG_BOOLEAN"); + SQAIInfo.DefSQConst(engine, SCRIPTCONFIG_INGAME, "CONFIG_INGAME"); + SQAIInfo.DefSQConst(engine, SCRIPTCONFIG_DEVELOPER, "CONFIG_DEVELOPER"); + + /* Pre 1.2 had an AI prefix */ + SQAIInfo.DefSQConst(engine, SCRIPTCONFIG_NONE, "AICONFIG_NONE"); + SQAIInfo.DefSQConst(engine, SCRIPTCONFIG_RANDOM, "AICONFIG_RANDOM"); + SQAIInfo.DefSQConst(engine, SCRIPTCONFIG_BOOLEAN, "AICONFIG_BOOLEAN"); + SQAIInfo.DefSQConst(engine, SCRIPTCONFIG_INGAME, "AICONFIG_INGAME"); + SQAIInfo.PostRegister(engine); engine->AddMethod("RegisterAI", &AIInfo::Constructor, 2, "tx"); engine->AddMethod("RegisterDummyAI", &AIInfo::DummyConstructor, 2, "tx"); @@ -81,15 +72,11 @@ template <> const char *GetClassNameconfig_list.push_back(config); + info->config_list.push_front(config); - /* Check if we have settings */ - if (info->engine->MethodExists(*info->SQ_instance, "GetSettings")) { - if (!info->GetSettings()) return SQ_ERROR; - } if (info->engine->MethodExists(*info->SQ_instance, "MinVersionToLoad")) { if (!info->engine->CallIntegerMethod(*info->SQ_instance, "MinVersionToLoad", &info->min_loadable_version, MAX_GET_SETTING_OPS)) return SQ_ERROR; } else { @@ -141,11 +128,6 @@ template <> const char *GetClassNameengine->CallMethod(*this->SQ_instance, "GetSettings", NULL, MAX_GET_SETTING_OPS); -} - AIInfo::AIInfo() : min_loadable_version(0), use_as_random(false), @@ -155,18 +137,6 @@ AIInfo::AIInfo() : AIInfo::~AIInfo() { - /* Free all allocated strings */ - for (AIConfigItemList::iterator it = this->config_list.begin(); it != this->config_list.end(); it++) { - free((*it).name); - free((*it).description); - if (it->labels != NULL) { - for (LabelMapping::iterator it2 = (*it).labels->Begin(); it2 != (*it).labels->End(); it2++) { - free(it2->second); - } - delete it->labels; - } - } - this->config_list.clear(); free(this->api_version); } @@ -176,191 +146,6 @@ bool AIInfo::CanLoadFromVersion(int vers return version >= this->min_loadable_version && version <= this->GetVersion(); } -SQInteger AIInfo::AddSetting(HSQUIRRELVM vm) -{ - AIConfigItem config; - memset(&config, 0, sizeof(config)); - config.max_value = 1; - config.step_size = 1; - uint 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; - if (SQ_FAILED(sq_getstring(vm, -2, &sqkey))) return SQ_ERROR; - const char *key = SQ2OTTD(sqkey); - - if (strcmp(key, "name") == 0) { - const SQChar *sqvalue; - if (SQ_FAILED(sq_getstring(vm, -1, &sqvalue))) return SQ_ERROR; - char *name = strdup(SQ2OTTD(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 = strchr(name, '=')) != NULL) *s = '_'; - while ((s = strchr(name, ',')) != NULL) *s = '_'; - config.name = name; - items |= 0x001; - } else if (strcmp(key, "description") == 0) { - const SQChar *sqdescription; - if (SQ_FAILED(sq_getstring(vm, -1, &sqdescription))) return SQ_ERROR; - config.description = strdup(SQ2OTTD(sqdescription)); - items |= 0x002; - } else if (strcmp(key, "min_value") == 0) { - SQInteger res; - if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR; - config.min_value = res; - items |= 0x004; - } else if (strcmp(key, "max_value") == 0) { - SQInteger res; - if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR; - config.max_value = res; - items |= 0x008; - } else if (strcmp(key, "easy_value") == 0) { - SQInteger res; - if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR; - config.easy_value = res; - items |= 0x010; - } else if (strcmp(key, "medium_value") == 0) { - SQInteger res; - if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR; - config.medium_value = res; - items |= 0x020; - } else if (strcmp(key, "hard_value") == 0) { - SQInteger res; - if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR; - config.hard_value = res; - items |= 0x040; - } else if (strcmp(key, "random_deviation") == 0) { - SQInteger res; - if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR; - config.random_deviation = res; - items |= 0x200; - } else if (strcmp(key, "custom_value") == 0) { - SQInteger res; - if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR; - config.custom_value = res; - items |= 0x080; - } else if (strcmp(key, "step_size") == 0) { - SQInteger res; - if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR; - config.step_size = res; - } else if (strcmp(key, "flags") == 0) { - SQInteger res; - if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR; - 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); - - /* Don't allow both random_deviation and AICONFIG_RANDOM to - * be set for the same config item. */ - if ((items & 0x200) != 0 && (config.flags & AICONFIG_RANDOM) != 0) { - char error[1024]; - snprintf(error, sizeof(error), "Setting both random_deviation and AICONFIG_RANDOM is not allowed"); - this->engine->ThrowError(error); - return SQ_ERROR; - } - /* Reset the bit for random_deviation as it's optional. */ - items &= ~0x200; - - /* Make sure all properties are defined */ - uint mask = (config.flags & AICONFIG_BOOLEAN) ? 0x1F3 : 0x1FF; - if (items != mask) { - char error[1024]; - snprintf(error, sizeof(error), "please define all properties of a setting (min/max not allowed for booleans)"); - this->engine->ThrowError(error); - return SQ_ERROR; - } - - this->config_list.push_back(config); - return 0; -} - -SQInteger AIInfo::AddLabels(HSQUIRRELVM vm) -{ - const SQChar *sq_setting_name; - if (SQ_FAILED(sq_getstring(vm, -2, &sq_setting_name))) return SQ_ERROR; - const char *setting_name = SQ2OTTD(sq_setting_name); - - AIConfigItem *config = NULL; - for (AIConfigItemList::iterator it = this->config_list.begin(); it != this->config_list.end(); it++) { - if (strcmp((*it).name, setting_name) == 0) config = &(*it); - } - - if (config == NULL) { - char error[1024]; - snprintf(error, sizeof(error), "Trying to add labels for non-defined setting '%s'", setting_name); - this->engine->ThrowError(error); - return SQ_ERROR; - } - if (config->labels != NULL) return SQ_ERROR; - - config->labels = new LabelMapping; - - /* Read the table and find all labels */ - sq_pushnull(vm); - while (SQ_SUCCEEDED(sq_next(vm, -2))) { - const SQChar *sq_key; - const SQChar *sq_label; - if (SQ_FAILED(sq_getstring(vm, -2, &sq_key))) return SQ_ERROR; - if (SQ_FAILED(sq_getstring(vm, -1, &sq_label))) return SQ_ERROR; - /* Because squirrel doesn't support identifiers starting with a digit, - * we skip the first character. */ - const char *key_string = SQ2OTTD(sq_key); - int key = atoi(key_string + 1); - const char *label = SQ2OTTD(sq_label); - - /* !Contains() prevents strdup from leaking. */ - if (!config->labels->Contains(key)) config->labels->Insert(key, strdup(label)); - - sq_pop(vm, 2); - } - sq_pop(vm, 1); - - return 0; -} - -const AIConfigItemList *AIInfo::GetConfigList() const -{ - return &this->config_list; -} - -const AIConfigItem *AIInfo::GetConfigItem(const char *name) const -{ - for (AIConfigItemList::const_iterator it = this->config_list.begin(); it != this->config_list.end(); it++) { - if (strcmp((*it).name, name) == 0) return &(*it); - } - return NULL; -} - -int AIInfo::GetSettingDefaultValue(const char *name) const -{ - for (AIConfigItemList::const_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 (GetGameSettings().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; -} - AILibrary::~AILibrary() { diff --git a/src/ai/ai_info.hpp b/src/ai/ai_info.hpp --- a/src/ai/ai_info.hpp +++ b/src/ai/ai_info.hpp @@ -14,40 +14,8 @@ #ifdef ENABLE_AI -#include -#include "../core/smallmap_type.hpp" #include "../script/script_info.hpp" - -/** Bitmask of flags for AI settings. */ -enum AIConfigFlags { - AICONFIG_NONE = 0x0, ///< No flags set. - 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) ). - AICONFIG_INGAME = 0x4, ///< This setting can be changed while the AI is running. - AICONFIG_AI_DEVELOPER = 0x8, ///< This setting will only be visible when the ai development tools are active. -}; - -typedef SmallMap LabelMapping; ///< Map-type used to map the setting numbers to labels. - -/** Info about a single AI setting. */ -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. - int random_deviation; ///< The maximum random deviation from the default value. - int step_size; ///< The step size in the gui. - AIConfigFlags flags; ///< Flags for the configuration setting. - LabelMapping *labels; ///< Text labels for the integer values. -}; - -extern AIConfigItem _start_date_config; - -typedef std::list AIConfigItemList; ///< List of AIConfig items. +#include "../script/script_config.hpp" /** All static information from an AI like name, version, etc. */ class AIInfo : public ScriptInfo { @@ -71,41 +39,11 @@ public: static SQInteger DummyConstructor(HSQUIRRELVM vm); /** - * Get the settings of the AI. - */ - bool GetSettings(); - - /** - * Get the config list for this AI. - */ - const AIConfigItemList *GetConfigList() const; - - /** - * Get the description of a certain ai config option. - */ - const AIConfigItem *GetConfigItem(const char *name) const; - - /** * Check if we can start this AI. */ bool CanLoadFromVersion(int version) const; /** - * Set a setting. - */ - SQInteger AddSetting(HSQUIRRELVM vm); - - /** - * Add labels for a setting. - */ - SQInteger AddLabels(HSQUIRRELVM vm); - - /** - * Get the default value for a setting. - */ - int GetSettingDefaultValue(const char *name) const; - - /** * Use this AI as a random AI. */ bool UseAsRandomAI() const { return this->use_as_random; } @@ -116,10 +54,9 @@ public: const char *GetAPIVersion() const { return this->api_version; } private: - AIConfigItemList config_list; ///< List of settings from this AI. - int min_loadable_version; ///< The AI can load savegame data if the version is equal or greater than this. - bool use_as_random; ///< Should this AI be used when the user wants a "random AI"? - const char *api_version; ///< API version used by this AI. + int min_loadable_version; ///< The AI can load savegame data if the version is equal or greater than this. + bool use_as_random; ///< Should this AI be used when the user wants a "random AI"? + const char *api_version; ///< API version used by this AI. }; /** All static information from an AI library like name, version, etc. */ diff --git a/src/ai/ai_instance.cpp b/src/ai/ai_instance.cpp --- a/src/ai/ai_instance.cpp +++ b/src/ai/ai_instance.cpp @@ -22,6 +22,7 @@ #include "../script/script_fatalerror.hpp" #include "../script/script_suspend.hpp" #include "../script/script_storage.hpp" +#include "ai_info.hpp" #include "ai_instance.hpp" /* Convert all AI related classes to Squirrel data. diff --git a/src/ai/api/ai_changelog.hpp b/src/ai/api/ai_changelog.hpp --- a/src/ai/api/ai_changelog.hpp +++ b/src/ai/api/ai_changelog.hpp @@ -31,7 +31,7 @@ * \li AICompany::GetQuarterlyPerformanceRating * \li AICompany::GetQuarterlyCompanyValue * \li AIController::GetOpsTillSuspend - * \li AIInfo::AICONFIG_AI_DEVELOPER + * \li AIInfo::CONFIG_DEVELOPER * \li AIOrder::GetOrderRefit * \li AIOrder::IsRefitOrder * \li AIOrder::SetOrderRefit @@ -44,7 +44,9 @@ * * API renames: * \li AITown::GetLastMonthTransported to AITown::GetLastMonthSupplied to better - * reflect what it does + * reflect what it does. + * \li AIInfo has all its configure settings renamed from AICONFIG to just CONFIG + * like CONFIG_RANDOM. * * API removals: * \li AICompany::GetCompanyValue, use AICompany::GetQuarterlyCompanyValue instead. diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -1150,8 +1150,8 @@ DEF_CONSOLE_CMD(ConStartAI) AIConfig *config = AIConfig::GetConfig((CompanyID)n); if (argc >= 2) { - config->ChangeAI(argv[1], -1, true); - if (!config->HasAI()) { + config->Change(argv[1], -1, true); + if (!config->HasScript()) { IConsoleWarning("Failed to load the specified AI"); return true; } diff --git a/src/saveload/ai_sl.cpp b/src/saveload/ai_sl.cpp --- a/src/saveload/ai_sl.cpp +++ b/src/saveload/ai_sl.cpp @@ -39,7 +39,7 @@ static void SaveReal_AIPL(int *index_ptr CompanyID index = (CompanyID)*index_ptr; AIConfig *config = AIConfig::GetConfig(index); - if (config->HasAI()) { + if (config->HasScript()) { ttd_strlcpy(_ai_saveload_name, config->GetName(), lengthof(_ai_saveload_name)); _ai_saveload_version = config->GetVersion(); } else { @@ -48,7 +48,7 @@ static void SaveReal_AIPL(int *index_ptr _ai_saveload_version = -1; } - _ai_saveload_is_random = config->IsRandomAI(); + _ai_saveload_is_random = config->IsRandom(); _ai_saveload_settings[0] = '\0'; config->SettingsToString(_ai_saveload_settings, lengthof(_ai_saveload_settings)); @@ -61,7 +61,7 @@ static void Load_AIPL() { /* Free all current data */ for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) { - AIConfig::GetConfig(c, AIConfig::AISS_FORCE_GAME)->ChangeAI(NULL); + AIConfig::GetConfig(c, AIConfig::SSS_FORCE_GAME)->Change(NULL); } CompanyID index; @@ -76,17 +76,17 @@ static void Load_AIPL() continue; } - AIConfig *config = AIConfig::GetConfig(index, AIConfig::AISS_FORCE_GAME); + AIConfig *config = AIConfig::GetConfig(index, AIConfig::SSS_FORCE_GAME); if (StrEmpty(_ai_saveload_name)) { /* A random AI. */ - config->ChangeAI(NULL, -1, false, true); + config->Change(NULL, -1, false, true); } else { - config->ChangeAI(_ai_saveload_name, _ai_saveload_version, false, _ai_saveload_is_random); - if (!config->HasAI()) { + config->Change(_ai_saveload_name, _ai_saveload_version, false, _ai_saveload_is_random); + if (!config->HasScript()) { /* No version of the AI available that can load the data. Try to load the * latest version of the AI instead. */ - config->ChangeAI(_ai_saveload_name, -1, false, _ai_saveload_is_random); - if (!config->HasAI()) { + config->Change(_ai_saveload_name, -1, false, _ai_saveload_is_random); + if (!config->HasScript()) { if (strcmp(_ai_saveload_name, "%_dummy") != 0) { DEBUG(ai, 0, "The savegame has an AI by the name '%s', version %d which is no longer available.", _ai_saveload_name, _ai_saveload_version); DEBUG(ai, 0, "A random other AI will be loaded in its place."); diff --git a/src/script/api/script_controller.cpp b/src/script/api/script_controller.cpp --- a/src/script/api/script_controller.cpp +++ b/src/script/api/script_controller.cpp @@ -21,6 +21,7 @@ #include "../../ai/ai_config.hpp" #include "../../ai/ai.hpp" #include "../script_fatalerror.hpp" +#include "../script_info.hpp" #include "../script_suspend.hpp" #include "script_log.hpp" @@ -96,7 +97,7 @@ ScriptController::~ScriptController() snprintf(library_name, sizeof(library_name), "%s.%d", library, version); strtolower(library_name); - AILibrary *lib = AI::FindLibrary(library, version); + ScriptInfo *lib = (ScriptInfo *)AI::FindLibrary(library, version); if (lib == NULL) { char error[1024]; snprintf(error, sizeof(error), "couldn't find library '%s' with version %d", library, version); diff --git a/src/script/script_config.cpp b/src/script/script_config.cpp new file mode 100644 --- /dev/null +++ b/src/script/script_config.cpp @@ -0,0 +1,201 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file script_config.cpp Implementation of ScriptConfig. */ + +#include "../stdafx.h" +#include "../settings_type.h" +#include "../core/random_func.hpp" +#include "script_info.hpp" +#include "script_config.hpp" + +void ScriptConfig::Change(const char *name, int version, bool force_exact_match, bool is_random) +{ + free(this->name); + this->name = (name == NULL) ? NULL : strdup(name); + this->info = (name == NULL) ? NULL : this->FindInfo(this->name, version, force_exact_match); + this->version = (info == NULL) ? -1 : info->GetVersion(); + this->is_random = is_random; + if (this->config_list != NULL) delete this->config_list; + this->config_list = (info == NULL) ? NULL : new ScriptConfigItemList(); + if (this->config_list != NULL) this->PushExtraConfigList(); + + this->ClearConfigList(); + + if (_game_mode == GM_NORMAL && this->info != NULL) { + /* If we're in an existing game and the Script is changed, set all settings + * for the Script that have the random flag to a random value. */ + for (ScriptConfigItemList::const_iterator it = this->info->GetConfigList()->begin(); it != this->info->GetConfigList()->end(); it++) { + if ((*it).flags & SCRIPTCONFIG_RANDOM) { + this->SetSetting((*it).name, InteractiveRandomRange((*it).max_value - (*it).min_value) + (*it).min_value); + } + } + this->AddRandomDeviation(); + } +} + +ScriptConfig::ScriptConfig(const ScriptConfig *config) +{ + this->name = (config->name == NULL) ? NULL : strdup(config->name); + this->info = config->info; + this->version = config->version; + this->config_list = NULL; + this->is_random = config->is_random; + + for (SettingValueList::const_iterator it = config->settings.begin(); it != config->settings.end(); it++) { + this->settings[strdup((*it).first)] = (*it).second; + } + this->AddRandomDeviation(); +} + +ScriptConfig::~ScriptConfig() +{ + free(this->name); + this->ResetSettings(); + if (this->config_list != NULL) delete this->config_list; +} + +ScriptInfo *ScriptConfig::GetInfo() const +{ + return this->info; +} + +const ScriptConfigItemList *ScriptConfig::GetConfigList() +{ + if (this->info != NULL) return this->info->GetConfigList(); + if (this->config_list == NULL) { + this->config_list = new ScriptConfigItemList(); + this->PushExtraConfigList(); + } + return this->config_list; +} + +void ScriptConfig::ClearConfigList() +{ + for (SettingValueList::iterator it = this->settings.begin(); it != this->settings.end(); it++) { + free((*it).first); + } + this->settings.clear(); +} + +int ScriptConfig::GetSetting(const char *name) const +{ + /* Return default values if the difficulty is not set to Custom */ + if (GetGameSettings().difficulty.diff_level != 3) { + return this->info->GetSettingDefaultValue(name); + } + + SettingValueList::const_iterator it = this->settings.find(name); + if (it == this->settings.end()) return this->info->GetSettingDefaultValue(name); + return (*it).second; +} + +void ScriptConfig::SetSetting(const char *name, int value) +{ + /* You can only set Script specific settings if an Script is selected. */ + if (this->info == NULL) return; + + const ScriptConfigItem *config_item = this->info->GetConfigItem(name); + if (config_item == NULL) return; + + value = Clamp(value, config_item->min_value, config_item->max_value); + + SettingValueList::iterator it = this->settings.find(name); + if (it != this->settings.end()) { + (*it).second = value; + } else { + this->settings[strdup(name)] = value; + } +} + +void ScriptConfig::ResetSettings() +{ + for (SettingValueList::iterator it = this->settings.begin(); it != this->settings.end(); it++) { + free((*it).first); + } + this->settings.clear(); +} + +void ScriptConfig::AddRandomDeviation() +{ + for (ScriptConfigItemList::const_iterator it = this->GetConfigList()->begin(); it != this->GetConfigList()->end(); it++) { + if ((*it).random_deviation != 0) { + this->SetSetting((*it).name, InteractiveRandomRange((*it).random_deviation * 2) - (*it).random_deviation + this->GetSetting((*it).name)); + } + } +} + +bool ScriptConfig::HasScript() const +{ + return this->info != NULL; +} + +bool ScriptConfig::IsRandom() const +{ + return this->is_random; +} + +const char *ScriptConfig::GetName() const +{ + return this->name; +} + +int ScriptConfig::GetVersion() const +{ + return this->version; +} + +void ScriptConfig::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 ScriptConfig::SettingsToString(char *string, size_t size) const +{ + string[0] = '\0'; + for (SettingValueList::const_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_t needed_size = strlen((*it).first) + 1 + strlen(no) + 1; + /* If it doesn't fit, skip the next settings */ + if (size <= needed_size) break; + size -= needed_size; + + strcat(string, (*it).first); + strcat(string, "="); + strcat(string, no); + strcat(string, ","); + } + /* Remove the last ',', but only if at least one setting was saved. */ + size_t len = strlen(string); + if (len > 0) string[len - 1] = '\0'; +} diff --git a/src/script/script_config.hpp b/src/script/script_config.hpp new file mode 100644 --- /dev/null +++ b/src/script/script_config.hpp @@ -0,0 +1,191 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file script_config.hpp ScriptConfig stores the configuration settings of every Script. */ + +#ifndef SCRIPT_CONFIG_HPP +#define SCRIPT_CONFIG_HPP + +#include +#include +#include "../core/smallmap_type.hpp" +#include "../core/string_compare_type.hpp" +#include "../company_type.h" + +/** Bitmask of flags for Script settings. */ +enum ScriptConfigFlags { + SCRIPTCONFIG_NONE = 0x0, ///< No flags set. + SCRIPTCONFIG_RANDOM = 0x1, ///< When randomizing the Script, pick any value between min_value and max_value when on custom difficulty setting. + SCRIPTCONFIG_BOOLEAN = 0x2, ///< This value is a boolean (either 0 (false) or 1 (true) ). + SCRIPTCONFIG_INGAME = 0x4, ///< This setting can be changed while the Script is running. + SCRIPTCONFIG_DEVELOPER = 0x8, ///< This setting will only be visible when the Script development tools are active. +}; + +typedef SmallMap LabelMapping; ///< Map-type used to map the setting numbers to labels. + +/** Info about a single Script setting. */ +struct ScriptConfigItem { + 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. + int random_deviation; ///< The maximum random deviation from the default value. + int step_size; ///< The step size in the gui. + ScriptConfigFlags flags; ///< Flags for the configuration setting. + LabelMapping *labels; ///< Text labels for the integer values. +}; + +typedef std::list ScriptConfigItemList; ///< List of ScriptConfig items. + +extern ScriptConfigItem _start_date_config; + +/** + * Script settings. + */ +class ScriptConfig { +protected: + /** List with name=>value pairs of all script-specific settings */ + typedef std::map SettingValueList; + +public: + ScriptConfig() : + name(NULL), + version(-1), + info(NULL), + config_list(NULL), + is_random(false) + {} + + /** + * Create a new Script config that is a copy of an existing config. + * @param config The object to copy. + */ + ScriptConfig(const ScriptConfig *config); + + /** Delete an Script configuration. */ + virtual ~ScriptConfig(); + + /** + * Set another Script to be loaded in this slot. + * @param name The name of the Script. + * @param version The version of the Script to load, or -1 of latest. + * @param force_exact_match If true try to find the exact same version + * as specified. If false any compatible version is ok. + * @param is_random Is the Script chosen randomly? + */ + void Change(const char *name, int version = -1, bool force_exact_match = false, bool is_random = false); + + /** + * Get the ScriptInfo linked to this ScriptConfig. + */ + class ScriptInfo *GetInfo() const; + + /** + * Get the config list for this ScriptConfig. + */ + const ScriptConfigItemList *GetConfigList(); + + /** + * Where to get the config from, either default (depends on current game + * mode) or force either newgame or normal + */ + enum ScriptSettingSource { + SSS_DEFAULT, ///< Get the Script config from the current game mode + SSS_FORCE_NEWGAME, ///< Get the newgame Script config + SSS_FORCE_GAME, ///< Get the Script config from the current game + }; + + /** + * 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. + */ + virtual int GetSetting(const char *name) const; + + /** + * Set the value of a setting for this config. + */ + virtual void SetSetting(const char *name, int value); + + /** + * Reset all settings to their default value. + */ + void ResetSettings(); + + /** + * Randomize all settings the Script requested to be randomized. + */ + void AddRandomDeviation(); + + /** + * Is this config attached to an Script? In other words, is there a Script + * that is assigned to this slot. + */ + bool HasScript() const; + + /** + * Is the current Script a randomly chosen Script? + */ + bool IsRandom() const; + + /** + * Get the name of the Script. + */ + const char *GetName() const; + + /** + * Get the version of the Script. + */ + int GetVersion() const; + + /** + * Convert a string which is stored in the config file or savegames to + * custom settings of this Script. + */ + 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, size_t size) const; + +protected: + const char *name; ///< Name of the Script + int version; ///< Version of the Script + class ScriptInfo *info; ///< ScriptInfo object for related to this Script version + SettingValueList settings; ///< List with all setting=>value pairs that are configure for this Script + ScriptConfigItemList *config_list; ///< List with all settings defined by this Script + bool is_random; ///< True if the AI in this slot was randomly chosen. + + /** + * In case you have mandatory non-Script-definable config entries in your + * list, add them to this function. + */ + virtual void PushExtraConfigList() {}; + + /** + * Routine that clears the config list. + */ + virtual void ClearConfigList(); + + /** + * This function should call back to the Scanner in charge of this Config, + * to find the ScriptInfo belonging to a name+version. + */ + virtual ScriptInfo *FindInfo(const char *name, int version, bool force_exact_match) = 0; +}; + +#endif /* SCRIPT_CONFIG_HPP */ diff --git a/src/script/script_info.cpp b/src/script/script_info.cpp --- a/src/script/script_info.cpp +++ b/src/script/script_info.cpp @@ -10,6 +10,7 @@ /** @file script_info.cpp Implementation of ScriptInfo. */ #include "../stdafx.h" +#include "../settings_type.h" #include "squirrel_helper.hpp" @@ -20,9 +21,25 @@ static const int MAX_GET_OPS = 1000; /** Number of operations to create an instance of a script. */ static const int MAX_CREATEINSTANCE_OPS = 100000; +/** Maximum number of operations allowed for getting a particular setting. */ +static const int MAX_GET_SETTING_OPS = 100000; + ScriptInfo::~ScriptInfo() { + /* Free all allocated strings */ + for (ScriptConfigItemList::iterator it = this->config_list.begin(); it != this->config_list.end(); it++) { + free((*it).name); + free((*it).description); + if (it->labels != NULL) { + for (LabelMapping::iterator it2 = (*it).labels->Begin(); it2 != (*it).labels->End(); it2++) { + free(it2->second); + } + delete it->labels; + } + } + this->config_list.clear(); + free(this->author); free(this->name); free(this->short_name); @@ -90,5 +107,200 @@ bool ScriptInfo::CheckMethod(const char if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetURL", &info->url, MAX_GET_OPS)) return SQ_ERROR; } + /* Check if we have settings */ + if (info->engine->MethodExists(*info->SQ_instance, "GetSettings")) { + if (!info->GetSettings()) return SQ_ERROR; + } + return 0; } + +bool ScriptInfo::GetSettings() +{ + return this->engine->CallMethod(*this->SQ_instance, "GetSettings", NULL, MAX_GET_SETTING_OPS); +} + +SQInteger ScriptInfo::AddSetting(HSQUIRRELVM vm) +{ + ScriptConfigItem config; + memset(&config, 0, sizeof(config)); + config.max_value = 1; + config.step_size = 1; + uint 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; + if (SQ_FAILED(sq_getstring(vm, -2, &sqkey))) return SQ_ERROR; + const char *key = SQ2OTTD(sqkey); + + if (strcmp(key, "name") == 0) { + const SQChar *sqvalue; + if (SQ_FAILED(sq_getstring(vm, -1, &sqvalue))) return SQ_ERROR; + char *name = strdup(SQ2OTTD(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 = strchr(name, '=')) != NULL) *s = '_'; + while ((s = strchr(name, ',')) != NULL) *s = '_'; + config.name = name; + items |= 0x001; + } else if (strcmp(key, "description") == 0) { + const SQChar *sqdescription; + if (SQ_FAILED(sq_getstring(vm, -1, &sqdescription))) return SQ_ERROR; + config.description = strdup(SQ2OTTD(sqdescription)); + items |= 0x002; + } else if (strcmp(key, "min_value") == 0) { + SQInteger res; + if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR; + config.min_value = res; + items |= 0x004; + } else if (strcmp(key, "max_value") == 0) { + SQInteger res; + if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR; + config.max_value = res; + items |= 0x008; + } else if (strcmp(key, "easy_value") == 0) { + SQInteger res; + if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR; + config.easy_value = res; + items |= 0x010; + } else if (strcmp(key, "medium_value") == 0) { + SQInteger res; + if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR; + config.medium_value = res; + items |= 0x020; + } else if (strcmp(key, "hard_value") == 0) { + SQInteger res; + if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR; + config.hard_value = res; + items |= 0x040; + } else if (strcmp(key, "random_deviation") == 0) { + SQInteger res; + if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR; + config.random_deviation = res; + items |= 0x200; + } else if (strcmp(key, "custom_value") == 0) { + SQInteger res; + if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR; + config.custom_value = res; + items |= 0x080; + } else if (strcmp(key, "step_size") == 0) { + SQInteger res; + if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR; + config.step_size = res; + } else if (strcmp(key, "flags") == 0) { + SQInteger res; + if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR; + config.flags = (ScriptConfigFlags)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); + + /* Don't allow both random_deviation and SCRIPTCONFIG_RANDOM to + * be set for the same config item. */ + if ((items & 0x200) != 0 && (config.flags & SCRIPTCONFIG_RANDOM) != 0) { + char error[1024]; + snprintf(error, sizeof(error), "Setting both random_deviation and SCRIPTCONFIG_RANDOM is not allowed"); + this->engine->ThrowError(error); + return SQ_ERROR; + } + /* Reset the bit for random_deviation as it's optional. */ + items &= ~0x200; + + /* Make sure all properties are defined */ + uint mask = (config.flags & SCRIPTCONFIG_BOOLEAN) ? 0x1F3 : 0x1FF; + if (items != mask) { + char error[1024]; + snprintf(error, sizeof(error), "please define all properties of a setting (min/max not allowed for booleans)"); + this->engine->ThrowError(error); + return SQ_ERROR; + } + + this->config_list.push_back(config); + return 0; +} + +SQInteger ScriptInfo::AddLabels(HSQUIRRELVM vm) +{ + const SQChar *sq_setting_name; + if (SQ_FAILED(sq_getstring(vm, -2, &sq_setting_name))) return SQ_ERROR; + const char *setting_name = SQ2OTTD(sq_setting_name); + + ScriptConfigItem *config = NULL; + for (ScriptConfigItemList::iterator it = this->config_list.begin(); it != this->config_list.end(); it++) { + if (strcmp((*it).name, setting_name) == 0) config = &(*it); + } + + if (config == NULL) { + char error[1024]; + snprintf(error, sizeof(error), "Trying to add labels for non-defined setting '%s'", setting_name); + this->engine->ThrowError(error); + return SQ_ERROR; + } + if (config->labels != NULL) return SQ_ERROR; + + config->labels = new LabelMapping; + + /* Read the table and find all labels */ + sq_pushnull(vm); + while (SQ_SUCCEEDED(sq_next(vm, -2))) { + const SQChar *sq_key; + const SQChar *sq_label; + if (SQ_FAILED(sq_getstring(vm, -2, &sq_key))) return SQ_ERROR; + if (SQ_FAILED(sq_getstring(vm, -1, &sq_label))) return SQ_ERROR; + /* Because squirrel doesn't support identifiers starting with a digit, + * we skip the first character. */ + const char *key_string = SQ2OTTD(sq_key); + int key = atoi(key_string + 1); + const char *label = SQ2OTTD(sq_label); + + /* !Contains() prevents strdup from leaking. */ + if (!config->labels->Contains(key)) config->labels->Insert(key, strdup(label)); + + sq_pop(vm, 2); + } + sq_pop(vm, 1); + + return 0; +} + +const ScriptConfigItemList *ScriptInfo::GetConfigList() const +{ + return &this->config_list; +} + +const ScriptConfigItem *ScriptInfo::GetConfigItem(const char *name) const +{ + for (ScriptConfigItemList::const_iterator it = this->config_list.begin(); it != this->config_list.end(); it++) { + if (strcmp((*it).name, name) == 0) return &(*it); + } + return NULL; +} + +int ScriptInfo::GetSettingDefaultValue(const char *name) const +{ + for (ScriptConfigItemList::const_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 (GetGameSettings().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; +} diff --git a/src/script/script_info.hpp b/src/script/script_info.hpp --- a/src/script/script_info.hpp +++ b/src/script/script_info.hpp @@ -15,6 +15,8 @@ #include #include "../misc/countedptr.hpp" +#include "script_config.hpp" + class ScriptInfo : public SimpleCountedObject { public: ScriptInfo() : @@ -98,9 +100,41 @@ public: */ virtual class ScriptScanner *GetScanner() { return this->scanner; } + /** + * Get the settings of the Script. + */ + bool GetSettings(); + + /** + * Get the config list for this Script. + */ + const ScriptConfigItemList *GetConfigList() const; + + /** + * Get the description of a certain Script config option. + */ + const ScriptConfigItem *GetConfigItem(const char *name) const; + + /** + * Set a setting. + */ + SQInteger AddSetting(HSQUIRRELVM vm); + + /** + * Add labels for a setting. + */ + SQInteger AddLabels(HSQUIRRELVM vm); + + /** + * Get the default value for a setting. + */ + int GetSettingDefaultValue(const char *name) const; + + protected: - class Squirrel *engine; ///< Engine used to register for Squirrel. - HSQOBJECT *SQ_instance; ///< The Squirrel instance created for this info. + class Squirrel *engine; ///< Engine used to register for Squirrel. + HSQOBJECT *SQ_instance; ///< The Squirrel instance created for this info. + ScriptConfigItemList config_list; ///< List of settings from this Script. private: char *main_script; ///< The full path of the script. diff --git a/src/settings.cpp b/src/settings.cpp --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1320,7 +1320,7 @@ static void AILoadConfig(IniFile *ini, c /* Clean any configured AI */ for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) { - AIConfig::GetConfig(c, AIConfig::AISS_FORCE_NEWGAME)->ChangeAI(NULL); + AIConfig::GetConfig(c, AIConfig::SSS_FORCE_NEWGAME)->Change(NULL); } /* If no group exists, return */ @@ -1328,10 +1328,10 @@ static void AILoadConfig(IniFile *ini, c CompanyID c = COMPANY_FIRST; for (item = group->item; c < MAX_COMPANIES && item != NULL; c++, item = item->next) { - AIConfig *config = AIConfig::GetConfig(c, AIConfig::AISS_FORCE_NEWGAME); + AIConfig *config = AIConfig::GetConfig(c, AIConfig::SSS_FORCE_NEWGAME); - config->ChangeAI(item->name); - if (!config->HasAI()) { + config->Change(item->name); + if (!config->HasScript()) { 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; @@ -1443,12 +1443,12 @@ static void AISaveConfig(IniFile *ini, c group->Clear(); for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) { - AIConfig *config = AIConfig::GetConfig(c, AIConfig::AISS_FORCE_NEWGAME); + AIConfig *config = AIConfig::GetConfig(c, AIConfig::SSS_FORCE_NEWGAME); const char *name; char value[1024]; config->SettingsToString(value, lengthof(value)); - if (config->HasAI()) { + if (config->HasScript()) { name = config->GetName(); } else { name = "none";