From 8273f323b1e84a6c722316f699703c653eaf0851 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 25 Apr 2013 14:03:35 +0000 Subject: [PATCH] - it is possible to edit data of another mod or H3 data via mods - mods can access only ID's from dependenies, virtual "core" mod and itself (optional for some mods compatibility) - metadata field for JsonNode, used to track source mod - moved wog creatures into wog mod - (linux) convertMP3 option for vcmibuilder for systems where SDL_Mixer can't play mp3's --- .../WoG/config/wog/creatures.json | 0 Mods/WoG/config/wog/factions.json | 38 ++++ Mods/WoG/mod.json | 16 +- client/GUIClasses.cpp | 6 + config/factions/castle.json | 2 +- config/factions/conflux.json | 2 +- config/factions/dungeon.json | 2 +- config/factions/fortress.json | 2 +- config/factions/inferno.json | 2 +- config/factions/necropolis.json | 2 +- config/factions/rampart.json | 2 +- config/factions/stronghold.json | 2 +- config/factions/tower.json | 2 +- config/gameConfig.json | 3 +- lib/CArtHandler.cpp | 2 +- lib/CCreatureHandler.cpp | 9 +- lib/CHeroHandler.cpp | 12 +- lib/CModHandler.cpp | 169 +++++++++++++----- lib/CModHandler.h | 31 +++- lib/CObjectHandler.cpp | 2 - lib/CTownHandler.cpp | 12 +- lib/JsonNode.cpp | 63 +++++-- lib/JsonNode.h | 6 + vcmibuilder | 32 +++- 24 files changed, 321 insertions(+), 98 deletions(-) rename config/creatures/wog.json => Mods/WoG/config/wog/creatures.json (100%) create mode 100644 Mods/WoG/config/wog/factions.json diff --git a/config/creatures/wog.json b/Mods/WoG/config/wog/creatures.json similarity index 100% rename from config/creatures/wog.json rename to Mods/WoG/config/wog/creatures.json diff --git a/Mods/WoG/config/wog/factions.json b/Mods/WoG/config/wog/factions.json new file mode 100644 index 000000000..b2f1a6676 --- /dev/null +++ b/Mods/WoG/config/wog/factions.json @@ -0,0 +1,38 @@ +{ + "core:castle" : + { + "commander" : "paladin1" + }, + "core:conflux" : + { + "commander" : "astralSpirit1" + }, + "core:dungeon" : + { + "commander" : "brute1" + }, + "core:fortress" : + { + "commander" : "shaman1" + }, + "core:inferno" : + { + "commander" : "succubus1" + }, + "core:necropolis" : + { + "commander" : "soulEater1" + }, + "core:rampart" : + { + "commander" : "hierophant1" + }, + "core:stronghold" : + { + "commander" : "ogreLeader1" + }, + "core:tower" : + { + "commander" : "templeGuardian1" + } +} diff --git a/Mods/WoG/mod.json b/Mods/WoG/mod.json index a2959465b..d7d33c647 100644 --- a/Mods/WoG/mod.json +++ b/Mods/WoG/mod.json @@ -1,6 +1,10 @@ { "filesystem": { + "CONFIG/" : + [ + { "type" : "dir", "path" : "/Config"} + ], "DATA/" : [ {"type" : "lod", "path" : "/Data/hmm35wog.pac"}, @@ -34,5 +38,15 @@ }, "name" : "In The Wake of Gods", - "description" : "Unnofficial addon for Heroes of Might and Magic III" + "description" : "Unnofficial addon for Heroes of Might and Magic III", + + "creatures" : + [ + "config/wog/creatures.json" + ], + + "factions" : + [ + "config/wog/factions.json" + ] } diff --git a/client/GUIClasses.cpp b/client/GUIClasses.cpp index 9348edb0b..9139bcd4c 100644 --- a/client/GUIClasses.cpp +++ b/client/GUIClasses.cpp @@ -4417,6 +4417,12 @@ void CArtPlace::setArtifact(const CArtifactInstance *art) bonusValue = 0; } } + else + { + baseType = CComponent::artifact; + type = art->artType->iconIndex; + bonusValue = 0; + } if (locked) // Locks should appear as empty. hoverText = CGI->generaltexth->allTexts[507]; diff --git a/config/factions/castle.json b/config/factions/castle.json index c0345521c..5aaaff9ec 100644 --- a/config/factions/castle.json +++ b/config/factions/castle.json @@ -4,7 +4,7 @@ "index" : 0, "nativeTerrain": "grass", "alignment" : "good", - "commander" : "paladin1", + "commander" : "zealot", "creatureBackground" : { "120px" : "TPCASCAS", diff --git a/config/factions/conflux.json b/config/factions/conflux.json index 5115a89d7..bd645afd6 100644 --- a/config/factions/conflux.json +++ b/config/factions/conflux.json @@ -4,7 +4,7 @@ "index" : 8, "nativeTerrain": "grass", "alignment" : "neutral", - "commander" : "astralSpirit1", + "commander" : "iceElemental", "creatureBackground" : { "120px" : "TPCASELE", diff --git a/config/factions/dungeon.json b/config/factions/dungeon.json index 998924d5c..61b5e5374 100644 --- a/config/factions/dungeon.json +++ b/config/factions/dungeon.json @@ -4,7 +4,7 @@ "index" : 5, "nativeTerrain": "subterra", "alignment" : "evil", - "commander" : "brute1", + "commander" : "medusaQueen", "creatureBackground" : { "120px" : "TPCASDUN", diff --git a/config/factions/fortress.json b/config/factions/fortress.json index 87a4050bd..214f09592 100644 --- a/config/factions/fortress.json +++ b/config/factions/fortress.json @@ -4,7 +4,7 @@ "index" : 7, "nativeTerrain": "swamp", "alignment" : "neutral", - "commander" : "shaman1", + "commander" : "lizardWarrior", "creatureBackground" : { "120px" : "TPCASFOR", diff --git a/config/factions/inferno.json b/config/factions/inferno.json index fc5db5c8a..afdf05c52 100644 --- a/config/factions/inferno.json +++ b/config/factions/inferno.json @@ -4,7 +4,7 @@ "index" : 3, "nativeTerrain": "lava", "alignment" : "evil", - "commander" : "succubus1", + "commander" : "magog", "creatureBackground" : { "120px" : "TPCASINF", diff --git a/config/factions/necropolis.json b/config/factions/necropolis.json index 298ab6663..0b39e1f6a 100644 --- a/config/factions/necropolis.json +++ b/config/factions/necropolis.json @@ -4,7 +4,7 @@ "index" : 4, "nativeTerrain": "dirt", "alignment" : "evil", - "commander" : "soulEater1", + "commander" : "powerLich", "creatureBackground" : { "120px" : "TPCASNEC", diff --git a/config/factions/rampart.json b/config/factions/rampart.json index 4fe90994c..9a56cea6c 100644 --- a/config/factions/rampart.json +++ b/config/factions/rampart.json @@ -4,7 +4,7 @@ "index" : 1, "nativeTerrain": "grass", "alignment" : "good", - "commander" : "hierophant1", + "commander" : "grandElf", "creatureBackground" : { "120px" : "TPCASRAM", diff --git a/config/factions/stronghold.json b/config/factions/stronghold.json index 0a0ff298b..2d91ae010 100644 --- a/config/factions/stronghold.json +++ b/config/factions/stronghold.json @@ -4,7 +4,7 @@ "index" : 6, "nativeTerrain": "rough", "alignment" : "neutral", - "commander" : "ogreLeader1", + "commander" : "orcChieftain", "creatureBackground" : { "120px" : "TPCASSTR", diff --git a/config/factions/tower.json b/config/factions/tower.json index f4503331d..029789ec5 100644 --- a/config/factions/tower.json +++ b/config/factions/tower.json @@ -4,7 +4,7 @@ "index" : 2, "nativeTerrain" : "snow", "alignment" : "good", - "commander" : "templeGuardian1", + "commander" : "archMage", "creatureBackground" : { "120px" : "TPCASTOW", diff --git a/config/gameConfig.json b/config/gameConfig.json index 94c477969..44df4b6b9 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -28,8 +28,7 @@ "config/creatures/conflux.json", "config/creatures/neutral.json", - "config/creatures/special.json", - "config/creatures/wog.json" + "config/creatures/special.json" ], "heroes" : diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index 8a3f22975..a51291900 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -366,7 +366,7 @@ void CArtHandler::loadComponents(CArtifact * art, const JsonNode & node) art->constituents.reset(new std::vector()); BOOST_FOREACH (auto component, node["components"].Vector()) { - VLC->modh->identifiers.requestIdentifier("artifact." + component.String(), [=](si32 id) + VLC->modh->identifiers.requestIdentifier("artifact", component, [=](si32 id) { // when this code is called both combinational art as well as component are loaded // so it is safe to access any of them diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index e45807e11..c31fedb11 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -179,7 +179,10 @@ CCreatureHandler::CCreatureHandler() void CCreatureHandler::loadCommanders() { - const JsonNode config(ResourceID("config/commanders.json")); + JsonNode data(ResourceID("config/commanders.json")); + data.setMeta("core"); // assume that commanders are in core mod (for proper bonuses resolution) + + const JsonNode & config = data; // switch to const data accessors BOOST_FOREACH (auto bonus, config["bonusPerLevel"].Vector()) { @@ -627,14 +630,14 @@ void CCreatureHandler::loadCreatureJson(CCreature * creature, const JsonNode & c } } - VLC->modh->identifiers.requestIdentifier(std::string("faction.") + config["faction"].String(), [=](si32 faction) + VLC->modh->identifiers.requestIdentifier("faction", config["faction"], [=](si32 faction) { creature->faction = faction; }); BOOST_FOREACH(const JsonNode &value, config["upgrades"].Vector()) { - VLC->modh->identifiers.requestIdentifier(std::string("creature.") + value.String(), [=](si32 identifier) + VLC->modh->identifiers.requestIdentifier("creature", value, [=](si32 identifier) { creature->upgrades.insert(CreatureID(identifier)); }); diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index 95ad70b29..566700f6f 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -105,14 +105,14 @@ CHeroClass *CHeroClassHandler::loadFromJson(const JsonNode & node) { int value = tavern.second.Float(); - VLC->modh->identifiers.requestIdentifier("faction." + tavern.first, + VLC->modh->identifiers.requestIdentifier(tavern.second.meta, "faction", tavern.first, [=](si32 factionID) { heroClass->selectionProbability[factionID] = value; }); } - VLC->modh->identifiers.requestIdentifier("faction." + node["faction"].String(), + VLC->modh->identifiers.requestIdentifier("faction", node["faction"], [=](si32 factionID) { heroClass->faction = factionID; @@ -237,7 +237,7 @@ CHero * CHeroHandler::loadFromJson(const JsonNode & node) loadHeroSkills(hero, node); loadHeroSpecialty(hero, node); - VLC->modh->identifiers.requestIdentifier("heroClass." + node["class"].String(), + VLC->modh->identifiers.requestIdentifier("heroClass", node["class"], [=](si32 classID) { hero->heroClass = classes.heroClasses[classID]; @@ -261,7 +261,7 @@ void CHeroHandler::loadHeroArmy(CHero * hero, const JsonNode & node) assert(hero->initialArmy[i].minAmount <= hero->initialArmy[i].maxAmount); - VLC->modh->identifiers.requestIdentifier("creature." + source["creature"].String(), [=](si32 creature) + VLC->modh->identifiers.requestIdentifier("creature", source["creature"], [=](si32 creature) { hero->initialArmy[i].creature = CreatureID(creature); }); @@ -278,7 +278,7 @@ void CHeroHandler::loadHeroSkills(CHero * hero, const JsonNode & node) size_t currentIndex = hero->secSkillsInit.size(); hero->secSkillsInit.push_back(std::make_pair(SecondarySkill(-1), skillLevel)); - VLC->modh->identifiers.requestIdentifier("skill." + set["skill"].String(), [=](si32 id) + VLC->modh->identifiers.requestIdentifier("skill", set["skill"], [=](si32 id) { hero->secSkillsInit[currentIndex].first = SecondarySkill(id); }); @@ -300,7 +300,7 @@ void CHeroHandler::loadHeroSkills(CHero * hero, const JsonNode & node) } else { - VLC->modh->identifiers.requestIdentifier("spell." + spell.String(), + VLC->modh->identifiers.requestIdentifier("spell", spell, [=](si32 spellID) { hero->spells.insert(SpellID(spellID)); diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index d8d5e3627..dfdf762d7 100644 --- a/lib/CModHandler.cpp +++ b/lib/CModHandler.cpp @@ -44,82 +44,142 @@ void CIdentifierStorage::checkIdentifier(std::string & ID) } } -void CIdentifierStorage::requestIdentifier(std::string name, const boost::function & callback) +CIdentifierStorage::ObjectCallback::ObjectCallback(std::string localScope, std::string remoteScope, std::string type, + std::string name, const boost::function & callback): + localScope(localScope), + remoteScope(remoteScope), + type(type), + name(name), + callback(callback) +{} + +static std::pair splitString(std::string input, char separator) { - checkIdentifier(name); + std::pair ret; + size_t splitPos = input.find(separator); - // old version with immediate callback posibility. Can't be used for some cases -/* auto iter = registeredObjects.find(name); - - if (iter != registeredObjects.end()) - callback(iter->second); //already registered - trigger callback immediately + if (splitPos == std::string::npos) + { + ret.first.clear(); + ret.second = input; + } else { - if(boost::algorithm::starts_with(name, "primSkill.")) - logGlobal->warnStream() << "incorrect primSkill name requested"; + ret.first = input.substr(0, splitPos); + ret.second = input.substr(splitPos + 1); + } + return ret; +} - missingObjects[name].push_back(callback); // queue callback - }*/ +void CIdentifierStorage::requestIdentifier(ObjectCallback callback) +{ + checkIdentifier(callback.type); + checkIdentifier(callback.name); - missingObjects[name].push_back(callback); // queue callback + assert(!callback.localScope.empty()); + + scheduledRequests.push_back(callback); +} + +void CIdentifierStorage::requestIdentifier(std::string scope, std::string type, std::string name, const boost::function & callback) +{ + auto pair = splitString(name, ':'); // remoteScope:name + + requestIdentifier(ObjectCallback(scope, pair.first, type, pair.second, callback)); +} + +void CIdentifierStorage::requestIdentifier(std::string type, const JsonNode & name, const boost::function & callback) +{ + auto pair = splitString(name.String(), ':'); // remoteScope:name + + requestIdentifier(ObjectCallback(name.meta, pair.first, type, pair.second, callback)); +} + +void CIdentifierStorage::requestIdentifier(const JsonNode & name, const boost::function & callback) +{ + auto pair = splitString(name.String(), ':'); // remoteScope: + auto pair2 = splitString(pair.second, '.'); // type.name + + requestIdentifier(ObjectCallback(name.meta, pair.first, pair2.first, pair2.second, callback)); } void CIdentifierStorage::registerObject(std::string scope, std::string type, std::string name, si32 identifier) { - //TODO: use scope + ObjectData data; + data.scope = scope; + data.id = identifier; + std::string fullID = type + '.' + name; checkIdentifier(fullID); - // do not allow to register same object twice - assert(registeredObjects.find(fullID) == registeredObjects.end()); + registeredObjects.insert(std::make_pair(fullID, data)); +} - registeredObjects[fullID] = identifier; +bool CIdentifierStorage::resolveIdentifier(const ObjectCallback & request) +{ + std::set allowedScopes; - // old version with immediate callback posibility. Can't be used for some cases - /*auto iter = missingObjects.find(fullID); - if (iter != missingObjects.end()) + if (request.remoteScope.empty()) { - //call all awaiting callbacks - BOOST_FOREACH(auto function, iter->second) + // normally ID's from all required mods, own mod and virtual "core" mod are allowed + if (request.localScope != "core") + allowedScopes = VLC->modh->getModData(request.localScope).dependencies; + + allowedScopes.insert(request.localScope); + allowedScopes.insert("core"); + } + else + { + // //...unless destination mod was specified explicitly + allowedScopes.insert(request.remoteScope); + } + + std::string fullID = request.type + '.' + request.name; + + auto entries = registeredObjects.equal_range(fullID); + if (entries.first != entries.second) + { + for (auto it = entries.first; it != entries.second; it++) { - function(identifier); + if (vstd::contains(allowedScopes, it->second.scope)) + { + request.callback(it->second.id); + return true; + } } - missingObjects.erase(iter); - }*/ + + // error found. Try to generate some debug info + logGlobal->errorStream() << "Unknown identifier " << request.type << "." << request.name << " from mod " << request.localScope; + for (auto it = entries.first; it != entries.second; it++) + { + logGlobal->errorStream() << "\tID is available in mod " << it->second.scope; + } + + // temporary code to smooth 0.92->0.93 transition + request.callback(entries.first->second.id); + return true; + } + return false; } void CIdentifierStorage::finalize() { - for (auto it = missingObjects.begin(); it!= missingObjects.end();) + bool errorsFound = false; + + BOOST_FOREACH(const ObjectCallback & request, scheduledRequests) { - auto object = registeredObjects.find(it->first); - if (object != registeredObjects.end()) - { - BOOST_FOREACH(auto function, it->second) - { - function(object->second); - } - it = missingObjects.erase(it); - } - else - it++; + errorsFound |= !resolveIdentifier(request); } - // print list of missing objects and crash - // in future should try to do some cleanup (like returning all id's as 0) - if (!missingObjects.empty()) + if (errorsFound) { - BOOST_FOREACH(auto object, missingObjects) - { - logGlobal->errorStream() << "Error: object " << object.first << " was not found!"; - } BOOST_FOREACH(auto object, registeredObjects) { - logGlobal->traceStream() << object.first << " -> " << object.second; + logGlobal->traceStream() << object.first << " -> " << object.second.id; } - logGlobal->errorStream() << "All known identifiers were dumped into log file"; + logGlobal->errorStream() << "All known identifiers were dumped into log file"; } - assert(missingObjects.empty()); + assert(errorsFound == false); } CContentHandler::ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, size_t size, std::string objectName): @@ -127,11 +187,16 @@ CContentHandler::ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, objectName(objectName), originalData(handler->loadLegacyData(size)) { + BOOST_FOREACH(auto & node, originalData) + { + node.setMeta("core"); + } } void CContentHandler::ContentTypeHandler::preloadModData(std::string modName, std::vector fileList) { JsonNode data = JsonUtils::assembleFromFiles(fileList); + data.setMeta(modName); ModInfo & modInfo = modData[modName]; @@ -425,6 +490,13 @@ std::vector CModHandler::getActiveMods() return activeMods; } +CModInfo & CModHandler::getModData(TModID modId) +{ + CModInfo & mod = allMods.at(modId); + assert(vstd::contains(activeMods, modId)); // not really necessary but won't hurt + return mod; +} + template void CModHandler::handleData(Handler handler, const JsonNode & source, std::string listName, std::string schemaName) { @@ -447,7 +519,7 @@ void CModHandler::loadGameContent() CContentHandler content; logGlobal->infoStream() << "\tInitializing content hander: " << timer.getDiff() << " ms"; - // first - load virtual "core" mod tht contains all data + // first - load virtual "core" mod that contains all data // TODO? move all data into real mods? RoE, AB, SoD, WoG content.preloadModData("core", JsonNode(ResourceID("config/gameConfig.json"))); logGlobal->infoStream() << "\tParsing original game data: " << timer.getDiff() << " ms"; @@ -475,12 +547,11 @@ void CModHandler::loadGameContent() } logGlobal->infoStream() << "\tLoading mod data: " << timer.getDiff() << "ms"; - logGlobal->infoStream() << "\tDone loading data"; - VLC->creh->loadCrExpBon(); VLC->creh->buildBonusTreeForTiers(); //do that after all new creatures are loaded identifiers.finalize(); + logGlobal->infoStream() << "\tResolving identifiers: " << timer.getDiff() << " ms"; logGlobal->infoStream() << "\tAll game content loaded in " << totalTime.getDiff() << " ms"; } diff --git a/lib/CModHandler.h b/lib/CModHandler.h index a640dea1e..3db7e431d 100644 --- a/lib/CModHandler.h +++ b/lib/CModHandler.h @@ -25,15 +25,38 @@ class IHandlerBase; /// if possible, objects ID's should be in format ., camelCase e.g. "creature.grandElf" class CIdentifierStorage { - std::map registeredObjects; - std::map > > missingObjects; + struct ObjectCallback // entry created on ID request + { + std::string localScope; /// scope from which this ID was requested + std::string remoteScope; /// scope in which this object must be found + std::string type; /// type, e.g. creature, faction, hero, etc + std::string name; /// string ID + boost::function callback; + + ObjectCallback(std::string localScope, std::string remoteScope, std::string type, std::string name, const boost::function & callback); + }; + + struct ObjectData // entry created on ID registration + { + si32 id; + std::string scope; /// scope in which this ID located + }; + + std::multimap registeredObjects; + std::vector scheduledRequests; /// Check if identifier can be valid (camelCase, point as separator) void checkIdentifier(std::string & ID); + + void requestIdentifier(ObjectCallback callback); + bool resolveIdentifier(const ObjectCallback & callback); public: /// request identifier for specific object name. If ID is not yet resolved callback will be queued /// and will be called later - void requestIdentifier(std::string name, const boost::function & callback); + void requestIdentifier(std::string scope, std::string type, std::string name, const boost::function & callback); + void requestIdentifier(std::string type, const JsonNode & name, const boost::function & callback); + void requestIdentifier(const JsonNode & name, const boost::function & callback); + /// registers new object, calls all associated callbacks void registerObject(std::string scope, std::string type, std::string name, si32 identifier); @@ -143,6 +166,8 @@ public: /// returns list of mods that should be active with order in which they shoud be loaded std::vector getActiveMods(); + CModInfo & getModData(TModID modId); + /// load content from all available mods void loadGameContent(); diff --git a/lib/CObjectHandler.cpp b/lib/CObjectHandler.cpp index a29d64729..bcdc83fa4 100644 --- a/lib/CObjectHandler.cpp +++ b/lib/CObjectHandler.cpp @@ -828,8 +828,6 @@ void CGHeroInstance::initHero() commander->setArmyObj (castToArmyObj()); //TODO: separate function for setting commanders commander->giveStackExp (exp); //after our exp is set } - else - commander = nullptr; hoverName = VLC->generaltexth->allTexts[15]; boost::algorithm::replace_first(hoverName,"%s",name); diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index 95cfed8ba..7e7306c52 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -390,7 +390,7 @@ CTown::ClientInfo::Point JsonToPoint(const JsonNode & node) void CTownHandler::loadSiegeScreen(CTown &town, const JsonNode & source) { town.clientInfo.siegePrefix = source["imagePrefix"].String(); - VLC->modh->identifiers.requestIdentifier(std::string("creature.") + source["shooter"].String(), [&town](si32 creature) + VLC->modh->identifiers.requestIdentifier("creature", source["shooter"], [&town](si32 creature) { town.clientInfo.siegeShooter = CreatureID(creature); }); @@ -469,7 +469,7 @@ void CTownHandler::loadTown(CTown &town, const JsonNode & source) else town.primaryRes = resIter - boost::begin(GameConstants::RESOURCE_NAMES); - VLC->modh->identifiers.requestIdentifier(std::string("creature." + source["warMachine"].String()), + VLC->modh->identifiers.requestIdentifier("creature", source["warMachine"], [&town](si32 creature) { town.warMachine = CArtHandler::creatureToMachineID(CreatureID(creature)); @@ -498,7 +498,7 @@ void CTownHandler::loadTown(CTown &town, const JsonNode & source) for (size_t j=0; jmodh->identifiers.requestIdentifier(std::string("creature.") + level[j].String(), [=, &town](si32 creature) + VLC->modh->identifiers.requestIdentifier("creature", level[j], [=, &town](si32 creature) { town.creatures[i][j] = CreatureID(creature); }); @@ -510,7 +510,7 @@ void CTownHandler::loadTown(CTown &town, const JsonNode & source) { int chance = node.second.Float(); - VLC->modh->identifiers.requestIdentifier("heroClass." + node.first, [=, &town](si32 classID) + VLC->modh->identifiers.requestIdentifier(node.second.meta, "heroClass",node.first, [=, &town](si32 classID) { VLC->heroh->classes.heroClasses[classID]->selectionProbability[town.faction->index] = chance; }); @@ -520,7 +520,7 @@ void CTownHandler::loadTown(CTown &town, const JsonNode & source) { int chance = node.second.Float(); - VLC->modh->identifiers.requestIdentifier("spell." + node.first, [=, &town](si32 spellID) + VLC->modh->identifiers.requestIdentifier(node.second.meta, "spell", node.first, [=, &town](si32 spellID) { SpellID(spellID).toSpell()->probabilities[town.faction->index] = chance; }); @@ -568,7 +568,7 @@ CFaction * CTownHandler::loadFromJson(const JsonNode &source) faction->name = source["name"].String(); - VLC->modh->identifiers.requestIdentifier ("creature." + source["commander"].String(), + VLC->modh->identifiers.requestIdentifier ("creature", source["commander"], [=](si32 commanderID) { faction->commander = CreatureID(commanderID); diff --git a/lib/JsonNode.cpp b/lib/JsonNode.cpp index 7557a8b2f..3e14efb96 100644 --- a/lib/JsonNode.cpp +++ b/lib/JsonNode.cpp @@ -50,6 +50,7 @@ JsonNode::JsonNode(ResourceID && fileURI): JsonNode::JsonNode(const JsonNode ©): type(DATA_NULL) { + meta = copy.meta; setType(copy.getType()); switch(type) { @@ -70,6 +71,7 @@ JsonNode::~JsonNode() void JsonNode::swap(JsonNode &b) { using std::swap; + swap(meta, b.meta); swap(data, b.data); swap(type, b.type); } @@ -107,6 +109,31 @@ JsonNode::JsonType JsonNode::getType() const return type; } +void JsonNode::setMeta(std::string metadata, bool recursive) +{ + meta = metadata; + if (recursive) + { + switch (type) + { + break; case DATA_VECTOR: + { + BOOST_FOREACH(auto & node, Vector()) + { + node.setMeta(metadata); + } + } + break; case DATA_STRUCT: + { + BOOST_FOREACH(auto & node, Struct()) + { + node.second.setMeta(metadata); + } + } + } + } +} + void JsonNode::setType(JsonType Type) { if (type == Type) @@ -1183,17 +1210,16 @@ const T & parseByMap(const std::map & map, const JsonNode * val, void JsonUtils::resolveIdentifier (si32 &var, const JsonNode &node, std::string name) { - const JsonNode *value; - value = &node[name]; - if (!value->isNull()) + const JsonNode &value = node[name]; + if (!value.isNull()) { - switch (value->getType()) + switch (value.getType()) { case JsonNode::DATA_FLOAT: - var = value->Float(); + var = value.Float(); break; case JsonNode::DATA_STRING: - VLC->modh->identifiers.requestIdentifier (value->String(), [&](si32 identifier) + VLC->modh->identifiers.requestIdentifier(value, [&](si32 identifier) { var = identifier; }); @@ -1212,7 +1238,7 @@ void JsonUtils::resolveIdentifier (const JsonNode &node, si32 &var) var = node.Float(); break; case JsonNode::DATA_STRING: - VLC->modh->identifiers.requestIdentifier (node.String(), [&](si32 identifier) + VLC->modh->identifiers.requestIdentifier (node, [&](si32 identifier) { var = identifier; }); @@ -1301,7 +1327,7 @@ Bonus * JsonUtils::parseBonus (const JsonNode &ability) { shared_ptr l2 = make_shared(); //TODO: How the hell resolve pointer to creature? const JsonVector vec = limiter["parameters"].Vector(); - VLC->modh->identifiers.requestIdentifier(std::string("creature.") + vec[0].String(), [=](si32 creature) + VLC->modh->identifiers.requestIdentifier("creature", vec[0], [=](si32 creature) { l2->setCreature (CreatureID(creature)); }); @@ -1522,11 +1548,19 @@ void JsonUtils::merge(JsonNode & dest, JsonNode & source) switch (source.getType()) { - break; case JsonNode::DATA_NULL: dest.clear(); - break; case JsonNode::DATA_BOOL: std::swap(dest.Bool(), source.Bool()); - break; case JsonNode::DATA_FLOAT: std::swap(dest.Float(), source.Float()); - break; case JsonNode::DATA_STRING: std::swap(dest.String(), source.String()); - break; case JsonNode::DATA_VECTOR: + case JsonNode::DATA_NULL: + { + dest.clear(); + break; + } + case JsonNode::DATA_BOOL: + case JsonNode::DATA_FLOAT: + case JsonNode::DATA_STRING: + { + std::swap(dest, source); + break; + } + case JsonNode::DATA_VECTOR: { size_t total = std::min(source.Vector().size(), dest.Vector().size()); @@ -1541,8 +1575,9 @@ void JsonUtils::merge(JsonNode & dest, JsonNode & source) std::move(source.Vector().begin(), source.Vector().end(), std::back_inserter(dest.Vector())); } + break; } - break; case JsonNode::DATA_STRUCT: + case JsonNode::DATA_STRUCT: { //recursively merge all entries from struct BOOST_FOREACH(auto & node, source.Struct()) diff --git a/lib/JsonNode.h b/lib/JsonNode.h index 0e177fc51..0658480a8 100644 --- a/lib/JsonNode.h +++ b/lib/JsonNode.h @@ -46,6 +46,9 @@ private: JsonData data; public: + /// free to use metadata field + std::string meta; + //Create empty node JsonNode(JsonType Type = DATA_NULL); //Create tree from Json-formatted input @@ -63,6 +66,8 @@ public: bool operator == (const JsonNode &other) const; bool operator != (const JsonNode &other) const; + void setMeta(std::string metadata, bool recursive = true); + /// Convert node to another type. Converting to NULL will clear all data void setType(JsonType Type); JsonType getType() const; @@ -101,6 +106,7 @@ public: template void serialize(Handler &h, const int version) { + h & meta; // simple saving - save json in its string interpretation if (h.saving) { diff --git a/vcmibuilder b/vcmibuilder index 819c1fb37..472a00d93 100755 --- a/vcmibuilder +++ b/vcmibuilder @@ -30,6 +30,7 @@ do --dest) dest_dir=$2 ; shift 2 ;; --wog) wog_archive=$2 ; shift 2 ;; --vcmi) vcmi_archive=$2 ; shift 2 ;; + --convertMP3) useffmpeg=true; shift 1 ;; --download) download=true ; shift 1 ;; --validate) validate=true ; shift 1 ;; *) print_help=true ; shift 1 ;; @@ -57,12 +58,15 @@ then echo " --vcmi ARCHIVE " "Path to manually downloaded VCMI data package" echo " " "Requires unzip" echo - echo " --download " "If specified vcmibuilder will download packages using wget" + echo " --convertMP3 " "Convert all mp3 files into ogg/vorbis" + echo " " "Requires ffmpeg" + echo + echo " --download " "Automatically download requied packages using wget" echo " " "Requires wget and Internet connection" echo echo " --dest DIRECTORY " "Path where resulting data will be placed. Default is ~/.vcmi" echo - echo " --validate " "If specified vcmibuilder will run basic validness checks" + echo " --validate " "Run basic validness checks" exit 0 fi @@ -110,6 +114,11 @@ then fi fi +if [[ -n "$useffmpeg" ]] +then + test_utility "ffmpeg" "-version" +fi + if [[ -n "$cd1_dir" ]] then test_utility "unshield" "-V" @@ -251,6 +260,25 @@ then unzip -qo "$vcmi_archive" -d "$dest_dir" || fail "Error: failed to extract VCMI archive!" fi +if [[ -n "$useffmpeg" ]] +then + # now when all music files (including WoG theme) were installed convert them to ogg + echo "Converting mp3 files..." + + OIFS="$IFS" + IFS=$'\n' + + for file in `find "$dest_dir" -type f -name "*.mp3"` + do + echo "Converting $file" + ogg=${file%.*} + ffmpeg -y -i "$file" -acodec libvorbis "$ogg".ogg 2>/dev/null || fail "Error: failed to convert $file to ogg/vorbis using ffmpeg" "rm -f "$ogg".ogg" + rm -f $file + done + + IFS="$OIFS" +fi + if [[ -n "$validate" ]] then test -f "$dest_dir"/Data/H3bitmap.lod || fail "Error: Heroes 3 data files are missing!"