diff --git a/Mods/WoG/filesystem.json b/Mods/WoG/mod.json similarity index 87% rename from Mods/WoG/filesystem.json rename to Mods/WoG/mod.json index 29e42e6c0..678fa5b32 100644 --- a/Mods/WoG/filesystem.json +++ b/Mods/WoG/mod.json @@ -31,5 +31,9 @@ [ {"type" : "dir", "path" : "ALL/MODS/WOG/Maps"} ], - } + }, + + "name" : "In The Wake of Gods", + "description" : "Unnofficial addon for Heroes of Might and Magic III", + "priority" : 5 } diff --git a/Mods/vcmi/filesystem.json b/Mods/vcmi/mod.json similarity index 65% rename from Mods/vcmi/filesystem.json rename to Mods/vcmi/mod.json index 95b859294..5c21d9792 100644 --- a/Mods/vcmi/filesystem.json +++ b/Mods/vcmi/mod.json @@ -13,5 +13,9 @@ [ {"type" : "dir", "path" : "ALL/MODS/VCMI/Maps"} ] - } + }, + + "name" : "VCMI essential files", + "description" : "Essential files required for VCMI to run correctly", + "priority" : 10 } diff --git a/client/CMT.cpp b/client/CMT.cpp index f77ca930c..c67991618 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -130,7 +130,7 @@ void init() tlog0<<"\tInitializing sound: "<(CGI)->setFromLib(); CCS->soundh->initCreaturesSounds(CGI->creh->creatures); CCS->soundh->initSpellsSounds(CGI->spellh->spells); @@ -241,7 +241,7 @@ int main(int argc, char** argv) atexit(dispose); tlog0 <<"Creating console and logfile: "<modh = this; loadConfigFromFile ("defaultMods"); - findAvailableMods(); - //CResourceHandler::loadModsFilesystems(); //scan for all mods - //TODO: mod filesystem is already initialized at LibClasses launch - //TODO: load default (last?) config } void CModHandler::loadConfigFromFile (std::string name) @@ -94,51 +90,59 @@ void CModHandler::loadConfigFromFile (std::string name) modules.STACK_ARTIFACT = gameModules["STACK_ARTIFACTS"].Bool(); modules.COMMANDERS = gameModules["COMMANDERS"].Bool(); modules.MITHRIL = gameModules["MITHRIL"].Bool(); - - //TODO: load only mods from the list } -void CModHandler::saveConfigToFile (std::string name) +void CModHandler::initialize(std::vector availableMods) { - //JsonNode savedConf = config; - //JsonNode schema(ResourceID("config/defaultSettings.json")); - - //savedConf.Struct().erase("session"); - //savedConf.minimize(schema); - - CResourceHandler::get()->createResource("config/" + name +".json"); - - std::ofstream file(CResourceHandler::get()->getResourceName(ResourceID("config/" + name +".json")), std::ofstream::trunc); - //file << savedConf; -} - - -void CModHandler::findAvailableMods() -{ - //TODO: read mods from Mods/ folder - - auto & configList = CResourceHandler::get()->getResourcesWithName (ResourceID("CONFIG/mod.json")); - - BOOST_FOREACH(auto & entry, configList) + BOOST_FOREACH(std::string &name, availableMods) { - auto stream = entry.getLoader()->load (entry.getResourceName()); - std::unique_ptr textData (new ui8[stream->getSize()]); - stream->read (textData.get(), stream->getSize()); + std::string modFileName = "mods/" + name + "/mod.json"; - tlog3 << "\t\tFound mod file: " << entry.getResourceName() << "\n"; - allMods[allMods.size()].config.reset(new JsonNode((char*)textData.get(), stream->getSize())); + if (CResourceHandler::get()->existsResource(ResourceID(modFileName))) + { + const JsonNode config = JsonNode(ResourceID(modFileName)); + + if (!config.isNull()) + { + allMods[name].identifier = name; + allMods[name].name = config["name"].String(); + allMods[name].description = config["description"].String(); + allMods[name].loadPriority = config["priority"].Float(); + activeMods.push_back(name); + + tlog1 << "\t\tMod "; + tlog2 << allMods[name].name; + tlog1 << " enabled\n"; + } + } + else + tlog1 << "\t\t Directory " << name << " does not contains VCMI mod\n"; } + + std::sort(activeMods.begin(), activeMods.end(), [&](std::string a, std::string b) + { + return allMods[a].loadPriority < allMods[b].loadPriority; + }); +} + + +std::vector CModHandler::getActiveMods() +{ + return activeMods; } void CModHandler::loadActiveMods() { - BOOST_FOREACH(auto & mod, allMods) + BOOST_FOREACH(std::string & modName, activeMods) { - const JsonNode & config = *mod.second.config; + std::string modFileName = "mods/" + modName + "/mod.json"; - VLC->townh->load(config["factions"]); - VLC->creh->load(config["creatures"]); + const JsonNode config = JsonNode(ResourceID(modFileName)); + + VLC->townh->load(JsonUtils::assembleFromFiles(config ["factions"].convertTo >())); + VLC->creh->load( JsonUtils::assembleFromFiles(config["creatures"].convertTo >())); } + VLC->creh->buildBonusTreeForTiers(); //do that after all new creatures are loaded identifiers.finalize(); } diff --git a/lib/CModHandler.h b/lib/CModHandler.h index 90d262082..28495c646 100644 --- a/lib/CModHandler.h +++ b/lib/CModHandler.h @@ -40,39 +40,48 @@ public: void finalize() const; }; -typedef si32 TModID; +typedef std::string TModID; class DLL_LINKAGE CModInfo { public: - /// TODO: list of mods that should be loaded before this one - std::vector requirements; + /// identifier, identical to name of folder with mod + std::string identifier; - /// mod configuration (mod.json). - std::shared_ptr config; //TODO: unique_ptr can't be serialized + /// human-readable strings + std::string name; + std::string description; + + /// priority in which this mod should be loaded + /// may be somewhat ignored to load required mods first or overriden by user + double loadPriority; + + /// TODO: list of mods that should be loaded before this one + std::set requirements; + + // mod configuration (mod.json). (no need to store it right now) + // std::shared_ptr config; //TODO: unique_ptr can't be serialized template void serialize(Handler &h, const int version) { - h & requirements & config; + h & name & requirements; } }; class DLL_LINKAGE CModHandler { - //std::string currentConfig; //save settings in this file - std::map allMods; - std::set activeMods;//TODO: use me + std::vector activeMods;//active mods, in order in which they were loaded + void loadConfigFromFile (std::string name); public: CIdentifierStorage identifiers; - /// management of game settings config - void loadConfigFromFile (std::string name); - void saveConfigToFile (std::string name); + /// receives list of available mods and trying to load mod.json from all of them + void initialize(std::vector availableMods); - /// find all available mods and load them into FS - void findAvailableMods(); + /// returns list of mods that should be active with order in which they shoud be loaded + std::vector getActiveMods(); /// load content from all available mods void loadActiveMods(); diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index d0808648a..56f7109f3 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -502,7 +502,8 @@ void CTownHandler::load(const JsonNode &source) void CTownHandler::load() { - JsonNode buildingsConf(ResourceID("config/buildings.json")); + JsonNode gameConf(ResourceID("config/gameConfig.json")); + JsonNode buildingsConf = JsonUtils::assembleFromFiles(gameConf["factions"].convertTo >()); JsonNode legacyConfig; loadLegacyData(legacyConfig); diff --git a/lib/Filesystem/CResourceLoader.cpp b/lib/Filesystem/CResourceLoader.cpp index 108d06ee8..12f058bd9 100644 --- a/lib/Filesystem/CResourceLoader.cpp +++ b/lib/Filesystem/CResourceLoader.cpp @@ -330,8 +330,7 @@ void CResourceHandler::initialize() //create "LOCAL" dir with current userDir (may be same as rootDir) initialLoader->addLoader("LOCAL/", userDir, false); - //recurseInDir("ALL/CONFIG", 0);// look for configs - recurseInDir("ALL/CONFIG", 4);// look for mods (2) and mod files (3) in config folder + recurseInDir("ALL/CONFIG", 0);// look for configs recurseInDir("ALL/DATA", 0); // look for archives recurseInDir("ALL/MODS", 2); // look for mods. Depth 2 is required for now but won't cause issues if no mods present } @@ -374,7 +373,7 @@ void CResourceHandler::loadFileSystem(const std::string &fsConfigURI) BOOST_FOREACH(auto & entry, mountPoint.second.Vector()) { CStopWatch timer; - tlog5 << "\t\tLoading resource at " << entry["path"].String(); + tlog5 << "\t\tLoading resource at " << entry["path"].String() << "\n"; if (entry["type"].String() == "dir") loadDirectory(mountPoint.first, entry); @@ -385,35 +384,40 @@ void CResourceHandler::loadFileSystem(const std::string &fsConfigURI) if (entry["type"].String() == "vid") loadArchive(mountPoint.first, entry, EResType::ARCHIVE_VID); - tlog5 << " took " << timer.getDiff() << " ms.\n"; + tlog5 << "Resource loaded in " << timer.getDiff() << " ms.\n"; } } } -void CResourceHandler::loadModsFilesystems() +std::vector CResourceHandler::getAvailableMods() { auto iterator = initialLoader->getIterator([](const ResourceID & ident) -> bool { std::string name = ident.getName(); - return ident.getType() == EResType::TEXT - && std::count(name.begin(), name.end(), '/') == 3 - && boost::algorithm::starts_with(name, "ALL/MODS/") - && boost::algorithm::ends_with(name, "FILESYSTEM"); + return ident.getType() == EResType::DIRECTORY + && std::count(name.begin(), name.end(), '/') == 2 + && boost::algorithm::starts_with(name, "ALL/MODS/"); }); - //sorted storage for found mods - //implements basic load order (entries in hashtable are basically random) - std::set foundMods; + //storage for found mods + std::vector foundMods; while (iterator.hasNext()) { - foundMods.insert(iterator->getName()); + std::string name = iterator->getName(); + + name.erase(0, name.find_last_of('/') + 1); //Remove path prefix + + foundMods.push_back(name); ++iterator; } + return foundMods; +} - BOOST_FOREACH(const std::string & entry, foundMods) +void CResourceHandler::setActiveMods(std::vector enabledMods) +{ + BOOST_FOREACH(std::string & modName, enabledMods) { - tlog3 << "\t\tFound mod filesystem: " << entry << "\n"; - loadFileSystem(entry); + loadFileSystem("all/mods/" + modName + "/mod.json"); } } diff --git a/lib/Filesystem/CResourceLoader.h b/lib/Filesystem/CResourceLoader.h index cd430c606..bbd658147 100644 --- a/lib/Filesystem/CResourceLoader.h +++ b/lib/Filesystem/CResourceLoader.h @@ -380,10 +380,11 @@ public: static void loadArchive(const std::string & mountPoint, const JsonNode & config, EResType::Type archiveType); /** - * Experimental. Checks all subfolders of MODS directory for presence of ERA-style mods - * If this directory has filesystem.json file it will be added to resources + * Checks all subfolders of MODS directory for presence of mods + * If this directory has mod.json file it will be added to resources */ - static void loadModsFilesystems(); + static std::vector getAvailableMods(); + static void setActiveMods(std::vector enabledMods); //WARNING: not reentrable. Do not call it twice!!! private: /** Instance of resource loader */ diff --git a/lib/JsonNode.cpp b/lib/JsonNode.cpp index 69f53ff31..6fa04bf55 100644 --- a/lib/JsonNode.cpp +++ b/lib/JsonNode.cpp @@ -1023,6 +1023,12 @@ void JsonUtils::validate(JsonNode & node, const JsonNode& schema) void JsonUtils::merge(JsonNode & dest, JsonNode & source) { + if (dest.getType() == JsonNode::DATA_NULL) + { + std::swap(dest, source); + return; + } + switch (source.getType()) { break; case JsonNode::DATA_NULL: dest.setType(JsonNode::DATA_NULL); @@ -1031,11 +1037,19 @@ void JsonUtils::merge(JsonNode & dest, JsonNode & source) break; case JsonNode::DATA_STRING: std::swap(dest.String(), source.String()); break; case JsonNode::DATA_VECTOR: { - //reserve place and *move* data from source to dest - source.Vector().reserve(source.Vector().size() + dest.Vector().size()); + size_t total = std::min(source.Vector().size(), dest.Vector().size()); - std::move(source.Vector().begin(), source.Vector().end(), - std::back_inserter(dest.Vector())); + for (size_t i=0; i< total; i++) + merge(dest.Vector()[i], source.Vector()[i]); + + if (source.Vector().size() < dest.Vector().size()) + { + //reserve place and *move* data from source to dest + source.Vector().reserve(source.Vector().size() + dest.Vector().size()); + + std::move(source.Vector().begin(), source.Vector().end(), + std::back_inserter(dest.Vector())); + } } break; case JsonNode::DATA_STRUCT: { @@ -1050,4 +1064,16 @@ void JsonUtils::mergeCopy(JsonNode & dest, JsonNode source) { // uses copy created in stack to safely merge two nodes merge(dest, source); -} \ No newline at end of file +} + +JsonNode JsonUtils::assembleFromFiles(std::vector files) +{ + JsonNode result; + + BOOST_FOREACH(std::string file, files) + { + JsonNode section(ResourceID(file, EResType::TEXT)); + merge(result, section); + } + return result; +} diff --git a/lib/JsonNode.h b/lib/JsonNode.h index 90420fa4c..bc71d72b8 100644 --- a/lib/JsonNode.h +++ b/lib/JsonNode.h @@ -110,7 +110,7 @@ namespace JsonUtils /// recursivly merges source into dest, replacing identical fields /// struct : recursively calls this function - /// arrays : append array in dest with data from source + /// arrays : each entry will be merged recursively /// 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 @@ -119,6 +119,12 @@ namespace JsonUtils /// this function will preserve data stored in source by creating copy DLL_LINKAGE void mergeCopy(JsonNode & dest, JsonNode source); + /** + * @brief generate one Json structure from multiple files + * @param files - list of filenames with parts of json structure + */ + DLL_LINKAGE JsonNode assembleFromFiles(std::vector files); + /// removes all nodes that are identical to default entry in schema DLL_LINKAGE void minimize(JsonNode & node, const JsonNode& schema); @@ -155,7 +161,6 @@ namespace JsonDetail ret.insert(entry.first, entry.second.convertTo()); } return ret; - } }; diff --git a/lib/VCMI_Lib.cpp b/lib/VCMI_Lib.cpp index 674fcb431..df7e980ed 100644 --- a/lib/VCMI_Lib.cpp +++ b/lib/VCMI_Lib.cpp @@ -31,12 +31,20 @@ LibClasses * VLC = NULL; DLL_LINKAGE VCMIDirs GVCMIDirs; - -DLL_LINKAGE void initDLL(CConsoleHandler *Console, std::ostream *Logfile) +DLL_LINKAGE void preinitDLL(CConsoleHandler *Console, std::ostream *Logfile) { console = Console; logfile = Logfile; VLC = new LibClasses; + try + { + VLC->loadFilesystem(); + } + HANDLE_EXCEPTION; +} + +DLL_LINKAGE void loadDLLClasses() +{ try { VLC->init(); @@ -55,19 +63,20 @@ void LibClasses::loadFilesystem() CResourceHandler::loadFileSystem("ALL/config/filesystem.json"); tlog0<<"\t Data loading: "<initialize(CResourceHandler::getAvailableMods()); + CResourceHandler::setActiveMods(modh->getActiveMods()); tlog0<<"\t Mod filesystems: "<load(); tlog0<<"\tGeneral text handler: "< void serialize(Handler &h, const int version) @@ -57,4 +57,6 @@ public: extern DLL_LINKAGE LibClasses * VLC; -DLL_LINKAGE void initDLL(CConsoleHandler *Console, std::ostream *Logfile); +DLL_LINKAGE void preinitDLL(CConsoleHandler *Console, std::ostream *Logfile); +DLL_LINKAGE void loadDLLClasses(); + diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 7aa54fcd4..c8765f649 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -509,9 +509,9 @@ int main(int argc, char** argv) port = _ttoi(argv[1]); #endif } - LibClasses::loadFilesystem(); + preinitDLL(console,logfile); tlog0 << "Port " << port << " will be used." << std::endl; - initDLL(console,logfile); + loadDLLClasses(); srand ( (ui32)time(NULL) ); try {