diff --git a/CHANGELOG.md b/CHANGELOG.md index d58d6280d3..c3c5fe5578 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Bug #2311: Targeted scripts are not properly supported on non-unique RefIDs Bug #2473: Unable to overstock merchants Bug #2798: Mutable ESM records + Bug #2976 [reopened]: Issues combining settings from the command line and both config files Bug #3676: NiParticleColorModifier isn't applied properly Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects Bug #3862: Random container contents behave differently than vanilla diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 0c3d006c4f..0dce198f8a 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -89,10 +89,10 @@ std::pair > CS::Editor::readConfi desc.add_options() ("data", boost::program_options::value()->default_value(Files::EscapePathContainer(), "data")->multitoken()->composing()) - ("data-local", boost::program_options::value()->default_value("")) + ("data-local", boost::program_options::value()->default_value(Files::EscapePath(), "")) ("fs-strict", boost::program_options::value()->implicit_value(true)->default_value(false)) ("encoding", boost::program_options::value()->default_value("win1252")) - ("resources", boost::program_options::value()->default_value("resources")) + ("resources", boost::program_options::value()->default_value(Files::EscapePath(), "resources")) ("fallback-archive", boost::program_options::value()-> default_value(Files::EscapeStringVector(), "fallback-archive")->multitoken()) ("fallback", boost::program_options::value()->default_value(FallbackMap(), "") @@ -112,7 +112,7 @@ std::pair > CS::Editor::readConfi mDocumentManager.setEncoding(ToUTF8::calculateEncoding(mEncodingName)); mFileDialog.setEncoding (QString::fromUtf8(mEncodingName.c_str())); - mDocumentManager.setResourceDir (mResources = variables["resources"].as().toStdString()); + mDocumentManager.setResourceDir (mResources = variables["resources"].as().mPath); if (variables["script-blacklist-use"].as()) mDocumentManager.setBlacklistedScripts ( @@ -125,14 +125,9 @@ std::pair > CS::Editor::readConfi dataDirs = Files::PathContainer(Files::EscapePath::toPathContainer(variables["data"].as())); } - std::string local = variables["data-local"].as().toStdString(); + Files::PathContainer::value_type local(variables["data-local"].as().mPath); if (!local.empty()) - { - if (local.front() == '\"') - local = local.substr(1, local.length() - 2); - - dataLocal.push_back(Files::PathContainer::value_type(local)); - } + dataLocal.push_back(local); mCfgMgr.processPaths (dataDirs); mCfgMgr.processPaths (dataLocal, true); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index a39dd2e398..adf4bf18c2 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -63,20 +63,20 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("data", bpo::value()->default_value(Files::EscapePathContainer(), "data") ->multitoken()->composing(), "set data directories (later directories have higher priority)") - ("data-local", bpo::value()->default_value(""), + ("data-local", bpo::value()->default_value(Files::EscapePath(), ""), "set local data directory (highest priority)") ("fallback-archive", bpo::value()->default_value(Files::EscapeStringVector(), "fallback-archive") - ->multitoken(), "set fallback BSA archives (later archives have higher priority)") + ->multitoken()->composing(), "set fallback BSA archives (later archives have higher priority)") - ("resources", bpo::value()->default_value("resources"), + ("resources", bpo::value()->default_value(Files::EscapePath(), "resources"), "set resources directory") - ("start", bpo::value()->default_value(""), + ("start", bpo::value()->default_value(""), "set initial cell") ("content", bpo::value()->default_value(Files::EscapeStringVector(), "") - ->multitoken(), "content file(s): esm/esp, or omwgame/omwaddon") + ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon") ("no-sound", bpo::value()->implicit_value(true) ->default_value(false), "disable all sounds") @@ -90,7 +90,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("script-console", bpo::value()->implicit_value(true) ->default_value(false), "enable console-only script functionality") - ("script-run", bpo::value()->default_value(""), + ("script-run", bpo::value()->default_value(""), "select a file containing a list of console commands that is executed on startup") ("script-warn", bpo::value()->implicit_value (1) @@ -101,12 +101,12 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat "\t2 - treat warnings as errors") ("script-blacklist", bpo::value()->default_value(Files::EscapeStringVector(), "") - ->multitoken(), "ignore the specified script (if the use of the blacklist is enabled)") + ->multitoken()->composing(), "ignore the specified script (if the use of the blacklist is enabled)") ("script-blacklist-use", bpo::value()->implicit_value(true) ->default_value(true), "enable script blacklisting") - ("load-savegame", bpo::value()->default_value(""), + ("load-savegame", bpo::value()->default_value(Files::EscapePath(), ""), "load a save game file on game startup (specify an absolute filename or a filename relative to the current working directory)") ("skip-menu", bpo::value()->implicit_value(true) @@ -118,14 +118,14 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("fs-strict", bpo::value()->implicit_value(true) ->default_value(false), "strict file system handling (no case folding)") - ("encoding", bpo::value()-> + ("encoding", bpo::value()-> default_value("win1252"), "Character encoding used in OpenMW game messages:\n" "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n" "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" "\n\twin1252 - Western European (Latin) alphabet, used by default") - ("fallback", bpo::value()->default_value(FallbackMap(), "") + ("fallback", bpo::value()->default_value(FallbackMap(), "") ->multitoken()->composing(), "fallback values") ("no-grab", bpo::value()->implicit_value(true)->default_value(false), "Don't grab mouse cursor") @@ -159,14 +159,16 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat { cfgMgr.readConfiguration(variables, desc, true); - Version::Version v = Version::getOpenmwVersion(variables["resources"].as().toStdString()); + Version::Version v = Version::getOpenmwVersion(variables["resources"].as().mPath.string()); std::cout << v.describe() << std::endl; return false; } + bpo::variables_map composingVariables = cfgMgr.separateComposingVariables(variables, desc); cfgMgr.readConfiguration(variables, desc); + cfgMgr.mergeComposingVariables(variables, composingVariables, desc); - Version::Version v = Version::getOpenmwVersion(variables["resources"].as().toStdString()); + Version::Version v = Version::getOpenmwVersion(variables["resources"].as().mPath.string()); std::cout << v.describe() << std::endl; engine.setGrabMouse(!variables["no-grab"].as()); @@ -181,18 +183,13 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat Files::PathContainer dataDirs(Files::EscapePath::toPathContainer(variables["data"].as())); - std::string local(variables["data-local"].as().toStdString()); + Files::PathContainer::value_type local(variables["data-local"].as().mPath); if (!local.empty()) - { - if (local.front() == '\"') - local = local.substr(1, local.length() - 2); - - dataDirs.push_back(Files::PathContainer::value_type(local)); - } + dataDirs.push_back(local); cfgMgr.processPaths(dataDirs); - engine.setResourceDir(variables["resources"].as().toStdString()); + engine.setResourceDir(variables["resources"].as().mPath); engine.setDataDirs(dataDirs); // fallback archives @@ -230,7 +227,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat engine.setWarningsMode (variables["script-warn"].as()); engine.setScriptBlacklist (variables["script-blacklist"].as().toStdStringVector()); engine.setScriptBlacklistUse (variables["script-blacklist-use"].as()); - engine.setSaveGameFile (variables["load-savegame"].as().toStdString()); + engine.setSaveGameFile (variables["load-savegame"].as().mPath.string()); // other settings Fallback::Map::init(variables["fallback"].as().mMap); diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index b771b7fc8e..223045ab5f 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -118,12 +118,19 @@ bool Config::GameSettings::readFile(QTextStream &stream, QMultiMap const& tokens, FallbackMap*, int) +{ + if (v.empty()) + { + v = boost::any(FallbackMap()); + } + + FallbackMap *map = boost::any_cast(&v); + + for (const auto& token : tokens) + { + std::string temp = Files::EscapeHashString::processString(token); + size_t sep = temp.find(","); + if (sep < 1 || sep == temp.length() - 1 || sep == std::string::npos) +#if (BOOST_VERSION < 104200) + throw boost::program_options::validation_error("invalid value"); +#else + throw boost::program_options::validation_error(boost::program_options::validation_error::invalid_option_value); +#endif + + std::string key(temp.substr(0, sep)); + std::string value(temp.substr(sep + 1)); + + map->mMap[key] = value; + } +} diff --git a/components/fallback/validate.hpp b/components/fallback/validate.hpp index cef52e4624..96690f50a5 100644 --- a/components/fallback/validate.hpp +++ b/components/fallback/validate.hpp @@ -16,32 +16,7 @@ namespace Fallback std::map mMap; }; - void validate(boost::any &v, std::vector const &tokens, FallbackMap*, int) - { - if (v.empty()) - { - v = boost::any(FallbackMap()); - } - - FallbackMap *map = boost::any_cast(&v); - - for (std::vector::const_iterator it = tokens.begin(); it != tokens.end(); ++it) - { - std::string temp = Files::EscapeHashString::processString(*it); - int sep = temp.find(","); - if (sep < 1 || sep == (int)temp.length() - 1) -#if (BOOST_VERSION < 104200) - throw boost::program_options::validation_error("invalid value"); -#else - throw boost::program_options::validation_error(boost::program_options::validation_error::invalid_option_value); -#endif - - std::string key(temp.substr(0, sep)); - std::string value(temp.substr(sep + 1)); - - map->mMap[key] = value; - } - } + void validate(boost::any &v, std::vector const &tokens, FallbackMap*, int); } #endif diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 0ba2d15193..c7847b1227 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -2,6 +2,7 @@ #include #include +#include #include /** @@ -58,23 +59,87 @@ void ConfigurationManager::readConfiguration(boost::program_options::variables_m { bool silent = mSilent; mSilent = quiet; + + // User config has the highest priority. + auto composingVariables = separateComposingVariables(variables, description); + loadConfig(mFixedPath.getUserConfigPath(), variables, description); + mergeComposingVariables(variables, composingVariables, description); + boost::program_options::notify(variables); // read either local or global config depending on type of installation + composingVariables = separateComposingVariables(variables, description); bool loaded = loadConfig(mFixedPath.getLocalPath(), variables, description); + mergeComposingVariables(variables, composingVariables, description); boost::program_options::notify(variables); if (!loaded) { + composingVariables = separateComposingVariables(variables, description); loadConfig(mFixedPath.getGlobalConfigPath(), variables, description); + mergeComposingVariables(variables, composingVariables, description); boost::program_options::notify(variables); } - // User config has the highest priority. - loadConfig(mFixedPath.getUserConfigPath(), variables, description); - boost::program_options::notify(variables); - mSilent = silent; } +boost::program_options::variables_map ConfigurationManager::separateComposingVariables(boost::program_options::variables_map & variables, boost::program_options::options_description& description) +{ + boost::program_options::variables_map composingVariables; + for (auto itr = variables.begin(); itr != variables.end();) + { + if (description.find((*itr).first, false).semantic()->is_composing()) + { + composingVariables.emplace(*itr); + itr = variables.erase(itr); + } + else + ++itr; + } + return composingVariables; +} + +void ConfigurationManager::mergeComposingVariables(boost::program_options::variables_map & first, boost::program_options::variables_map & second, boost::program_options::options_description& description) +{ + for (auto& [name, variableValue] : first) + { + if (description.find(name, false).semantic()->is_composing()) + { + if (second[name].defaulted() || second[name].empty()) + continue; + + boost::any& firstValue = variableValue.value(); + const boost::any& secondValue = second[name].value(); + + if (firstValue.type() == typeid(Files::EscapePathContainer)) + { + auto& firstPathContainer = boost::any_cast(firstValue); + const auto& secondPathContainer = boost::any_cast(secondValue); + + firstPathContainer.insert(firstPathContainer.end(), secondPathContainer.begin(), secondPathContainer.end()); + } + else if (firstValue.type() == typeid(Files::EscapeStringVector)) + { + auto& firstVector = boost::any_cast(firstValue); + const auto& secondVector = boost::any_cast(secondValue); + + firstVector.mVector.insert(firstVector.mVector.end(), secondVector.mVector.begin(), secondVector.mVector.end()); + } + else if (firstValue.type() == typeid(Fallback::FallbackMap)) + { + auto& firstMap = boost::any_cast(firstValue); + const auto& secondMap = boost::any_cast(secondValue); + + std::map tempMap(secondMap.mMap); + tempMap.merge(firstMap.mMap); + firstMap.mMap.swap(tempMap); + } + else + Log(Debug::Error) << "Unexpected composing variable type. Curse boost and their blasted arcane templates."; + } + } + +} + void ConfigurationManager::processPaths(Files::PathContainer& dataDirs, bool create) { std::string path; diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 446abd4dc5..0ec0a1f67d 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -25,6 +25,10 @@ struct ConfigurationManager void readConfiguration(boost::program_options::variables_map& variables, boost::program_options::options_description& description, bool quiet=false); + boost::program_options::variables_map separateComposingVariables(boost::program_options::variables_map& variables, boost::program_options::options_description& description); + + void mergeComposingVariables(boost::program_options::variables_map& first, boost::program_options::variables_map& second, boost::program_options::options_description& description); + void processPaths(Files::PathContainer& dataDirs, bool create = false); ///< \param create Try creating the directory, if it does not exist.