Browse Source

- basic support for dependencies\conflicts for mods
- adventure map objects transparency should be more similar to h3

Ivan Savenko 12 years ago
parent
commit
7e7d12095b
8 changed files with 228 additions and 60 deletions
  1. 1 2
      Mods/WoG/mod.json
  2. 5 1
      Mods/vcmi/mod.json
  3. 12 12
      client/GUIClasses.cpp
  4. 12 5
      client/UIFramework/SDL_Extensions.cpp
  5. 136 15
      lib/CModHandler.cpp
  6. 17 6
      lib/CModHandler.h
  7. 31 19
      lib/CObjectHandler.cpp
  8. 14 0
      lib/JsonNode.h

+ 1 - 2
Mods/WoG/mod.json

@@ -34,6 +34,5 @@
 	},
 
 	"name" : "In The Wake of Gods",
-	"description" : "Unnofficial addon for Heroes of Might and Magic III",
-	"priority" : 5
+	"description" : "Unnofficial addon for Heroes of Might and Magic III"
 }

+ 5 - 1
Mods/vcmi/mod.json

@@ -17,5 +17,9 @@
 
 	"name" : "VCMI essential files",
 	"description" : "Essential files required for VCMI to run correctly",
-	"priority" : 10
+	
+	"requires" :
+	[
+		"wog"
+	]
 }

+ 12 - 12
client/GUIClasses.cpp

@@ -5911,20 +5911,20 @@ void CWindowWithArtifacts::artifactRemoved(const ArtifactLocation &artLoc)
 
 void CWindowWithArtifacts::artifactMoved(const ArtifactLocation &artLoc, const ArtifactLocation &destLoc)
 {
-    CArtifactsOfHero *destaoh = NULL;
+	CArtifactsOfHero *destaoh = NULL;
 	BOOST_FOREACH(CArtifactsOfHero *aoh, artSets)
-    {
+	{
 		aoh->artifactMoved(artLoc, destLoc);
-        aoh->redraw();
-        if(destLoc.isHolder(aoh->getHero()))
-            destaoh = aoh;
-    }
-
-    //Make sure the status bar is updated so it does not display old text
-    if(destaoh != NULL)
-    {
-        destaoh->getArtPlace(destLoc.slot)->hover(true);
-    }
+		aoh->redraw();
+		if(destLoc.isHolder(aoh->getHero()))
+			destaoh = aoh;
+	}
+
+	//Make sure the status bar is updated so it does not display old text
+	if(destaoh != NULL && destaoh->getArtPlace(destLoc.slot) != NULL)
+	{
+		destaoh->getArtPlace(destLoc.slot)->hover(true);
+	}
 }
 
 void CWindowWithArtifacts::artifactDisassembled(const ArtifactLocation &artLoc)

+ 12 - 5
client/UIFramework/SDL_Extensions.cpp

@@ -194,13 +194,20 @@ Uint32 CSDL_Ext::SDL_GetPixel(SDL_Surface *surface, const int & x, const int & y
 
 void CSDL_Ext::alphaTransform(SDL_Surface *src)
 {
-	//NOTE: colors #7 & #8 used in some of WoG objects. Don't know how they're handled by H3
 	assert(src->format->BitsPerPixel == 8);
-	SDL_Color colors[] = {{0,0,0,0}, {0,0,0,32}, {0,0,0,64}, {0,0,0,128}, {0,0,0,128},
-						{255,255,255,0}, {255,255,255,0}, {255,255,255,0}, {0,0,0,192}, {0,0,0,192}};
+	SDL_Color colors[] =
+	{
+	    {  0,   0,  0,   0}, {  0,   0,   0,  32}, {  0,   0,   0,  64},
+	    {  0,   0,  0, 128}, {  0,   0,   0, 128}
+	};
+
 
-	SDL_SetColors(src, colors, 0, ARRAY_COUNT(colors));
-	SDL_SetColorKey(src, SDL_SRCCOLORKEY, SDL_MapRGBA(src->format, 0, 0, 0, 255));
+	for (size_t i=0; i< ARRAY_COUNT(colors); i++ )
+	{
+		SDL_Color & palColor = src->format->palette->colors[i];
+		palColor = colors[i];
+	}
+	SDL_SetColorKey(src, SDL_SRCCOLORKEY, 0);
 }
 
 static void prepareOutRect(SDL_Rect *src, SDL_Rect *dst, const SDL_Rect & clip_rect)

+ 136 - 15
lib/CModHandler.cpp

@@ -115,37 +115,155 @@ void CModHandler::loadConfigFromFile (std::string name)
 	modules.MITHRIL = gameModules["MITHRIL"].Bool();
 }
 
+// currentList is passed by value to get current list of depending mods
+bool CModHandler::hasCircularDependency(TModID modID, std::set <TModID> currentList) const
+{
+	const CModInfo & mod = allMods.at(modID);
+
+	// Mod already present? We found a loop
+	if (vstd::contains(currentList, modID))
+	{
+		tlog0 << "Error: Circular dependency detected! Printing dependency list:\n";
+		tlog0 << "\t" << mod.name << " -> \n";
+		return true;
+	}
+
+	currentList.insert(modID);
+
+	// recursively check every dependency of this mod
+	BOOST_FOREACH(const TModID & dependency, mod.dependencies)
+	{
+		if (hasCircularDependency(dependency, currentList))
+		{
+			tlog0 << "\t" << mod.name << " ->\n"; // conflict detected, print dependency list
+			return true;
+		}
+	}
+	return false;
+}
+
+bool CModHandler::checkDependencies(const std::vector <TModID> & input) const
+{
+	BOOST_FOREACH(const TModID & id, input)
+	{
+		const CModInfo & mod = allMods.at(id);
+
+		BOOST_FOREACH(const TModID & dep, mod.dependencies)
+		{
+			if (!vstd::contains(input, dep))
+			{
+				tlog0 << "Error: Mod " << mod.name << " requires missing " << dep << "!\n";
+				return false;
+			}
+		}
+
+		BOOST_FOREACH(const TModID & conflicting, mod.conflicts)
+		{
+			if (vstd::contains(input, conflicting))
+			{
+				tlog0 << "Error: Mod " << mod.name << " conflicts with " << allMods.at(conflicting).name << "!\n";
+				return false;
+			}
+		}
+
+		if (hasCircularDependency(id))
+			return false;
+	}
+	return true;
+}
+
+std::vector <TModID> CModHandler::resolveDependencies(std::vector <TModID> input) const
+{
+	// Algorithm may not be the fastest one but VCMI does not needs any speed here
+	// Unless user have dozens of mods with complex dependencies this cide should be fine
+
+	std::vector <TModID> output;
+	output.reserve(input.size());
+
+	std::set <TModID> resolvedMods;
+
+	// Check if all mod dependencies are resolved (moved to resolvedMods)
+	auto isResolved = [&](const CModInfo mod) -> bool
+	{
+		BOOST_FOREACH(const TModID & dependency, mod.dependencies)
+		{
+			if (!vstd::contains(resolvedMods, dependency))
+				return false;
+		}
+		return true;
+	};
+
+	while (!input.empty())
+	{
+		for (auto it = input.begin(); it != input.end();)
+		{
+			if (isResolved(allMods.at(*it)))
+			{
+				resolvedMods.insert(*it);
+				output.push_back(*it);
+				it = input.erase(it);
+				continue;
+			}
+			it++;
+		}
+	}
+
+	return output;
+}
+
 void CModHandler::initialize(std::vector<std::string> availableMods)
 {
-	BOOST_FOREACH(std::string &name, availableMods)
+	JsonNode modConfig(ResourceID("config/modSettings.json"));
+	const JsonNode & modList = modConfig["activeMods"];
+	JsonNode resultingList;
+
+	std::vector <TModID> detectedMods;
+
+	BOOST_FOREACH(std::string name, availableMods)
 	{
+		boost::to_lower(name);
 		std::string modFileName = "mods/" + name + "/mod.json";
 
 		if (CResourceHandler::get()->existsResource(ResourceID(modFileName)))
 		{
 			const JsonNode config = JsonNode(ResourceID(modFileName));
 
-			if (!config.isNull())
+			if (config.isNull())
+				continue;
+
+			if (!modList[name].isNull() && modList[name].Bool() == false )
 			{
-				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";
+				resultingList[name].Bool() = false;
+				continue; // disabled mod
 			}
+			resultingList[name].Bool() = true;
+
+			CModInfo & mod = allMods[name];
+
+			mod.identifier = name;
+			mod.name = config["name"].String();
+			mod.description =  config["description"].String();
+			mod.dependencies = config["depends"].convertTo<std::set<std::string> >();
+			mod.conflicts =    config["conflicts"].convertTo<std::set<std::string> >();
+			detectedMods.push_back(name);
 		}
 		else
 			tlog1 << "\t\t Directory " << name << " does not contains VCMI mod\n";
 	}
 
-	std::sort(activeMods.begin(), activeMods.end(), [&](std::string a, std::string b)
+	if (!checkDependencies(detectedMods))
 	{
-		return allMods[a].loadPriority < allMods[b].loadPriority;
-	});
+		tlog0 << "Critical error: failed to load mods! Exiting...\n";
+		exit(1);
+	}
+
+	activeMods = resolveDependencies(detectedMods);
+
+	modConfig["activeMods"] = resultingList;
+	CResourceHandler::get()->createResource("CONFIG/modSettings.json");
+
+	std::ofstream file(CResourceHandler::get()->getResourceName(ResourceID("config/modSettings.json")), std::ofstream::trunc);
+	file << modConfig;
 }
 
 
@@ -162,8 +280,11 @@ void handleData(Handler handler, const JsonNode & config)
 
 void CModHandler::loadActiveMods()
 {
-	BOOST_FOREACH(std::string & modName, activeMods)
+	BOOST_FOREACH(const TModID & modName, activeMods)
 	{
+		tlog1 << "\t\tLoading mod ";
+		tlog2 << allMods[modName].name << "\n";
+
 		std::string modFileName = "mods/" + modName + "/mod.json";
 
 		const JsonNode config = JsonNode(ResourceID(modFileName));

+ 17 - 6
lib/CModHandler.h

@@ -51,19 +51,18 @@ public:
 	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;
+	/// list of mods that should be loaded before this one
+	std::set <TModID> dependencies;
 
-	/// TODO: list of mods that should be loaded before this one
-	std::set <TModID> requirements;
+	/// list of mods that can't be used in the same time as this one
+	std::set <TModID> conflicts;
 
 	// mod configuration (mod.json). (no need to store it right now)
 	// std::shared_ptr<JsonNode> config; //TODO: unique_ptr can't be serialized
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & name & requirements;
+		h & identifier & description & name & dependencies & conflicts;
 	}
 };
 
@@ -73,6 +72,18 @@ class DLL_LINKAGE CModHandler
 	std::vector <TModID> activeMods;//active mods, in order in which they were loaded
 
 	void loadConfigFromFile (std::string name);
+
+	bool hasCircularDependency(TModID mod, std::set <TModID> currentList = std::set <TModID>()) const;
+
+	//returns false if mod list is incorrec and prints error to console. Possible errors are:
+	// - missing dependency mod
+	// - conflicting mod in load order
+	// - circular dependencies
+	bool checkDependencies(const std::vector <TModID> & input) const;
+
+	// returns load order in which all dependencies are resolved, e.g. loaded after required mods
+	// function assumes that input list is valid (checkDependencies returned true)
+	std::vector <TModID> resolveDependencies(std::vector<TModID> input) const;
 public:
 	CIdentifierStorage identifiers;
 

+ 31 - 19
lib/CObjectHandler.cpp

@@ -6894,39 +6894,51 @@ void CArmedInstance::updateMoraleBonusFromArmy()
 	}
 
 	//number of alignments and presence of undead
-	bool canMix = hasBonusOfType(Bonus::NONEVIL_ALIGNMENT_MIX);
-	std::set<si8> factions;
-	for(TSlots::const_iterator i=Slots().begin(); i!=Slots().end(); i++)
+	std::set<TFaction> factions;
+	bool hasUndead = false;
+
+	BOOST_FOREACH(auto slot, Slots())
 	{
-	 	// Take Angelic Alliance troop-mixing freedom of non-evil, non-Conflux units into account.
-	 	const si8 faction = i->second->type->faction;
-	 	if (canMix
-	 		&& ((faction >= 0 && faction <= 2) || faction == 6 || faction == 7))
-	 	{
-	 		factions.insert(0); // Insert a single faction of the affected group, Castle will do.
-	 	}
-	 	else
-	 	{
-	 		factions.insert(faction);
-	 	}
+		const CStackInstance * inst = slot.second;
+		const CCreature * creature  = VLC->creh->creatures[inst->getCreatureID()];
+
+		factions.insert(creature->faction);
+		// Check for undead flag instead of faction (undead mummies are neutral)
+		hasUndead |= inst->hasBonusOfType(Bonus::UNDEAD);
+	}
+
+	size_t factionsInArmy = factions.size();
+
+	// Take Angelic Alliance troop-mixing freedom of non-evil units into account.
+	if (hasBonusOfType(Bonus::NONEVIL_ALIGNMENT_MIX))
+	{
+		size_t mixableFactions = 0;
+
+		BOOST_FOREACH(TFaction f, factions)
+		{
+			if (VLC->townh->factions[f].alignment != EAlignment::EVIL)
+				mixableFactions++;
+		}
+		if (mixableFactions > 0)
+			factionsInArmy -= mixableFactions - 1;
 	}
 
-	if(factions.size() == 1)
+	if(factionsInArmy == 1)
 	{
 		b->val = +1;
 		b->description = VLC->generaltexth->arraytxt[115]; //All troops of one alignment +1
 	}
 	else
 	{
-	 	b->val = 2-factions.size();
-		b->description = boost::str(boost::format(VLC->generaltexth->arraytxt[114]) % factions.size() % b->val); //Troops of %d alignments %d
+	 	b->val = 2 - factionsInArmy;
+		b->description = boost::str(boost::format(VLC->generaltexth->arraytxt[114]) % factionsInArmy % b->val); //Troops of %d alignments %d
 	}
 	boost::algorithm::trim(b->description);
 
-	//-1 modifier for any Necropolis unit in army
+	//-1 modifier for any Undead unit in army
 	const ui8 UNDEAD_MODIFIER_ID = -2;
 	Bonus *undeadModifier = getBonusList().getFirst(Selector::source(Bonus::ARMY, UNDEAD_MODIFIER_ID));
- 	if(vstd::contains(factions, ETownType::NECROPOLIS))
+ 	if(hasUndead)
 	{
 		if(!undeadModifier)
 			addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, -1, UNDEAD_MODIFIER_ID, VLC->generaltexth->arraytxt[116]));

+ 14 - 0
lib/JsonNode.h

@@ -175,6 +175,20 @@ namespace JsonDetail
 		}
 	};
 
+	template<typename Type>
+	struct JsonConverter<std::set<Type> >
+	{
+		static std::set<Type> convert(const JsonNode & node)
+		{
+			std::set<Type> ret;
+			BOOST_FOREACH(auto entry, node.Vector())
+			{
+				ret.insert(entry.convertTo<Type>());
+			}
+			return ret;
+		}
+	};
+
 	template<typename Type>
 	struct JsonConverter<std::vector<Type> >
 	{