mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Rebase on develop
This commit is contained in:
		| @@ -12,11 +12,59 @@ | |||||||
|  |  | ||||||
| #include "../../lib/JsonNode.h" | #include "../../lib/JsonNode.h" | ||||||
| #include "../../lib/filesystem/CFileInputStream.h" | #include "../../lib/filesystem/CFileInputStream.h" | ||||||
|  | #include "../../lib/GameConstants.h" | ||||||
|  |  | ||||||
|  | const int maxSections = 3; // versions consist from up to 3 sections, major.minor.patch | ||||||
|  |  | ||||||
|  | bool isCompatible(const QString & verMin, const QString & verMax) | ||||||
|  | { | ||||||
|  | 	QList<int> vcmiVersionList = {GameConstants::VCMI_VERSION_MAJOR, | ||||||
|  | 								  GameConstants::VCMI_VERSION_MINOR, | ||||||
|  | 								  GameConstants::VCMI_VERSION_PATCH}; | ||||||
|  |  | ||||||
|  | 	if(!verMin.isEmpty()) | ||||||
|  | 	{ | ||||||
|  | 		QStringList verMinList = verMin.split("."); | ||||||
|  | 		assert(verMinList.size() == maxSections); | ||||||
|  | 		bool compatibleMin = true; | ||||||
|  | 		for(int i = 0; i < maxSections; i++) | ||||||
|  | 		{ | ||||||
|  | 			if(verMinList[i].toInt() < vcmiVersionList[i]) | ||||||
|  | 			{ | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 			if(verMinList[i].toInt() > vcmiVersionList[i]) | ||||||
|  | 			{ | ||||||
|  | 				compatibleMin = false; | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if(!compatibleMin) | ||||||
|  | 			return false; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if(!verMax.isEmpty()) | ||||||
|  | 	{ | ||||||
|  | 		QStringList verMaxList = verMax.split("."); | ||||||
|  | 		assert(verMaxList.size() == maxSections); | ||||||
|  | 		for(int i = 0; i < maxSections; i++) | ||||||
|  | 		{ | ||||||
|  | 			if(verMaxList[i].toInt() > vcmiVersionList[i]) | ||||||
|  | 			{ | ||||||
|  | 				return true; | ||||||
|  | 			} | ||||||
|  | 			if(verMaxList[i].toInt() < vcmiVersionList[i]) | ||||||
|  | 			{ | ||||||
|  | 				return false; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
|  |  | ||||||
| bool CModEntry::compareVersions(QString lesser, QString greater) | bool CModEntry::compareVersions(QString lesser, QString greater) | ||||||
| { | { | ||||||
| 	static const int maxSections = 3; // versions consist from up to 3 sections, major.minor.patch |  | ||||||
|  |  | ||||||
| 	QStringList lesserList = lesser.split("."); | 	QStringList lesserList = lesser.split("."); | ||||||
| 	QStringList greaterList = greater.split("."); | 	QStringList greaterList = greater.split("."); | ||||||
|  |  | ||||||
| @@ -92,6 +140,15 @@ bool CModEntry::isUpdateable() const | |||||||
| 	return false; | 	return false; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | bool CModEntry::isCompatible() const | ||||||
|  | { | ||||||
|  | 	if(!isInstalled()) | ||||||
|  | 		return false; | ||||||
|  |  | ||||||
|  | 	auto compatibility = localData["compatibility"].toMap(); | ||||||
|  | 	return ::isCompatible(compatibility["min"].toString(), compatibility["max"].toString()); | ||||||
|  | } | ||||||
|  |  | ||||||
| bool CModEntry::isEssential() const | bool CModEntry::isEssential() const | ||||||
| { | { | ||||||
| 	return getValue("storedLocaly").toBool(); | 	return getValue("storedLocaly").toBool(); | ||||||
| @@ -102,6 +159,11 @@ bool CModEntry::isInstalled() const | |||||||
| 	return !localData.isEmpty(); | 	return !localData.isEmpty(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | bool CModEntry::isValid() const | ||||||
|  | { | ||||||
|  | 	return !localData.isEmpty() || !repository.isEmpty(); | ||||||
|  | } | ||||||
|  |  | ||||||
| int CModEntry::getModStatus() const | int CModEntry::getModStatus() const | ||||||
| { | { | ||||||
| 	int status = 0; | 	int status = 0; | ||||||
| @@ -193,7 +255,11 @@ static QVariant getValue(QVariant input, QString path) | |||||||
| 		QString remainder = "/" + path.section('/', 2, -1); | 		QString remainder = "/" + path.section('/', 2, -1); | ||||||
|  |  | ||||||
| 		entryName.remove(0, 1); | 		entryName.remove(0, 1); | ||||||
| 		return getValue(input.toMap().value(entryName), remainder); | 		QMap<QString, QString> keyNormalize; | ||||||
|  | 		for(auto & key : input.toMap().keys()) | ||||||
|  | 			keyNormalize[key.toLower()] = key; | ||||||
|  |  | ||||||
|  | 		return getValue(input.toMap().value(keyNormalize[entryName]), remainder); | ||||||
| 	} | 	} | ||||||
| 	else | 	else | ||||||
| 	{ | 	{ | ||||||
| @@ -203,6 +269,7 @@ static QVariant getValue(QVariant input, QString path) | |||||||
|  |  | ||||||
| CModEntry CModList::getMod(QString modname) const | CModEntry CModList::getMod(QString modname) const | ||||||
| { | { | ||||||
|  | 	modname = modname.toLower(); | ||||||
| 	QVariantMap repo; | 	QVariantMap repo; | ||||||
| 	QVariantMap local = localModList[modname].toMap(); | 	QVariantMap local = localModList[modname].toMap(); | ||||||
| 	QVariantMap settings; | 	QVariantMap settings; | ||||||
| @@ -246,14 +313,14 @@ CModEntry CModList::getMod(QString modname) const | |||||||
| 		QVariant repoVal = getValue(entry, path); | 		QVariant repoVal = getValue(entry, path); | ||||||
| 		if(repoVal.isValid()) | 		if(repoVal.isValid()) | ||||||
| 		{ | 		{ | ||||||
| 			if(repo.empty()) | 			auto repoValMap = repoVal.toMap(); | ||||||
|  | 			auto compatibility = repoValMap["compatibility"].toMap(); | ||||||
|  | 			if(isCompatible(compatibility["min"].toString(), compatibility["max"].toString())) | ||||||
| 			{ | 			{ | ||||||
| 				repo = repoVal.toMap(); | 				if(repo.empty() || CModEntry::compareVersions(repo["version"].toString(), repoValMap["version"].toString())) | ||||||
| 			} | 				{ | ||||||
| 			else | 					repo = repoValMap; | ||||||
| 			{ | 				} | ||||||
| 				if(CModEntry::compareVersions(repo["version"].toString(), repoVal.toMap()["version"].toString())) |  | ||||||
| 					repo = repoVal.toMap(); |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @@ -297,12 +364,12 @@ QVector<QString> CModList::getModList() const | |||||||
| 	{ | 	{ | ||||||
| 		for(auto it = repo.begin(); it != repo.end(); it++) | 		for(auto it = repo.begin(); it != repo.end(); it++) | ||||||
| 		{ | 		{ | ||||||
| 			knownMods.insert(it.key()); | 			knownMods.insert(it.key().toLower()); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	for(auto it = localModList.begin(); it != localModList.end(); it++) | 	for(auto it = localModList.begin(); it != localModList.end(); it++) | ||||||
| 	{ | 	{ | ||||||
| 		knownMods.insert(it.key()); | 		knownMods.insert(it.key().toLower()); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for(auto entry : knownMods) | 	for(auto entry : knownMods) | ||||||
|   | |||||||
| @@ -51,6 +51,10 @@ public: | |||||||
| 	bool isInstalled() const; | 	bool isInstalled() const; | ||||||
| 	// vcmi essential files | 	// vcmi essential files | ||||||
| 	bool isEssential() const; | 	bool isEssential() const; | ||||||
|  | 	// checks if verison is compatible with vcmi | ||||||
|  | 	bool isCompatible() const; | ||||||
|  | 	// returns if has any data | ||||||
|  | 	bool isValid() const; | ||||||
|  |  | ||||||
| 	// see ModStatus enum | 	// see ModStatus enum | ||||||
| 	int getModStatus() const; | 	int getModStatus() const; | ||||||
|   | |||||||
| @@ -245,6 +245,7 @@ bool CModFilterModel::filterMatchesThis(const QModelIndex & source) const | |||||||
| { | { | ||||||
| 	CModEntry mod = base->getMod(source.data(ModRoles::ModNameRole).toString()); | 	CModEntry mod = base->getMod(source.data(ModRoles::ModNameRole).toString()); | ||||||
| 	return (mod.getModStatus() & filterMask) == filteredType && | 	return (mod.getModStatus() & filterMask) == filteredType && | ||||||
|  | 			mod.isValid() && | ||||||
| 	       QSortFilterProxyModel::filterAcceptsRow(source.row(), source.parent()); | 	       QSortFilterProxyModel::filterAcceptsRow(source.row(), source.parent()); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -169,6 +169,10 @@ bool CModManager::canEnableMod(QString modname) | |||||||
| 	if(!mod.isInstalled()) | 	if(!mod.isInstalled()) | ||||||
| 		return addError(modname, "Mod must be installed first"); | 		return addError(modname, "Mod must be installed first"); | ||||||
|  |  | ||||||
|  | 	//check for compatibility | ||||||
|  | 	if(!mod.isCompatible()) | ||||||
|  | 		return addError(modname, "Mod is not compatible, please update VCMI and checkout latest mod revisions"); | ||||||
|  |  | ||||||
| 	for(auto modEntry : mod.getValue("depends").toStringList()) | 	for(auto modEntry : mod.getValue("depends").toStringList()) | ||||||
| 	{ | 	{ | ||||||
| 		if(!modList->hasMod(modEntry)) // required mod is not available | 		if(!modList->hasMod(modEntry)) // required mod is not available | ||||||
|   | |||||||
| @@ -532,6 +532,51 @@ JsonNode addMeta(JsonNode config, std::string meta) | |||||||
| 	return config; | 	return config; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | CModInfo::Version CModInfo::Version::GameVersion() | ||||||
|  | { | ||||||
|  | 	return Version(GameConstants::VCMI_VERSION_MAJOR, GameConstants::VCMI_VERSION_MINOR, GameConstants::VCMI_VERSION_PATCH); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | CModInfo::Version CModInfo::Version::fromString(std::string from) | ||||||
|  | { | ||||||
|  | 	int major = 0, minor = 0, patch = 0; | ||||||
|  | 	try | ||||||
|  | 	{ | ||||||
|  | 		auto pointPos = from.find('.'); | ||||||
|  | 		major = std::stoi(from.substr(0, pointPos)); | ||||||
|  | 		if(pointPos != std::string::npos) | ||||||
|  | 		{ | ||||||
|  | 			from = from.substr(pointPos + 1); | ||||||
|  | 			pointPos = from.find('.'); | ||||||
|  | 			minor = std::stoi(from.substr(0, pointPos)); | ||||||
|  | 			if(pointPos != std::string::npos) | ||||||
|  | 				patch = std::stoi(from.substr(pointPos + 1)); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	catch(const std::invalid_argument & e) | ||||||
|  | 	{ | ||||||
|  | 		return Version(); | ||||||
|  | 	} | ||||||
|  | 	return Version(major, minor, patch); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::string CModInfo::Version::toString() const | ||||||
|  | { | ||||||
|  | 	return std::to_string(major) + '.' + std::to_string(minor) + '.' + std::to_string(patch); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool CModInfo::Version::compatible(const Version & other, bool checkMinor, bool checkPatch) const | ||||||
|  | { | ||||||
|  | 	return  (major == other.major && | ||||||
|  | 			(!checkMinor || minor >= other.minor) && | ||||||
|  | 			(!checkPatch || minor > other.minor || (minor == other.minor && patch >= other.patch))); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool CModInfo::Version::isNull() const | ||||||
|  | { | ||||||
|  | 	return major == 0 && minor == 0 && patch == 0; | ||||||
|  | } | ||||||
|  |  | ||||||
| CModInfo::CModInfo(): | CModInfo::CModInfo(): | ||||||
| 	checksum(0), | 	checksum(0), | ||||||
| 	enabled(false), | 	enabled(false), | ||||||
| @@ -551,6 +596,12 @@ CModInfo::CModInfo(std::string identifier,const JsonNode & local, const JsonNode | |||||||
| 	validation(PENDING), | 	validation(PENDING), | ||||||
| 	config(addMeta(config, identifier)) | 	config(addMeta(config, identifier)) | ||||||
| { | { | ||||||
|  | 	version = Version::fromString(config["version"].String()); | ||||||
|  | 	if(!config["compatibility"].isNull()) | ||||||
|  | 	{ | ||||||
|  | 		vcmiCompatibleMin = Version::fromString(config["compatibility"]["min"].String()); | ||||||
|  | 		vcmiCompatibleMax = Version::fromString(config["compatibility"]["max"].String()); | ||||||
|  | 	} | ||||||
| 	loadLocalData(local); | 	loadLocalData(local); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -601,6 +652,13 @@ void CModInfo::loadLocalData(const JsonNode & data) | |||||||
| 		validated = data["validated"].Bool(); | 		validated = data["validated"].Bool(); | ||||||
| 		checksum  = strtol(data["checksum"].String().c_str(), nullptr, 16); | 		checksum  = strtol(data["checksum"].String().c_str(), nullptr, 16); | ||||||
| 	} | 	} | ||||||
|  | 	 | ||||||
|  | 	//check compatibility | ||||||
|  | 	bool wasEnabled = enabled; | ||||||
|  | 	enabled &= vcmiCompatibleMin.isNull() || Version::GameVersion().compatible(vcmiCompatibleMin); | ||||||
|  | 	enabled &= vcmiCompatibleMax.isNull() || vcmiCompatibleMax.compatible(Version::GameVersion()); | ||||||
|  | 	if(wasEnabled && !enabled) | ||||||
|  | 		logGlobal->warn("Mod %s is incompatible with current version of VCMI and cannot be enabled", name); | ||||||
|  |  | ||||||
| 	if (enabled) | 	if (enabled) | ||||||
| 		validation = validated ? PASSED : PENDING; | 		validation = validated ? PASSED : PENDING; | ||||||
|   | |||||||
| @@ -177,6 +177,30 @@ public: | |||||||
| 		FAILED, | 		FAILED, | ||||||
| 		PASSED | 		PASSED | ||||||
| 	}; | 	}; | ||||||
|  | 	 | ||||||
|  | 	struct Version | ||||||
|  | 	{ | ||||||
|  | 		int major = 0; | ||||||
|  | 		int minor = 0; | ||||||
|  | 		int patch = 0; | ||||||
|  | 		 | ||||||
|  | 		Version() = default; | ||||||
|  | 		Version(int mj, int mi, int p): major(mj), minor(mi), patch(p) {} | ||||||
|  | 		 | ||||||
|  | 		static Version GameVersion(); | ||||||
|  | 		static Version fromString(std::string from); | ||||||
|  | 		std::string toString() const; | ||||||
|  | 		 | ||||||
|  | 		bool compatible(const Version & other, bool checkMinor = false, bool checkPatch = false) const; | ||||||
|  | 		bool isNull() const; | ||||||
|  | 		 | ||||||
|  | 		template <typename Handler> void serialize(Handler &h, const int version) | ||||||
|  | 		{ | ||||||
|  | 			h & major; | ||||||
|  | 			h & minor; | ||||||
|  | 			h & patch; | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  |  | ||||||
| 	/// identifier, identical to name of folder with mod | 	/// identifier, identical to name of folder with mod | ||||||
| 	std::string identifier; | 	std::string identifier; | ||||||
| @@ -184,6 +208,12 @@ public: | |||||||
| 	/// human-readable strings | 	/// human-readable strings | ||||||
| 	std::string name; | 	std::string name; | ||||||
| 	std::string description; | 	std::string description; | ||||||
|  | 	 | ||||||
|  | 	/// version of the mod | ||||||
|  | 	Version version; | ||||||
|  | 	 | ||||||
|  | 	///The  vcmi versions compatible with the mod | ||||||
|  | 	Version vcmiCompatibleMin, vcmiCompatibleMax; | ||||||
|  |  | ||||||
| 	/// list of mods that should be loaded before this one | 	/// list of mods that should be loaded before this one | ||||||
| 	std::set <TModID> dependencies; | 	std::set <TModID> dependencies; | ||||||
| @@ -210,18 +240,6 @@ public: | |||||||
| 	static std::string getModDir(std::string name); | 	static std::string getModDir(std::string name); | ||||||
| 	static std::string getModFile(std::string name); | 	static std::string getModFile(std::string name); | ||||||
|  |  | ||||||
| 	template <typename Handler> void serialize(Handler &h, const int version) |  | ||||||
| 	{ |  | ||||||
| 		h & identifier; |  | ||||||
| 		h & description; |  | ||||||
| 		h & name; |  | ||||||
| 		h & dependencies; |  | ||||||
| 		h & conflicts; |  | ||||||
| 		h & config; |  | ||||||
| 		h & checksum; |  | ||||||
| 		h & validation; |  | ||||||
| 		h & enabled; |  | ||||||
| 	} |  | ||||||
| private: | private: | ||||||
| 	void loadLocalData(const JsonNode & data); | 	void loadLocalData(const JsonNode & data); | ||||||
| }; | }; | ||||||
| @@ -256,6 +274,13 @@ class DLL_LINKAGE CModHandler | |||||||
| 	void loadMods(std::string path, std::string parent, const JsonNode & modSettings, bool enableMods); | 	void loadMods(std::string path, std::string parent, const JsonNode & modSettings, bool enableMods); | ||||||
| 	void loadOneMod(std::string modName, std::string parent, const JsonNode & modSettings, bool enableMods); | 	void loadOneMod(std::string modName, std::string parent, const JsonNode & modSettings, bool enableMods); | ||||||
| public: | public: | ||||||
|  | 	 | ||||||
|  | 	class Incompatibility: public std::logic_error | ||||||
|  | 	{ | ||||||
|  | 	public: | ||||||
|  | 		Incompatibility(const std::string & w): std::logic_error(w) | ||||||
|  | 		{} | ||||||
|  | 	}; | ||||||
|  |  | ||||||
| 	CIdentifierStorage identifiers; | 	CIdentifierStorage identifiers; | ||||||
|  |  | ||||||
| @@ -336,8 +361,35 @@ public: | |||||||
|  |  | ||||||
| 	template <typename Handler> void serialize(Handler &h, const int version) | 	template <typename Handler> void serialize(Handler &h, const int version) | ||||||
| 	{ | 	{ | ||||||
| 		h & allMods; | 		if(h.saving) | ||||||
| 		h & activeMods; | 		{ | ||||||
|  | 			h & activeMods; | ||||||
|  | 			for(auto & m : activeMods) | ||||||
|  | 				h & allMods[m].version; | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			std::vector<TModID> newActiveMods; | ||||||
|  | 			h & newActiveMods; | ||||||
|  | 			for(auto & m : newActiveMods) | ||||||
|  | 			{ | ||||||
|  | 				if(!allMods.count(m)) | ||||||
|  | 					throw Incompatibility(m + " unkown mod"); | ||||||
|  | 				 | ||||||
|  | 				CModInfo::Version mver; | ||||||
|  | 				h & mver; | ||||||
|  | 				if(!allMods[m].version.isNull() && !mver.isNull() && !allMods[m].version.compatible(mver)) | ||||||
|  | 				{ | ||||||
|  | 					std::string err = allMods[m].name + | ||||||
|  | 					": version needed " + mver.toString() + | ||||||
|  | 					"but you have installed " + allMods[m].version.toString(); | ||||||
|  | 					throw Incompatibility(err); | ||||||
|  | 				} | ||||||
|  | 				allMods[m].enabled = true; | ||||||
|  | 			} | ||||||
|  | 			std::swap(activeMods, newActiveMods); | ||||||
|  | 		} | ||||||
|  | 				 | ||||||
| 		h & settings; | 		h & settings; | ||||||
| 		h & modules; | 		h & modules; | ||||||
| 		h & identifiers; | 		h & identifiers; | ||||||
|   | |||||||
| @@ -51,10 +51,18 @@ const TeamID TeamID::NO_TEAM = TeamID(255); | |||||||
|  |  | ||||||
| namespace GameConstants | namespace GameConstants | ||||||
| { | { | ||||||
|  | 	const int VCMI_VERSION_MAJOR = 1; | ||||||
|  | 	const int VCMI_VERSION_MINOR = 0; | ||||||
|  | 	const int VCMI_VERSION_PATCH = 0; | ||||||
|  |  | ||||||
|  | 	const std::string VCMI_VERSION_STRING = std::to_string(VCMI_VERSION_MAJOR) + "." + | ||||||
|  | 											std::to_string(VCMI_VERSION_MINOR) + "." + | ||||||
|  | 											std::to_string(VCMI_VERSION_PATCH); | ||||||
|  |  | ||||||
| #ifdef VCMI_NO_EXTRA_VERSION | #ifdef VCMI_NO_EXTRA_VERSION | ||||||
| 	const std::string VCMI_VERSION = std::string("VCMI 1.0.0"); | 	const std::string VCMI_VERSION = std::string("VCMI ") + VCMI_VERSION_STRING; | ||||||
| #else | #else | ||||||
| 	const std::string VCMI_VERSION = std::string("VCMI 1.0.0.") + GIT_SHA1; | 	const std::string VCMI_VERSION = std::string("VCMI ") + VCMI_VERSION_STRING + "." + GIT_SHA1; | ||||||
| #endif | #endif | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -36,6 +36,9 @@ struct IdTag | |||||||
|  |  | ||||||
| namespace GameConstants | namespace GameConstants | ||||||
| { | { | ||||||
|  | 	DLL_LINKAGE extern const int VCMI_VERSION_MAJOR; | ||||||
|  | 	DLL_LINKAGE extern const int VCMI_VERSION_MINOR; | ||||||
|  | 	DLL_LINKAGE extern const int VCMI_VERSION_PATCH; | ||||||
| 	DLL_LINKAGE extern const std::string VCMI_VERSION; | 	DLL_LINKAGE extern const std::string VCMI_VERSION; | ||||||
|  |  | ||||||
| 	const int PUZZLE_MAP_PIECES = 48; | 	const int PUZZLE_MAP_PIECES = 48; | ||||||
|   | |||||||
| @@ -131,9 +131,6 @@ public: | |||||||
| 			callWhenDeserializing(); | 			callWhenDeserializing(); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| private: |  | ||||||
| 	void update800(); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| extern DLL_LINKAGE LibClasses * VLC; | extern DLL_LINKAGE LibClasses * VLC; | ||||||
|   | |||||||
| @@ -22,22 +22,10 @@ | |||||||
| #include "CMapGenerator.h" | #include "CMapGenerator.h" | ||||||
| #include "../CRandomGenerator.h" | #include "../CRandomGenerator.h" | ||||||
| #include "Functions.h" | #include "Functions.h" | ||||||
|  | #include "../mapping/CMapEditManager.h" | ||||||
|  |  | ||||||
| void ObstaclePlacer::process() | void ObstacleProxy::collectPossibleObstacles(const Terrain & terrain) | ||||||
| { | { | ||||||
| 	auto * manager = zone.getModificator<ObjectManager>(); |  | ||||||
| 	if(!manager) |  | ||||||
| 		return; |  | ||||||
| 	 |  | ||||||
| 	auto * riverManager = zone.getModificator<RiverPlacer>(); |  | ||||||
| 	 |  | ||||||
| 	typedef std::vector<std::shared_ptr<const ObjectTemplate>> ObstacleVector; |  | ||||||
| 	//obstacleVector possibleObstacles; |  | ||||||
| 	 |  | ||||||
| 	std::map<int, ObstacleVector> obstaclesBySize; |  | ||||||
| 	typedef std::pair<int, ObstacleVector> ObstaclePair; |  | ||||||
| 	std::vector<ObstaclePair> possibleObstacles; |  | ||||||
| 	 |  | ||||||
| 	//get all possible obstacles for this terrain | 	//get all possible obstacles for this terrain | ||||||
| 	for(auto primaryID : VLC->objtypeh->knownObjects()) | 	for(auto primaryID : VLC->objtypeh->knownObjects()) | ||||||
| 	{ | 	{ | ||||||
| @@ -48,7 +36,7 @@ void ObstaclePlacer::process() | |||||||
| 			{ | 			{ | ||||||
| 				for(auto temp : handler->getTemplates()) | 				for(auto temp : handler->getTemplates()) | ||||||
| 				{ | 				{ | ||||||
| 					if(temp->canBePlacedAt(zone.getTerrainType()) && temp->getBlockMapOffset().valid()) | 					if(temp->canBePlacedAt(terrain) && temp->getBlockMapOffset().valid()) | ||||||
| 						obstaclesBySize[temp->getBlockedOffsets().size()].push_back(temp); | 						obstaclesBySize[temp->getBlockedOffsets().size()].push_back(temp); | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| @@ -62,122 +50,172 @@ void ObstaclePlacer::process() | |||||||
| 	{ | 	{ | ||||||
| 		return p1.first > p2.first; //bigger obstacles first | 		return p1.first > p2.first; //bigger obstacles first | ||||||
| 	}); | 	}); | ||||||
| 	 | } | ||||||
| 	auto blockedArea = zone.area().getSubarea([this](const int3 & t) |  | ||||||
|  | int ObstacleProxy::getWeightedObjects(const int3 & tile, const CMap * map, CRandomGenerator & rand, std::list<rmg::Object> & allObjects, std::vector<std::pair<rmg::Object*, int3>> & weightedObjects) | ||||||
|  | { | ||||||
|  | 	int maxWeight = std::numeric_limits<int>::min(); | ||||||
|  | 	for(int i = 0; i < possibleObstacles.size(); ++i) | ||||||
| 	{ | 	{ | ||||||
| 		return map.shouldBeBlocked(t); | 		if(!possibleObstacles[i].first) | ||||||
| 	}); | 			continue; | ||||||
| 	blockedArea.subtract(zone.areaUsed()); |  | ||||||
| 	zone.areaPossible().subtract(blockedArea); | 		auto shuffledObstacles = possibleObstacles[i].second; | ||||||
| 	 | 		RandomGeneratorUtil::randomShuffle(shuffledObstacles, rand); | ||||||
| 	 |  | ||||||
| 	auto prohibitedArea = zone.freePaths() + zone.areaUsed() + manager->getVisitableArea(); | 		for(auto temp : shuffledObstacles) | ||||||
| 	 | 		{ | ||||||
|  | 			auto handler = VLC->objtypeh->getHandlerFor(temp->id, temp->subid); | ||||||
|  | 			auto obj = handler->create(temp); | ||||||
|  | 			allObjects.emplace_back(*obj); | ||||||
|  | 			rmg::Object * rmgObject = &allObjects.back(); | ||||||
|  | 			for(auto & offset : obj->getBlockedOffsets()) | ||||||
|  | 			{ | ||||||
|  | 				rmgObject->setPosition(tile - offset); | ||||||
|  | 				if(!map->isInTheMap(rmgObject->getPosition())) | ||||||
|  | 					continue; | ||||||
|  |  | ||||||
|  | 				if(!rmgObject->getArea().getSubarea([map](const int3 & t) | ||||||
|  | 				{ | ||||||
|  | 					return !map->isInTheMap(t); | ||||||
|  | 				}).empty()) | ||||||
|  | 					continue; | ||||||
|  |  | ||||||
|  | 				if(isProhibited(rmgObject->getArea())) | ||||||
|  | 					continue; | ||||||
|  |  | ||||||
|  | 				int coverageBlocked = 0; | ||||||
|  | 				int coveragePossible = 0; | ||||||
|  | 				//do not use area intersection in optimization purposes | ||||||
|  | 				for(auto & t : rmgObject->getArea().getTilesVector()) | ||||||
|  | 				{ | ||||||
|  | 					auto coverage = verifyCoverage(t); | ||||||
|  | 					if(coverage.first) | ||||||
|  | 						++coverageBlocked; | ||||||
|  | 					if(coverage.second) | ||||||
|  | 						++coveragePossible; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				int coverageOverlap = possibleObstacles[i].first - coverageBlocked - coveragePossible; | ||||||
|  | 				int weight = possibleObstacles[i].first + coverageBlocked - coverageOverlap * possibleObstacles[i].first; | ||||||
|  | 				assert(coverageOverlap >= 0); | ||||||
|  |  | ||||||
|  | 				if(weight > maxWeight) | ||||||
|  | 				{ | ||||||
|  | 					weightedObjects.clear(); | ||||||
|  | 					maxWeight = weight; | ||||||
|  | 					weightedObjects.emplace_back(rmgObject, rmgObject->getPosition()); | ||||||
|  | 					if(weight > 0) | ||||||
|  | 						break; | ||||||
|  | 				} | ||||||
|  | 				else if(weight == maxWeight) | ||||||
|  | 					weightedObjects.emplace_back(rmgObject, rmgObject->getPosition()); | ||||||
|  |  | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if(maxWeight > 0) | ||||||
|  | 			break; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return maxWeight; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ObstacleProxy::placeObstacles(CMap * map, CRandomGenerator & rand) | ||||||
|  | { | ||||||
| 	//reverse order, since obstacles begin in bottom-right corner, while the map coordinates begin in top-left | 	//reverse order, since obstacles begin in bottom-right corner, while the map coordinates begin in top-left | ||||||
| 	auto blockedTiles = blockedArea.getTilesVector(); | 	auto blockedTiles = blockedArea.getTilesVector(); | ||||||
| 	int tilePos = 0; | 	int tilePos = 0; | ||||||
|  | 	std::set<CGObjectInstance*> objs; | ||||||
|  |  | ||||||
| 	while(!blockedArea.empty() && tilePos < blockedArea.getTilesVector().size()) | 	while(!blockedArea.empty() && tilePos < blockedArea.getTilesVector().size()) | ||||||
| 	{ | 	{ | ||||||
| 		auto tile = blockedArea.getTilesVector()[tilePos]; | 		auto tile = blockedArea.getTilesVector()[tilePos]; | ||||||
| 		 |  | ||||||
| 		std::list<rmg::Object> allObjects; | 		std::list<rmg::Object> allObjects; | ||||||
| 		std::vector<std::pair<rmg::Object*, int3>> weightedObjects; //obj + position | 		std::vector<std::pair<rmg::Object*, int3>> weightedObjects; | ||||||
| 		int maxWeight = std::numeric_limits<int>::min(); | 		int maxWeight = getWeightedObjects(tile, map, rand, allObjects, weightedObjects); | ||||||
| 		for(int i = 0; i < possibleObstacles.size(); ++i) |  | ||||||
| 		{ |  | ||||||
| 			if(!possibleObstacles[i].first) |  | ||||||
| 				continue; |  | ||||||
| 			 |  | ||||||
| 			auto shuffledObstacles = possibleObstacles[i].second; |  | ||||||
| 			RandomGeneratorUtil::randomShuffle(shuffledObstacles, generator.rand); |  | ||||||
| 			 |  | ||||||
| 			for(auto & temp : shuffledObstacles) |  | ||||||
| 			{ |  | ||||||
| 				auto handler = VLC->objtypeh->getHandlerFor(temp->id, temp->subid); |  | ||||||
| 				auto obj = handler->create(temp); |  | ||||||
| 				allObjects.emplace_back(*obj); |  | ||||||
| 				rmg::Object * rmgObject = &allObjects.back(); |  | ||||||
| 				for(auto & offset : obj->getBlockedOffsets()) |  | ||||||
| 				{ |  | ||||||
| 					rmgObject->setPosition(tile - offset); |  | ||||||
| 					if(!map.isOnMap(rmgObject->getPosition())) |  | ||||||
| 						continue; |  | ||||||
| 					 |  | ||||||
| 					if(!rmgObject->getArea().getSubarea([this](const int3 & t) |  | ||||||
| 					{ |  | ||||||
| 						return !map.isOnMap(t); |  | ||||||
| 					}).empty()) |  | ||||||
| 						continue; |  | ||||||
| 					 |  | ||||||
| 					if(prohibitedArea.overlap(rmgObject->getArea())) |  | ||||||
| 						continue; |  | ||||||
| 					 |  | ||||||
| 					if(!zone.area().contains(rmgObject->getArea())) |  | ||||||
| 						continue; |  | ||||||
| 					 |  | ||||||
| 					int coverageBlocked = 0; |  | ||||||
| 					int coveragePossible = 0; |  | ||||||
| 					//do not use area intersection in optimization purposes |  | ||||||
| 					for(auto & t : rmgObject->getArea().getTilesVector()) |  | ||||||
| 					{ |  | ||||||
| 						if(map.shouldBeBlocked(t)) |  | ||||||
| 							++coverageBlocked; |  | ||||||
| 						if(zone.areaPossible().contains(t)) |  | ||||||
| 							++coveragePossible; |  | ||||||
| 					} |  | ||||||
| 					 |  | ||||||
| 					int coverageOverlap = possibleObstacles[i].first - coverageBlocked - coveragePossible; |  | ||||||
| 					int weight = possibleObstacles[i].first + coverageBlocked - coverageOverlap * possibleObstacles[i].first; |  | ||||||
| 					assert(coverageOverlap >= 0); |  | ||||||
| 					 |  | ||||||
| 					if(weight > maxWeight) |  | ||||||
| 					{ |  | ||||||
| 						weightedObjects.clear(); |  | ||||||
| 						maxWeight = weight; |  | ||||||
| 						weightedObjects.emplace_back(rmgObject, rmgObject->getPosition()); |  | ||||||
| 						if(weight > 0) |  | ||||||
| 							break; |  | ||||||
| 					} |  | ||||||
| 					else if(weight == maxWeight) |  | ||||||
| 						weightedObjects.emplace_back(rmgObject, rmgObject->getPosition()); |  | ||||||
| 					 |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			 |  | ||||||
| 			if(maxWeight > 0) |  | ||||||
| 				break; |  | ||||||
| 		} |  | ||||||
| 		 |  | ||||||
| 		if(weightedObjects.empty()) | 		if(weightedObjects.empty()) | ||||||
| 		{ | 		{ | ||||||
| 			tilePos += 1; | 			tilePos += 1; | ||||||
| 			continue; | 			continue; | ||||||
| 		} | 		} | ||||||
| 		 |  | ||||||
| 		auto objIter = RandomGeneratorUtil::nextItem(weightedObjects, generator.rand); | 		auto objIter = RandomGeneratorUtil::nextItem(weightedObjects, rand); | ||||||
| 		objIter->first->setPosition(objIter->second); | 		objIter->first->setPosition(objIter->second); | ||||||
| 		manager->placeObject(*objIter->first, false, false); | 		placeObject(*objIter->first, objs); | ||||||
|  |  | ||||||
| 		blockedArea.subtract(objIter->first->getArea()); | 		blockedArea.subtract(objIter->first->getArea()); | ||||||
| 		tilePos = 0; | 		tilePos = 0; | ||||||
| 		 |  | ||||||
| 		//river processing | 		postProcess(*objIter->first); | ||||||
| 		if(riverManager) |  | ||||||
| 		{ |  | ||||||
| 			if(objIter->first->instances().front()->object().typeName == "mountain") |  | ||||||
| 				riverManager->riverSource().unite(objIter->first->getArea()); |  | ||||||
| 			if(objIter->first->instances().front()->object().typeName == "lake") |  | ||||||
| 				riverManager->riverSink().unite(objIter->first->getArea()); |  | ||||||
| 		} |  | ||||||
| 		 |  | ||||||
| 		if(maxWeight < 0) | 		if(maxWeight < 0) | ||||||
| 			logGlobal->warn("Placed obstacle with negative weight at %s", objIter->second.toString()); | 			logGlobal->warn("Placed obstacle with negative weight at %s", objIter->second.toString()); | ||||||
| 		 |  | ||||||
| 		for(auto & o : allObjects) | 		for(auto & o : allObjects) | ||||||
| 		{ | 		{ | ||||||
| 			if(&o != objIter->first) | 			if(&o != objIter->first) | ||||||
| 				o.clear(); | 				o.clear(); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	finalInsertion(map->getEditManager(), objs); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ObstacleProxy::finalInsertion(CMapEditManager * manager, std::set<CGObjectInstance*> & instances) | ||||||
|  | { | ||||||
|  | 	manager->insertObjects(instances); //insert as one operation - for undo purposes | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::pair<bool, bool> ObstacleProxy::verifyCoverage(const int3 & t) const | ||||||
|  | { | ||||||
|  | 	std::pair<bool, bool> result(false, false); | ||||||
|  | 	if(blockedArea.contains(t)) | ||||||
|  | 		result.first = true; | ||||||
|  | 	return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ObstacleProxy::placeObject(rmg::Object & object, std::set<CGObjectInstance*> & instances) | ||||||
|  | { | ||||||
|  | 	for (auto * instance : object.instances()) | ||||||
|  | 	{ | ||||||
|  | 		instances.insert(&instance->object()); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ObstacleProxy::postProcess(const rmg::Object & object) | ||||||
|  | { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool ObstacleProxy::isProhibited(const rmg::Area & objArea) const | ||||||
|  | { | ||||||
|  | 	return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | void ObstaclePlacer::process() | ||||||
|  | { | ||||||
|  | 	manager = zone.getModificator<ObjectManager>(); | ||||||
|  | 	if(!manager) | ||||||
|  | 		return; | ||||||
|  | 	 | ||||||
|  | 	riverManager = zone.getModificator<RiverPlacer>(); | ||||||
|  | 	 | ||||||
|  | 	collectPossibleObstacles(zone.getTerrainType()); | ||||||
|  | 	 | ||||||
|  | 	blockedArea = zone.area().getSubarea([this](const int3 & t) | ||||||
|  | 	{ | ||||||
|  | 		return map.shouldBeBlocked(t); | ||||||
|  | 	}); | ||||||
|  | 	blockedArea.subtract(zone.areaUsed()); | ||||||
|  | 	zone.areaPossible().subtract(blockedArea); | ||||||
|  | 	 | ||||||
|  | 	prohibitedArea = zone.freePaths() + zone.areaUsed() + manager->getVisitableArea(); | ||||||
|  | 		 | ||||||
|  | 	placeObstacles(&map.map(), generator.rand); | ||||||
| } | } | ||||||
|  |  | ||||||
| void ObstaclePlacer::init() | void ObstaclePlacer::init() | ||||||
| @@ -189,3 +227,45 @@ void ObstaclePlacer::init() | |||||||
| 	DEPENDENCY(RoadPlacer); | 	DEPENDENCY(RoadPlacer); | ||||||
| 	DEPENDENCY_ALL(RockPlacer); | 	DEPENDENCY_ALL(RockPlacer); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | std::pair<bool, bool> ObstaclePlacer::verifyCoverage(const int3 & t) const | ||||||
|  | { | ||||||
|  | 	std::pair<bool, bool> result(false, false); | ||||||
|  | 	if(map.shouldBeBlocked(t)) | ||||||
|  | 		result.first = true; | ||||||
|  | 	if(zone.areaPossible().contains(t)) | ||||||
|  | 		result.second = true; | ||||||
|  | 	return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ObstaclePlacer::placeObject(rmg::Object & object, std::set<CGObjectInstance*> &) | ||||||
|  | { | ||||||
|  | 	manager->placeObject(object, false, false); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ObstaclePlacer::postProcess(const rmg::Object & object) | ||||||
|  | { | ||||||
|  | 	//river processing | ||||||
|  | 	if(riverManager) | ||||||
|  | 	{ | ||||||
|  | 		if(object.instances().front()->object().typeName == "mountain") | ||||||
|  | 			riverManager->riverSource().unite(object.getArea()); | ||||||
|  | 		if(object.instances().front()->object().typeName == "lake") | ||||||
|  | 			riverManager->riverSink().unite(object.getArea()); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool ObstaclePlacer::isProhibited(const rmg::Area & objArea) const | ||||||
|  | { | ||||||
|  | 	if(prohibitedArea.overlap(objArea)) | ||||||
|  | 		return true; | ||||||
|  | 	  | ||||||
|  | 	if(!zone.area().contains(objArea)) | ||||||
|  | 		return true; | ||||||
|  | 	 | ||||||
|  | 	return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ObstaclePlacer::finalInsertion(CMapEditManager *, std::set<CGObjectInstance*> &) | ||||||
|  | { | ||||||
|  | } | ||||||
|   | |||||||
| @@ -11,11 +11,61 @@ | |||||||
| #pragma once | #pragma once | ||||||
| #include "Zone.h" | #include "Zone.h" | ||||||
|  |  | ||||||
| class ObstaclePlacer: public Modificator | class CMap; | ||||||
|  | class CMapEditManager; | ||||||
|  | class RiverPlacer; | ||||||
|  | class ObjectManager; | ||||||
|  | class DLL_LINKAGE ObstacleProxy | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	ObstacleProxy() = default; | ||||||
|  | 	virtual ~ObstacleProxy() = default; | ||||||
|  |  | ||||||
|  | 	rmg::Area blockedArea; | ||||||
|  |  | ||||||
|  | 	void collectPossibleObstacles(const Terrain & terrain); | ||||||
|  |  | ||||||
|  | 	void placeObstacles(CMap * map, CRandomGenerator & rand); | ||||||
|  |  | ||||||
|  | 	virtual std::pair<bool, bool> verifyCoverage(const int3 & t) const; | ||||||
|  |  | ||||||
|  | 	virtual void placeObject(rmg::Object & object, std::set<CGObjectInstance*> & instances); | ||||||
|  |  | ||||||
|  | 	virtual void postProcess(const rmg::Object & object); | ||||||
|  |  | ||||||
|  | 	virtual bool isProhibited(const rmg::Area & objArea) const; | ||||||
|  | 	 | ||||||
|  | 	virtual void finalInsertion(CMapEditManager * manager, std::set<CGObjectInstance*> & instances); | ||||||
|  |  | ||||||
|  | protected: | ||||||
|  | 	int getWeightedObjects(const int3 & tile, const CMap * map, CRandomGenerator & rand, std::list<rmg::Object> & allObjects, std::vector<std::pair<rmg::Object*, int3>> & weightedObjects); | ||||||
|  |  | ||||||
|  | 	typedef std::vector<std::shared_ptr<const ObjectTemplate>> ObstacleVector; | ||||||
|  | 	std::map<int, ObstacleVector> obstaclesBySize; | ||||||
|  | 	typedef std::pair<int, ObstacleVector> ObstaclePair; | ||||||
|  | 	std::vector<ObstaclePair> possibleObstacles; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class ObstaclePlacer: public Modificator, public ObstacleProxy | ||||||
| { | { | ||||||
| public: | public: | ||||||
| 	MODIFICATOR(ObstaclePlacer); | 	MODIFICATOR(ObstaclePlacer); | ||||||
| 	 | 	 | ||||||
| 	void process() override; | 	void process() override; | ||||||
| 	void init() override; | 	void init() override; | ||||||
|  | 	 | ||||||
|  | 	std::pair<bool, bool> verifyCoverage(const int3 & t) const override; | ||||||
|  | 	 | ||||||
|  | 	void placeObject(rmg::Object & object, std::set<CGObjectInstance*> & instances) override; | ||||||
|  | 	 | ||||||
|  | 	void postProcess(const rmg::Object & object) override; | ||||||
|  | 	 | ||||||
|  | 	bool isProhibited(const rmg::Area & objArea) const override; | ||||||
|  | 	 | ||||||
|  | 	void finalInsertion(CMapEditManager * manager, std::set<CGObjectInstance*> & instances) override; | ||||||
|  | 	 | ||||||
|  | private: | ||||||
|  | 	rmg::Area prohibitedArea; | ||||||
|  | 	RiverPlacer * riverManager; | ||||||
|  | 	ObjectManager * manager; | ||||||
| }; | }; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user