diff options
Diffstat (limited to 'src/script')
-rw-r--r-- | src/script/api/script_controller.cpp | 3 | ||||
-rw-r--r-- | src/script/script_config.cpp | 201 | ||||
-rw-r--r-- | src/script/script_config.hpp | 191 | ||||
-rw-r--r-- | src/script/script_info.cpp | 212 | ||||
-rw-r--r-- | src/script/script_info.hpp | 38 |
5 files changed, 642 insertions, 3 deletions
diff --git a/src/script/api/script_controller.cpp b/src/script/api/script_controller.cpp index e81af3603..0638a6651 100644 --- 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 index 000000000..1818270e8 --- /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 <http://www.gnu.org/licenses/>. + */ + +/** @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 index 000000000..e16f8999a --- /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 <http://www.gnu.org/licenses/>. + */ + +/** @file script_config.hpp ScriptConfig stores the configuration settings of every Script. */ + +#ifndef SCRIPT_CONFIG_HPP +#define SCRIPT_CONFIG_HPP + +#include <map> +#include <list> +#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<int, char *> 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<ScriptConfigItem> 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<const char *, int, StringCompare> 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 index 862907c8b..afa90f40e 100644 --- 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 *name) const 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 index 1e118f164..b686d53f4 100644 --- a/src/script/script_info.hpp +++ b/src/script/script_info.hpp @@ -15,6 +15,8 @@ #include <squirrel.h> #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. |