Browse Source

- 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

Ivan Savenko 12 years ago
parent
commit
8273f323b1

+ 0 - 0
config/creatures/wog.json → Mods/WoG/config/wog/creatures.json


+ 38 - 0
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"
+	}
+}

+ 15 - 1
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"
+	]
 }

+ 6 - 0
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];

+ 1 - 1
config/factions/castle.json

@@ -4,7 +4,7 @@
 		"index" : 0,
 		"nativeTerrain": "grass",
 		"alignment" : "good",
-		"commander" : "paladin1",
+		"commander" : "zealot",
 		"creatureBackground" :
 		{
 			"120px" : "TPCASCAS",

+ 1 - 1
config/factions/conflux.json

@@ -4,7 +4,7 @@
 		"index" : 8,
 		"nativeTerrain": "grass",
 		"alignment" : "neutral",
-		"commander" : "astralSpirit1",
+		"commander" : "iceElemental",
 		"creatureBackground" :
 		{
 			"120px" : "TPCASELE",

+ 1 - 1
config/factions/dungeon.json

@@ -4,7 +4,7 @@
 		"index" : 5,
 		"nativeTerrain": "subterra",
 		"alignment" : "evil",
-		"commander" : "brute1",
+		"commander" : "medusaQueen",
 		"creatureBackground" :
 		{
 			"120px" : "TPCASDUN",

+ 1 - 1
config/factions/fortress.json

@@ -4,7 +4,7 @@
 		"index" : 7,
 		"nativeTerrain": "swamp",
 		"alignment" : "neutral",
-		"commander" : "shaman1",
+		"commander" : "lizardWarrior",
 		"creatureBackground" :
 		{
 			"120px" : "TPCASFOR",

+ 1 - 1
config/factions/inferno.json

@@ -4,7 +4,7 @@
 		"index" : 3,
 		"nativeTerrain": "lava",
 		"alignment" : "evil",
-		"commander" : "succubus1",
+		"commander" : "magog",
 		"creatureBackground" :
 		{
 			"120px" : "TPCASINF",

+ 1 - 1
config/factions/necropolis.json

@@ -4,7 +4,7 @@
 		"index" : 4,
 		"nativeTerrain": "dirt",
 		"alignment" : "evil",
-		"commander" : "soulEater1",
+		"commander" : "powerLich",
 		"creatureBackground" :
 		{
 			"120px" : "TPCASNEC",

+ 1 - 1
config/factions/rampart.json

@@ -4,7 +4,7 @@
 		"index" : 1,
 		"nativeTerrain": "grass",
 		"alignment" : "good",
-		"commander" : "hierophant1",
+		"commander" : "grandElf",
 		"creatureBackground" :
 		{
 			"120px" : "TPCASRAM",

+ 1 - 1
config/factions/stronghold.json

@@ -4,7 +4,7 @@
 		"index" : 6,
 		"nativeTerrain": "rough",
 		"alignment" : "neutral",
-		"commander" : "ogreLeader1",
+		"commander" : "orcChieftain",
 		"creatureBackground" :
 		{
 			"120px" : "TPCASSTR",

+ 1 - 1
config/factions/tower.json

@@ -4,7 +4,7 @@
 		"index" : 2,
 		"nativeTerrain" : "snow",
 		"alignment" : "good",
-		"commander" : "templeGuardian1",
+		"commander" : "archMage",
 		"creatureBackground" :
 		{
 			"120px" : "TPCASTOW",

+ 1 - 2
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" :

+ 1 - 1
lib/CArtHandler.cpp

@@ -366,7 +366,7 @@ void CArtHandler::loadComponents(CArtifact * art, const JsonNode & node)
 		art->constituents.reset(new std::vector<CArtifact *>());
 		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

+ 6 - 3
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));
 		});

+ 6 - 6
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));

+ 120 - 49
lib/CModHandler.cpp

@@ -44,82 +44,142 @@ void CIdentifierStorage::checkIdentifier(std::string & ID)
 	}
 }
 
-void CIdentifierStorage::requestIdentifier(std::string name, const boost::function<void(si32)> & callback)
+CIdentifierStorage::ObjectCallback::ObjectCallback(std::string localScope, std::string remoteScope, std::string type,
+                                                   std::string name, const boost::function<void(si32)> & callback):
+    localScope(localScope),
+    remoteScope(remoteScope),
+    type(type),
+    name(name),
+    callback(callback)
+{}
+
+static std::pair<std::string, std::string> splitString(std::string input, char separator)
 {
-	checkIdentifier(name);
+	std::pair<std::string, std::string> 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;
+}
+
+void CIdentifierStorage::requestIdentifier(ObjectCallback callback)
+{
+	checkIdentifier(callback.type);
+	checkIdentifier(callback.name);
 
-		missingObjects[name].push_back(callback); // queue callback
-	}*/
+	assert(!callback.localScope.empty());
 
-	missingObjects[name].push_back(callback); // queue callback
+	scheduledRequests.push_back(callback);
+}
+
+void CIdentifierStorage::requestIdentifier(std::string scope, std::string type, std::string name, const boost::function<void(si32)> & 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<void(si32)> & 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<void(si32)> & callback)
+{
+	auto pair  = splitString(name.String(), ':'); // remoteScope:<type.name>
+	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));
+}
+
+bool CIdentifierStorage::resolveIdentifier(const ObjectCallback & request)
+{
+	std::set<std::string> allowedScopes;
+
+	if (request.remoteScope.empty())
+	{
+		// 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);
+	}
 
-	registeredObjects[fullID] = identifier;
+	std::string fullID = request.type + '.' + request.name;
 
-	// old version with immediate callback posibility. Can't be used for some cases
-	/*auto iter = missingObjects.find(fullID);
-	if (iter != missingObjects.end())
+	auto entries = registeredObjects.equal_range(fullID);
+	if (entries.first != entries.second)
 	{
-		//call all awaiting callbacks
-		BOOST_FOREACH(auto function, iter->second)
+		for (auto it = entries.first; it != entries.second; it++)
+		{
+			if (vstd::contains(allowedScopes, it->second.scope))
+			{
+				request.callback(it->second.id);
+				return true;
+			}
+		}
+
+		// 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++)
 		{
-			function(identifier);
+			logGlobal->errorStream() << "\tID is available in mod " << it->second.scope;
 		}
-		missingObjects.erase(iter);
-	}*/
+
+		// 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<std::string> fileList)
 {
 	JsonNode data = JsonUtils::assembleFromFiles(fileList);
+	data.setMeta(modName);
 
 	ModInfo & modInfo = modData[modName];
 
@@ -425,6 +490,13 @@ std::vector<std::string> 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<typename Handler>
 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";
 }
 

+ 28 - 3
lib/CModHandler.h

@@ -25,15 +25,38 @@ class IHandlerBase;
 /// if possible, objects ID's should be in format <type>.<name>, camelCase e.g. "creature.grandElf"
 class CIdentifierStorage
 {
-	std::map<std::string, si32 > registeredObjects;
-	std::map<std::string, std::vector<boost::function<void(si32)> > > 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<void(si32)> callback;
+
+		ObjectCallback(std::string localScope, std::string remoteScope, std::string type, std::string name, const boost::function<void(si32)> & callback);
+	};
+
+	struct ObjectData // entry created on ID registration
+	{
+		si32 id;
+		std::string scope; /// scope in which this ID located
+	};
+
+	std::multimap<std::string, ObjectData > registeredObjects;
+	std::vector<ObjectCallback> 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<void(si32)> & callback);
+	void requestIdentifier(std::string scope, std::string type, std::string name, const boost::function<void(si32)> & callback);
+	void requestIdentifier(std::string type, const JsonNode & name, const boost::function<void(si32)> & callback);
+	void requestIdentifier(const JsonNode & name, const boost::function<void(si32)> & 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<std::string> getActiveMods();
 
+	CModInfo & getModData(TModID modId);
+
 	/// load content from all available mods
 	void loadGameContent();
 

+ 0 - 2
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);

+ 6 - 6
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; j<level.size(); j++)
 		{
-			VLC->modh->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);

+ 49 - 14
lib/JsonNode.cpp

@@ -50,6 +50,7 @@ JsonNode::JsonNode(ResourceID && fileURI):
 JsonNode::JsonNode(const JsonNode &copy):
 	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<std::string, T> & 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<CCreatureTypeLimiter> l2 = make_shared<CCreatureTypeLimiter>(); //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())

+ 6 - 0
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 <typename Handler> void serialize(Handler &h, const int version)
 	{
+		h & meta;
 		// simple saving - save json in its string interpretation
 		if (h.saving)
 		{

+ 30 - 2
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!"