diff --git a/src/ai/ai.hpp b/src/ai/ai.hpp index b005f9c990..9a33be9446 100644 --- a/src/ai/ai.hpp +++ b/src/ai/ai.hpp @@ -27,9 +27,9 @@ public: /** * Start a new AI company. * @param company At which slot the AI company should start. - * @param rerandomise_ai Whether to rerandomise the configured AI. + * @param deviate Whether to apply random deviation to the configured AI. */ - static void StartNew(CompanyID company, bool rerandomise_ai = true); + static void StartNew(CompanyID company, bool deviate = true); /** * Called every game-tick to let AIs do something. diff --git a/src/ai/ai_config.cpp b/src/ai/ai_config.cpp index 55e9bc0ba1..bdb986692e 100644 --- a/src/ai/ai_config.cpp +++ b/src/ai/ai_config.cpp @@ -8,6 +8,7 @@ /** @file ai_config.cpp Implementation of AIConfig. */ #include "../stdafx.h" +#include "../company_base.h" #include "../settings_type.h" #include "../string_func.h" #include "ai.hpp" @@ -24,6 +25,10 @@ if (source == SSS_FORCE_NEWGAME || (source == SSS_DEFAULT && _game_mode == GM_MENU)) { config = &_settings_newgame.ai_config[company]; } else { + if (source != SSS_FORCE_GAME) { + Company *c = Company::GetIfValid(company); + if (c != nullptr && c->ai_config != nullptr) return c->ai_config.get(); + } config = &_settings_game.ai_config[company]; } if (*config == nullptr) *config = new AIConfig(); diff --git a/src/ai/ai_core.cpp b/src/ai/ai_core.cpp index ac60b78ad9..297318371a 100644 --- a/src/ai/ai_core.cpp +++ b/src/ai/ai_core.cpp @@ -33,27 +33,32 @@ return !_networking || (_network_server && _settings_game.ai.ai_in_multiplayer); } -/* static */ void AI::StartNew(CompanyID company, bool rerandomise_ai) +/* static */ void AI::StartNew(CompanyID company, bool deviate) { assert(Company::IsValidID(company)); /* Clients shouldn't start AIs */ if (_networking && !_network_server) return; - AIConfig *config = AIConfig::GetConfig(company, AIConfig::SSS_FORCE_GAME); + Backup cur_company(_current_company, company, FILE_LINE); + Company *c = Company::Get(company); + + AIConfig *config = c->ai_config.get(); + if (config == nullptr) { + c->ai_config = std::make_unique(AIConfig::GetConfig(company, AIConfig::SSS_FORCE_GAME)); + config = c->ai_config.get(); + } + AIInfo *info = config->GetInfo(); - if (info == nullptr || (rerandomise_ai && config->IsRandom())) { + if (info == nullptr) { info = AI::scanner_info->SelectRandomAI(); assert(info != nullptr); /* Load default data and store the name in the settings */ - config->Change(info->GetName(), -1, false, true); + config->Change(info->GetName(), -1, false); } - if (rerandomise_ai) config->AddRandomDeviation(); + if (deviate) config->AddRandomDeviation(); config->AnchorUnchangeableSettings(); - Backup cur_company(_current_company, company, FILE_LINE); - Company *c = Company::Get(company); - c->ai_info = info; assert(c->ai_instance == nullptr); c->ai_instance = new AIInstance(); @@ -111,11 +116,11 @@ delete c->ai_instance; c->ai_instance = nullptr; c->ai_info = nullptr; + c->ai_config.reset(); cur_company.Restore(); InvalidateWindowClassesData(WC_SCRIPT_DEBUG, -1); - CloseWindowById(WC_SCRIPT_SETTINGS, company); } /* static */ void AI::Pause(CompanyID company) diff --git a/src/ai/ai_instance.cpp b/src/ai/ai_instance.cpp index a5eda5c617..0c028e9e86 100644 --- a/src/ai/ai_instance.cpp +++ b/src/ai/ai_instance.cpp @@ -66,7 +66,7 @@ void AIInstance::Died() ShowScriptDebugWindow(_current_company); - const AIInfo *info = AIConfig::GetConfig(_current_company, AIConfig::SSS_FORCE_GAME)->GetInfo(); + const AIInfo *info = AIConfig::GetConfig(_current_company)->GetInfo(); if (info != nullptr) { ShowErrorMessage(STR_ERROR_AI_PLEASE_REPORT_CRASH, INVALID_STRING_ID, WL_WARNING); diff --git a/src/company_base.h b/src/company_base.h index 1264aaa2a8..fdfa4a2428 100644 --- a/src/company_base.h +++ b/src/company_base.h @@ -122,6 +122,7 @@ struct Company : CompanyProperties, CompanyPool::PoolItem<&_company_pool> { class AIInstance *ai_instance; class AIInfo *ai_info; + std::unique_ptr ai_config; GroupStatistics group_all[VEH_COMPANY_END]; ///< NOSAVE: Statistics for the ALL_GROUP group. GroupStatistics group_default[VEH_COMPANY_END]; ///< NOSAVE: Statistics for the DEFAULT_GROUP group. diff --git a/src/company_cmd.cpp b/src/company_cmd.cpp index 32743dd666..80e62ade16 100644 --- a/src/company_cmd.cpp +++ b/src/company_cmd.cpp @@ -20,6 +20,7 @@ #include "network/network_base.h" #include "network/network_admin.h" #include "ai/ai.hpp" +#include "ai/ai_config.hpp" #include "company_manager_face.h" #include "window_func.h" #include "strings_func.h" diff --git a/src/game/game.hpp b/src/game/game.hpp index 9d83920024..7ec438e88e 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -31,9 +31,9 @@ public: /** * Start up a new GameScript. - * @param randomise Whether to randomise the configured GameScript. + * @param deviate Whether to apply random deviation to the configured GameScript. */ - static void StartNew(bool randomise = true); + static void StartNew(bool deviate = true); /** * Uninitialize the Game system. diff --git a/src/game/game_core.cpp b/src/game/game_core.cpp index 51279a742a..0230ac23e2 100644 --- a/src/game/game_core.cpp +++ b/src/game/game_core.cpp @@ -69,7 +69,7 @@ } } -/* static */ void Game::StartNew(bool randomise) +/* static */ void Game::StartNew(bool deviate) { if (Game::instance != nullptr) return; @@ -83,7 +83,7 @@ GameInfo *info = config->GetInfo(); if (info == nullptr) return; - if (randomise) config->AddRandomDeviation(); + if (deviate) config->AddRandomDeviation(); config->AnchorUnchangeableSettings(); Backup cur_company(_current_company, FILE_LINE); diff --git a/src/saveload/ai_sl.cpp b/src/saveload/ai_sl.cpp index 927c130470..062d0bd090 100644 --- a/src/saveload/ai_sl.cpp +++ b/src/saveload/ai_sl.cpp @@ -32,13 +32,19 @@ static const SaveLoad _ai_company_desc[] = { SLEG_SSTR("name", _ai_saveload_name, SLE_STR), SLEG_SSTR("settings", _ai_saveload_settings, SLE_STR), SLEG_CONDVAR("version", _ai_saveload_version, SLE_UINT32, SLV_108, SL_MAX_VERSION), - SLEG_CONDVAR("is_random", _ai_saveload_is_random, SLE_BOOL, SLV_136, SL_MAX_VERSION), + SLEG_CONDVAR("is_random", _ai_saveload_is_random, SLE_BOOL, SLV_136, SLV_AI_LOCAL_CONFIG), +}; + +static const SaveLoad _ai_running_desc[] = { + SLEG_CONDSSTR("running_name", _ai_saveload_name, SLE_STR, SLV_AI_LOCAL_CONFIG, SL_MAX_VERSION), + SLEG_CONDSSTR("running_settings", _ai_saveload_settings, SLE_STR, SLV_AI_LOCAL_CONFIG, SL_MAX_VERSION), + SLEG_CONDVAR("running_version", _ai_saveload_version, SLE_UINT32, SLV_AI_LOCAL_CONFIG, SL_MAX_VERSION), }; static void SaveReal_AIPL(int *index_ptr) { CompanyID index = (CompanyID)*index_ptr; - AIConfig *config = AIConfig::GetConfig(index); + AIConfig *config = AIConfig::GetConfig(index, AIConfig::SSS_FORCE_GAME); if (config->HasScript()) { _ai_saveload_name = config->GetName(); @@ -49,12 +55,21 @@ static void SaveReal_AIPL(int *index_ptr) _ai_saveload_version = -1; } - _ai_saveload_is_random = config->IsRandom(); _ai_saveload_settings = config->SettingsToString(); SlObject(nullptr, _ai_company_desc); + + if (!Company::IsValidAiID(index)) return; + /* If the AI was active, store its data too */ - if (Company::IsValidAiID(index)) AI::Save(index); + config = AIConfig::GetConfig(index); + _ai_saveload_name = config->GetName(); + _ai_saveload_version = config->GetVersion(); + _ai_saveload_settings = config->SettingsToString(); + + SlObject(nullptr, _ai_running_desc); + AI::Save(index); + } struct AIPLChunkHandler : ChunkHandler { @@ -78,42 +93,66 @@ struct AIPLChunkHandler : ChunkHandler { SlObject(nullptr, slt); if (_game_mode == GM_MENU || (_networking && !_network_server)) { - if (Company::IsValidAiID(index)) AIInstance::LoadEmpty(); + if (Company::IsValidAiID(index)) { + SlObject(nullptr, _ai_running_desc); + AIInstance::LoadEmpty(); + } continue; } AIConfig *config = AIConfig::GetConfig(index, AIConfig::SSS_FORCE_GAME); - if (_ai_saveload_name.empty()) { + if (_ai_saveload_name.empty() || _ai_saveload_is_random) { /* A random AI. */ - config->Change(std::nullopt, -1, false, true); + config->Change(std::nullopt, -1, false); } else { - config->Change(_ai_saveload_name, _ai_saveload_version, false, _ai_saveload_is_random); + config->Change(_ai_saveload_name, _ai_saveload_version, false); if (!config->HasScript()) { - /* No version of the AI available that can load the data. Try to load the + /* No version of the AI available. Try to configure the * latest version of the AI instead. */ - config->Change(_ai_saveload_name, -1, false, _ai_saveload_is_random); + config->Change(_ai_saveload_name, -1, false); if (!config->HasScript()) { if (_ai_saveload_name.compare("%_dummy") != 0) { Debug(script, 0, "The savegame has an AI by the name '{}', version {} which is no longer available.", _ai_saveload_name, _ai_saveload_version); - Debug(script, 0, "A random other AI will be loaded in its place."); - } else { - Debug(script, 0, "The savegame had no AIs available at the time of saving."); - Debug(script, 0, "A random available AI will be loaded now."); + Debug(script, 0, "Configuration switched to Random AI."); } } else { Debug(script, 0, "The savegame has an AI by the name '{}', version {} which is no longer available.", _ai_saveload_name, _ai_saveload_version); - Debug(script, 0, "The latest version of that AI has been loaded instead, but it'll not get the savegame data as it's incompatible."); + Debug(script, 0, "The latest version of that AI has been configured instead"); } - /* Make sure the AI doesn't get the saveload data, as it was not the - * writer of the saveload data in the first place */ - _ai_saveload_version = -1; } } - config->StringToSettings(_ai_saveload_settings); + if (!Company::IsValidAiID(index)) continue; + /* Load the AI saved data */ - if (Company::IsValidAiID(index)) config->SetToLoadData(AIInstance::Load(_ai_saveload_version)); + SlObject(nullptr, _ai_running_desc); + + Company::Get(index)->ai_config = std::make_unique(); + config = Company::Get(index)->ai_config.get(); + config->Change(_ai_saveload_name, _ai_saveload_version, false); + 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->Change(_ai_saveload_name, -1, false); + if (!config->HasScript()) { + if (_ai_saveload_name.compare("%_dummy") != 0) { + Debug(script, 0, "The savegame has an AI by the name '{}', version {} which is no longer available.", _ai_saveload_name, _ai_saveload_version); + Debug(script, 0, "A random other AI will be loaded in its place."); + } else { + Debug(script, 0, "The savegame had no AIs available at the time of saving."); + Debug(script, 0, "A random available AI will be loaded now."); + } + } else { + Debug(script, 0, "The savegame has an AI by the name '{}', version {} which is no longer available.", _ai_saveload_name, _ai_saveload_version); + Debug(script, 0, "The latest version of that AI has been loaded instead, but it'll not get the savegame data as it's incompatible."); + } + /* Make sure the AI doesn't get the saveload data, as it was not the + * writer of the saveload data in the first place */ + _ai_saveload_version = -1; + } + config->StringToSettings(_ai_saveload_settings); + config->SetToLoadData(AIInstance::Load(_ai_saveload_version)); } } diff --git a/src/saveload/compat/game_sl_compat.h b/src/saveload/compat/game_sl_compat.h index cc05913d6d..dc84ec0de7 100644 --- a/src/saveload/compat/game_sl_compat.h +++ b/src/saveload/compat/game_sl_compat.h @@ -17,7 +17,7 @@ const SaveLoadCompat _game_script_sl_compat[] = { SLC_VAR("name"), SLC_VAR("settings"), SLC_VAR("version"), - SLC_VAR("is_random"), + SLC_NULL(1, SL_MIN_VERSION, SLV_AI_LOCAL_CONFIG), }; /** Original field order for SlGameLanguageString. */ diff --git a/src/saveload/game_sl.cpp b/src/saveload/game_sl.cpp index 87083c6694..7196c5bc29 100644 --- a/src/saveload/game_sl.cpp +++ b/src/saveload/game_sl.cpp @@ -25,13 +25,11 @@ static std::string _game_saveload_name; static int _game_saveload_version; static std::string _game_saveload_settings; -static bool _game_saveload_is_random; static const SaveLoad _game_script_desc[] = { SLEG_SSTR("name", _game_saveload_name, SLE_STR), SLEG_SSTR("settings", _game_saveload_settings, SLE_STR), SLEG_VAR("version", _game_saveload_version, SLE_UINT32), - SLEG_VAR("is_random", _game_saveload_is_random, SLE_BOOL), }; static void SaveReal_GSDT(int *) @@ -47,7 +45,6 @@ static void SaveReal_GSDT(int *) _game_saveload_version = -1; } - _game_saveload_is_random = config->IsRandom(); _game_saveload_settings = config->SettingsToString(); SlObject(nullptr, _game_script_desc); @@ -77,11 +74,11 @@ struct GSDTChunkHandler : ChunkHandler { GameConfig *config = GameConfig::GetConfig(GameConfig::SSS_FORCE_GAME); if (!_game_saveload_name.empty()) { - config->Change(_game_saveload_name, _game_saveload_version, false, _game_saveload_is_random); + config->Change(_game_saveload_name, _game_saveload_version, false); if (!config->HasScript()) { /* No version of the GameScript available that can load the data. Try to load the * latest version of the GameScript instead. */ - config->Change(_game_saveload_name, -1, false, _game_saveload_is_random); + config->Change(_game_saveload_name, -1, false); if (!config->HasScript()) { if (_game_saveload_name.compare("%_dummy") != 0) { Debug(script, 0, "The savegame has an GameScript by the name '{}', version {} which is no longer available.", _game_saveload_name, _game_saveload_version); diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index 4a402c3f12..09a5e90f12 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -375,6 +375,7 @@ enum SaveLoadVersion : uint16_t { SLV_MAX_LOAN_FOR_COMPANY, ///< 330 PR#11224 Separate max loan for each company. SLV_DEPOT_UNBUNCHING, ///< 331 PR#11945 Allow unbunching shared order vehicles at a depot. + SLV_AI_LOCAL_CONFIG, ///< 332 PR#12003 Config of running AI is stored inside Company. SL_MAX_VERSION, ///< Highest possible saveload version }; diff --git a/src/script/script_config.cpp b/src/script/script_config.cpp index 852ffa1f43..21cee20925 100644 --- a/src/script/script_config.cpp +++ b/src/script/script_config.cpp @@ -18,7 +18,7 @@ #include "../safeguards.h" -void ScriptConfig::Change(std::optional name, int version, bool force_exact_match, bool is_random) +void ScriptConfig::Change(std::optional name, int version, bool force_exact_match) { if (name.has_value()) { this->name = std::move(name.value()); @@ -27,7 +27,6 @@ void ScriptConfig::Change(std::optional name, int version, bo this->info = nullptr; } this->version = (info == nullptr) ? -1 : info->GetVersion(); - this->is_random = is_random; this->config_list.reset(); this->to_load_data.reset(); @@ -39,7 +38,6 @@ ScriptConfig::ScriptConfig(const ScriptConfig *config) this->name = config->name; this->info = config->info; this->version = config->version; - this->is_random = config->is_random; this->to_load_data.reset(); for (const auto &item : config->settings) { @@ -139,11 +137,6 @@ bool ScriptConfig::HasScript() const return this->info != nullptr; } -bool ScriptConfig::IsRandom() const -{ - return this->is_random; -} - const std::string &ScriptConfig::GetName() const { return this->name; diff --git a/src/script/script_config.hpp b/src/script/script_config.hpp index 58c40189e9..42c77a845c 100644 --- a/src/script/script_config.hpp +++ b/src/script/script_config.hpp @@ -56,7 +56,6 @@ public: ScriptConfig() : version(-1), info(nullptr), - is_random(false), to_load_data(nullptr) {} @@ -77,7 +76,7 @@ public: * as specified. If false any compatible version is ok. * @param is_random Is the Script chosen randomly? */ - void Change(std::optional name, int version = -1, bool force_exact_match = false, bool is_random = false); + void Change(std::optional name, int version = -1, bool force_exact_match = false); /** * Get the ScriptInfo linked to this ScriptConfig. @@ -144,11 +143,6 @@ public: */ bool HasScript() const; - /** - * Is the current Script a randomly chosen Script? - */ - bool IsRandom() const; - /** * Get the name of the Script. */ @@ -188,7 +182,6 @@ protected: 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 std::unique_ptr config_list; ///< List with all settings defined by this Script - bool is_random; ///< True if the AI in this slot was randomly chosen. std::unique_ptr to_load_data; ///< Data to load after the Script start. /** diff --git a/src/script/script_gui.cpp b/src/script/script_gui.cpp index 7712bad358..21bee4ac2b 100644 --- a/src/script/script_gui.cpp +++ b/src/script/script_gui.cpp @@ -307,13 +307,11 @@ struct ScriptSettingsWindow : public Window { clicked_dropdown(false), closing_dropdown(false) { - this->script_config = GetConfig(slot); - this->CreateNestedTree(); this->vscroll = this->GetScrollbar(WID_SCRS_SCROLLBAR); this->FinishInitNested(slot); // Initializes 'this->line_height' as side effect. - this->RebuildVisibleSettings(); + this->OnInvalidateData(); } /** @@ -591,6 +589,8 @@ struct ScriptSettingsWindow : public Window { */ void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override { + this->script_config = GetConfig(this->slot); + if (this->script_config->GetConfigList()->empty()) this->Close(); this->RebuildVisibleSettings(); this->CloseChildWindows(WC_DROPDOWN_MENU); this->CloseChildWindows(WC_QUERY_STRING); @@ -1206,7 +1206,8 @@ struct ScriptDebugWindow : public Window { this->SetWidgetLoweredState(WID_SCRD_BREAK_STR_ON_OFF_BTN, this->filter.break_check_enabled); this->SetWidgetLoweredState(WID_SCRD_MATCH_CASE_BTN, this->filter.case_sensitive_break_check); - this->SetWidgetDisabledState(WID_SCRD_SETTINGS, this->filter.script_debug_company == INVALID_COMPANY); + this->SetWidgetDisabledState(WID_SCRD_SETTINGS, this->filter.script_debug_company == INVALID_COMPANY || + GetConfig(this->filter.script_debug_company)->GetConfigList()->empty()); extern CompanyID _local_company; this->SetWidgetDisabledState(WID_SCRD_RELOAD_TOGGLE, this->filter.script_debug_company == INVALID_COMPANY ||