diff --git a/config/defaultMods.json b/config/defaultMods.json index 6413b79af..e84c877f8 100644 --- a/config/defaultMods.json +++ b/config/defaultMods.json @@ -23,7 +23,8 @@ "ALL_CREATURES_GET_DOUBLE_MONTHS" : false, "NEGATIVE_LUCK" : false, "MAX_HEROES_AVAILABLE_PER_PLAYER" : 16, - "MAX_HEROES_ON_MAP_PER_PLAYER" : 8 + "MAX_HEROES_ON_MAP_PER_PLAYER" : 8, + "WINNING_HERO_WITH_NO_TROOPS_RETREATS": true }, "modules": diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index 571d5c10b..5d5095c5f 100644 --- a/lib/CModHandler.cpp +++ b/lib/CModHandler.cpp @@ -101,9 +101,9 @@ void CIdentifierStorage::requestIdentifier(std::string scope, std::string type, void CIdentifierStorage::requestIdentifier(std::string scope, std::string fullName, const std::function& callback) { - auto scopeAndFullName = splitString(fullName, ':'); - auto typeAndName = splitString(scopeAndFullName.second, '.'); - + auto scopeAndFullName = splitString(fullName, ':'); + auto typeAndName = splitString(scopeAndFullName.second, '.'); + requestIdentifier(ObjectCallback(scope, scopeAndFullName.first, typeAndName.first, typeAndName.second, callback, false)); } @@ -331,11 +331,11 @@ bool CContentHandler::ContentTypeHandler::loadMod(std::string modName, bool vali { ModInfo & modInfo = modData[modName]; bool result = true; - + auto performValidate = [&,this](JsonNode & data, const std::string & name){ handler->beforeValidate(data); if (validate) - result &= JsonUtils::validate(data, "vcmi:" + objectName, name); + result &= JsonUtils::validate(data, "vcmi:" + objectName, name); }; // apply patches @@ -355,7 +355,7 @@ bool CContentHandler::ContentTypeHandler::loadMod(std::string modName, bool vali if (originalData.size() > index) { JsonUtils::merge(originalData[index], data); - + performValidate(originalData[index],name); handler->loadObject(modName, name, originalData[index], index); @@ -550,21 +550,42 @@ CModHandler::CModHandler() void CModHandler::loadConfigFromFile (std::string name) { + std::string paths; + for(auto& p : CResourceHandler::get()->getResourceNames(ResourceID("config/" + name))) + { + paths += p + ", "; + } + paths = paths.substr(0, paths.size() - 2); + logGlobal->debugStream() << "Loading hardcoded features settings from [" << paths << "], result:"; settings.data = JsonUtils::assembleFromFiles("config/" + name); const JsonNode & hardcodedFeatures = settings.data["hardcodedFeatures"]; settings.MAX_HEROES_AVAILABLE_PER_PLAYER = hardcodedFeatures["MAX_HEROES_AVAILABLE_PER_PLAYER"].Float(); + logGlobal->debugStream() << "\tMAX_HEROES_AVAILABLE_PER_PLAYER\t" << settings.MAX_HEROES_AVAILABLE_PER_PLAYER; settings.MAX_HEROES_ON_MAP_PER_PLAYER = hardcodedFeatures["MAX_HEROES_ON_MAP_PER_PLAYER"].Float(); + logGlobal->debugStream() << "\tMAX_HEROES_ON_MAP_PER_PLAYER\t" << settings.MAX_HEROES_ON_MAP_PER_PLAYER; settings.CREEP_SIZE = hardcodedFeatures["CREEP_SIZE"].Float(); + logGlobal->debugStream() << "\tCREEP_SIZE\t" << settings.CREEP_SIZE; settings.WEEKLY_GROWTH = hardcodedFeatures["WEEKLY_GROWTH_PERCENT"].Float(); + logGlobal->debugStream() << "\tWEEKLY_GROWTH\t" << settings.WEEKLY_GROWTH; settings.NEUTRAL_STACK_EXP = hardcodedFeatures["NEUTRAL_STACK_EXP_DAILY"].Float(); + logGlobal->debugStream() << "\tNEUTRAL_STACK_EXP\t" << settings.NEUTRAL_STACK_EXP; settings.MAX_BUILDING_PER_TURN = hardcodedFeatures["MAX_BUILDING_PER_TURN"].Float(); + logGlobal->debugStream() << "\tMAX_BUILDING_PER_TURN\t" << settings.MAX_BUILDING_PER_TURN; settings.DWELLINGS_ACCUMULATE_CREATURES = hardcodedFeatures["DWELLINGS_ACCUMULATE_CREATURES"].Bool(); + logGlobal->debugStream() << "\tDWELLINGS_ACCUMULATE_CREATURES\t" << settings.DWELLINGS_ACCUMULATE_CREATURES; settings.ALL_CREATURES_GET_DOUBLE_MONTHS = hardcodedFeatures["ALL_CREATURES_GET_DOUBLE_MONTHS"].Bool(); + logGlobal->debugStream() << "\tALL_CREATURES_GET_DOUBLE_MONTHS\t" << settings.ALL_CREATURES_GET_DOUBLE_MONTHS; + settings.WINNING_HERO_WITH_NO_TROOPS_RETREATS = hardcodedFeatures["WINNING_HERO_WITH_NO_TROOPS_RETREATS"].Bool(); + logGlobal->debugStream() << "\tWINNING_HERO_WITH_NO_TROOPS_RETREATS\t" << settings.WINNING_HERO_WITH_NO_TROOPS_RETREATS; const JsonNode & gameModules = settings.data["modules"]; modules.STACK_EXP = gameModules["STACK_EXPERIENCE"].Bool(); + logGlobal->debugStream() << "\tSTACK_EXP\t" << modules.STACK_EXP; modules.STACK_ARTIFACT = gameModules["STACK_ARTIFACTS"].Bool(); + logGlobal->debugStream() << "\tSTACK_ARTIFACT\t" << modules.STACK_ARTIFACT; modules.COMMANDERS = gameModules["COMMANDERS"].Bool(); + logGlobal->debugStream() << "\tCOMMANDERS\t" << modules.COMMANDERS; modules.MITHRIL = gameModules["MITHRIL"].Bool(); + logGlobal->debugStream() << "\tMITHRIL\t" << modules.MITHRIL; } // currentList is passed by value to get current list of depending mods diff --git a/lib/CModHandler.h b/lib/CModHandler.h index 7d4f00fe5..b74918ca7 100644 --- a/lib/CModHandler.h +++ b/lib/CModHandler.h @@ -73,7 +73,7 @@ public: /// Function callback will be called during ID resolution phase of loading void requestIdentifier(std::string scope, std::string type, std::string name, const std::function & callback); ///fullName = [remoteScope:]type.name - void requestIdentifier(std::string scope, std::string fullName, const std::function & callback); + void requestIdentifier(std::string scope, std::string fullName, const std::function & callback); void requestIdentifier(std::string type, const JsonNode & name, const std::function & callback); void requestIdentifier(const JsonNode & name, const std::function & callback); @@ -253,17 +253,27 @@ public: int CREEP_SIZE; // neutral stacks won't grow beyond this number int WEEKLY_GROWTH; //percent - int NEUTRAL_STACK_EXP; + int NEUTRAL_STACK_EXP; int MAX_BUILDING_PER_TURN; bool DWELLINGS_ACCUMULATE_CREATURES; bool ALL_CREATURES_GET_DOUBLE_MONTHS; int MAX_HEROES_AVAILABLE_PER_PLAYER; int MAX_HEROES_ON_MAP_PER_PLAYER; + bool WINNING_HERO_WITH_NO_TROOPS_RETREATS; template void serialize(Handler &h, const int version) { h & data & CREEP_SIZE & WEEKLY_GROWTH & NEUTRAL_STACK_EXP & MAX_BUILDING_PER_TURN; - h & DWELLINGS_ACCUMULATE_CREATURES & ALL_CREATURES_GET_DOUBLE_MONTHS & MAX_HEROES_AVAILABLE_PER_PLAYER & MAX_HEROES_ON_MAP_PER_PLAYER; + h & DWELLINGS_ACCUMULATE_CREATURES & ALL_CREATURES_GET_DOUBLE_MONTHS & + MAX_HEROES_AVAILABLE_PER_PLAYER & MAX_HEROES_ON_MAP_PER_PLAYER; + if(version >= 756) + { + h & WINNING_HERO_WITH_NO_TROOPS_RETREATS; + } + else if(!h.saving) + { + WINNING_HERO_WITH_NO_TROOPS_RETREATS = true; + } } } settings; diff --git a/lib/Connection.h b/lib/Connection.h index 259b9f23f..00a16afac 100644 --- a/lib/Connection.h +++ b/lib/Connection.h @@ -27,7 +27,7 @@ #include "mapping/CCampaignHandler.h" //for CCampaignState #include "rmg/CMapGenerator.h" // for CMapGenOptions -const ui32 version = 755; +const ui32 version = 756; const ui32 minSupportedVersion = 753; class CISer; diff --git a/lib/JsonNode.cpp b/lib/JsonNode.cpp index c9ea181b2..8128efcbc 100644 --- a/lib/JsonNode.cpp +++ b/lib/JsonNode.cpp @@ -56,6 +56,15 @@ JsonNode::JsonNode(ResourceID && fileURI): *this = parser.parse(fileURI.getName()); } +JsonNode::JsonNode(const ResourceID & fileURI): + type(DATA_NULL) +{ + auto file = CResourceHandler::get()->load(fileURI)->readAll(); + + JsonParser parser(reinterpret_cast(file.first.get()), file.second); + *this = parser.parse(fileURI.getName()); +} + JsonNode::JsonNode(ResourceID && fileURI, bool &isValidSyntax): type(DATA_NULL) { @@ -328,7 +337,7 @@ void JsonUtils::parseTypedBonusShort(const JsonVector& source, Bonus *dest) resolveIdentifier(source[2],dest->subtype); dest->additionalInfo = source[3].Float(); dest->duration = Bonus::PERMANENT; //TODO: handle flags (as integer) - dest->turnsRemain = 0; + dest->turnsRemain = 0; } @@ -343,7 +352,7 @@ Bonus * JsonUtils::parseBonus (const JsonVector &ability_vec) //TODO: merge with return b; } b->type = it->second; - + parseTypedBonusShort(ability_vec, b); return b; } diff --git a/lib/JsonNode.h b/lib/JsonNode.h index dfe9aba0b..b926a4d6d 100644 --- a/lib/JsonNode.h +++ b/lib/JsonNode.h @@ -7,7 +7,7 @@ * Full text of license available in license.txt file, in main folder * */ - + #pragma once class JsonNode; @@ -55,6 +55,7 @@ public: explicit JsonNode(const char * data, size_t datasize); //Create tree from JSON file explicit JsonNode(ResourceID && fileURI); + explicit JsonNode(const ResourceID & fileURI); explicit JsonNode(ResourceID && fileURI, bool & isValidSyntax); //Copy c-tor JsonNode(const JsonNode ©); @@ -125,9 +126,9 @@ namespace JsonUtils /** * @brief parse short bonus format, excluding type * @note sets duration to Permament - */ + */ DLL_LINKAGE void parseTypedBonusShort(const JsonVector &source, Bonus *dest); - + /// DLL_LINKAGE Bonus * parseBonus (const JsonVector &ability_vec); DLL_LINKAGE Bonus * parseBonus (const JsonNode &bonus); @@ -144,7 +145,7 @@ namespace JsonUtils * @note this function will destroy data in source */ DLL_LINKAGE void merge(JsonNode & dest, JsonNode & source); - + /** * @brief recursively merges source into dest, replacing identical fields * struct : recursively calls this function @@ -152,12 +153,12 @@ namespace JsonUtils * values : value in source will replace value in dest * null : if value in source is present but set to null it will delete entry in dest * @note this function will preserve data stored in source by creating copy - */ + */ DLL_LINKAGE void mergeCopy(JsonNode & dest, JsonNode source); - + /** @brief recursively merges descendant into copy of base node * Result emulates inheritance semantic - * + * * */ DLL_LINKAGE void inherit(JsonNode & descendant, const JsonNode & base); @@ -200,7 +201,7 @@ namespace JsonDetail { // conversion helpers for JsonNode::convertTo (partial template function instantiation is illegal in c++) - template + template struct JsonConvImpl; template @@ -229,7 +230,7 @@ namespace JsonDetail ///this should be triggered only for numeric types and enums static_assert(boost::mpl::or_, std::is_enum, boost::is_class >::value, "Unsupported type for JsonNode::convertTo()!"); return JsonConvImpl, boost::is_class >::value >::convertImpl(node); - + } }; diff --git a/lib/filesystem/AdapterLoaders.cpp b/lib/filesystem/AdapterLoaders.cpp index cb2ceaf23..8c20743e5 100644 --- a/lib/filesystem/AdapterLoaders.cpp +++ b/lib/filesystem/AdapterLoaders.cpp @@ -87,6 +87,20 @@ boost::optional CFilesystemList::getResourceName(const ResourceID & return boost::optional(); } +std::set CFilesystemList::getResourceNames(const ResourceID & resourceName) const +{ + std::set paths; + for(auto& loader : getResourcesWithName(resourceName)) + { + auto rn = loader->getResourceName(resourceName); + if(rn) + { + paths.insert(*rn); + } + } + return std::move(paths); +} + std::unordered_set CFilesystemList::getFilteredFiles(std::function filter) const { std::unordered_set ret; diff --git a/lib/filesystem/AdapterLoaders.h b/lib/filesystem/AdapterLoaders.h index cabe5760e..2abe5fcb9 100644 --- a/lib/filesystem/AdapterLoaders.h +++ b/lib/filesystem/AdapterLoaders.h @@ -59,16 +59,9 @@ class DLL_LINKAGE CFilesystemList : public ISimpleResourceLoader std::set writeableLoaders; //FIXME: this is only compile fix, should be removed in the end - CFilesystemList(CFilesystemList &) - { - //class is not copyable - } - CFilesystemList &operator=(CFilesystemList &) - { - //class is not copyable - return *this; - } - + CFilesystemList(CFilesystemList &) = delete; + CFilesystemList &operator=(CFilesystemList &) = delete; + public: CFilesystemList(); ~CFilesystemList(); @@ -78,6 +71,7 @@ public: bool existsResource(const ResourceID & resourceName) const override; std::string getMountPoint() const override; boost::optional getResourceName(const ResourceID & resourceName) const override; + std::set getResourceNames(const ResourceID & resourceName) const override; std::unordered_set getFilteredFiles(std::function filter) const override; bool createResource(std::string filename, bool update = false) override; std::vector getResourcesWithName(const ResourceID & resourceName) const override; diff --git a/lib/filesystem/ISimpleResourceLoader.h b/lib/filesystem/ISimpleResourceLoader.h index f04762210..f3d0be633 100644 --- a/lib/filesystem/ISimpleResourceLoader.h +++ b/lib/filesystem/ISimpleResourceLoader.h @@ -53,6 +53,22 @@ public: return boost::optional(); } + /** + * Gets all full names of matching resources, e.g. names of files in filesystem. + * + * @return std::set with names. + */ + virtual std::set getResourceNames(const ResourceID & resourceName) const + { + std::set result; + auto rn = getResourceName(resourceName); + if(rn) + { + result.insert(*rn); + } + return result; + } + /** * Get list of files that matches filter function * diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index e2e56e113..368201a81 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -757,18 +757,21 @@ void CGameHandler::battleAfterLevelUp( const BattleResult &result ) RemoveObject ro(finishingBattle->winnerHero->id); sendAndApply(&ro); - SetAvailableHeroes sah; - sah.player = finishingBattle->victor; - sah.hid[0] = finishingBattle->winnerHero->subID; - sah.army[0].clear(); - sah.army[0].setCreature(SlotID(0), finishingBattle->winnerHero->type->initialArmy.at(0).creature, 1); + if (VLC->modh->settings.WINNING_HERO_WITH_NO_TROOPS_RETREATS) + { + SetAvailableHeroes sah; + sah.player = finishingBattle->victor; + sah.hid[0] = finishingBattle->winnerHero->subID; + sah.army[0].clear(); + sah.army[0].setCreature(SlotID(0), finishingBattle->winnerHero->type->initialArmy.at(0).creature, 1); - if(const CGHeroInstance *another = getPlayer(finishingBattle->victor)->availableHeroes.at(0)) - sah.hid[1] = another->subID; - else - sah.hid[1] = -1; + if(const CGHeroInstance *another = getPlayer(finishingBattle->victor)->availableHeroes.at(0)) + sah.hid[1] = another->subID; + else + sah.hid[1] = -1; - sendAndApply(&sah); + sendAndApply(&sah); + } } }