mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	- basic support for dependencies\conflicts for mods
- adventure map objects transparency should be more similar to h3
This commit is contained in:
		| @@ -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" | ||||
| } | ||||
|   | ||||
| @@ -17,5 +17,9 @@ | ||||
|  | ||||
| 	"name" : "VCMI essential files", | ||||
| 	"description" : "Essential files required for VCMI to run correctly", | ||||
| 	"priority" : 10 | ||||
| 	 | ||||
| 	"requires" : | ||||
| 	[ | ||||
| 		"wog" | ||||
| 	] | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
|     } | ||||
| 		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); | ||||
|     } | ||||
| 	//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) | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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()) | ||||
| 			{ | ||||
| 				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); | ||||
| 			if (config.isNull()) | ||||
| 				continue; | ||||
|  | ||||
| 				tlog1 << "\t\tMod "; | ||||
| 				tlog2 << allMods[name].name; | ||||
| 				tlog1 << " enabled\n"; | ||||
| 			if (!modList[name].isNull() && modList[name].Bool() == false ) | ||||
| 			{ | ||||
| 				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)); | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
|   | ||||
| @@ -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); | ||||
| 	} | ||||
|  | ||||
| 	if(factions.size() == 1) | ||||
| 	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(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])); | ||||
|   | ||||
| @@ -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> > | ||||
| 	{ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user