mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Merge remote-tracking branch 'vcmi/beta' into develop
This commit is contained in:
		| @@ -84,7 +84,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose() const | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if(treasureSourcesCount < 5) | ||||
| 			if(treasureSourcesCount < 5 && (town->garrisonHero || town->getUpperArmy()->getArmyStrength() < 10000)) | ||||
| 				continue; | ||||
|  | ||||
| 			if(cb->getHeroesInfo().size() < cb->getTownsInfo().size() + 1 | ||||
|   | ||||
							
								
								
									
										46
									
								
								ChangeLog.md
									
									
									
									
									
								
							
							
						
						
									
										46
									
								
								ChangeLog.md
									
									
									
									
									
								
							| @@ -1,10 +1,50 @@ | ||||
| # 1.3.0 -> 1.3.1 | ||||
| (unreleased) | ||||
|  | ||||
| * Fixed crash on starting game with outdated mods | ||||
| * Fixed Android mod manager crash  | ||||
| ### GENERAL: | ||||
| * Fixed framerate drops on hero movement with active hota mod | ||||
| * Fade-out animations will now be skipped when instant hero movement speed is used | ||||
| * Restarting loaded campaing scenario will now correctly reapply starting bonus | ||||
| * Reverted FPS limit on mobile systems back to 60 fps | ||||
| * Fixed loading of translations for maps and campaigns | ||||
| * Fixed loading of preconfigured starting army for heroes with preconfigured spells | ||||
| * Background battlefield obstacles will now appear below creatures | ||||
| * it is now possible to load save game located inside mod | ||||
| * Added option to configure reserved screen area in Launcher on iOS | ||||
| * Fixed border scrolling when game window is maximized | ||||
|  | ||||
| ### AI PLAYER: | ||||
| * BattleAI: Improved performance of AI spell selection | ||||
| * NKAI: Fixed freeze on attempt to exchange army between garrisoned and visiting hero | ||||
| * NKAI: Fixed town threat calculation | ||||
| * NKAI: Fixed recruitment of new heroes | ||||
| * VCAI: Added workaround to avoid freeze on attempting to reach unreachable location | ||||
| * VCAI: Fixed spellcasting by Archangels | ||||
|  | ||||
| ### RANDOM MAP GENERATOR: | ||||
| * Fixed placement of roads inside rock in underground | ||||
| * Fixed placement of shifted creature animations from HotA | ||||
| * Fixed placement of treasures at the boundary of wide connections | ||||
| * Added more potential locations for quest artifacts in zone | ||||
|  | ||||
| ### STABILITY: | ||||
| * When starting client without H3 data game will now show message instead of silently crashing | ||||
| * When starting invalid map in campaign, game will now show message instead of silently crashing | ||||
| * Blocked loading of saves made with different set of mods to prevent crashes | ||||
| * Fixed crash on starting game with outdated mods | ||||
| * Fixed crash on attempt to sacrifice all your artifacts in Altar of Sacrifice | ||||
| * Fixed crash on leveling up after winning battle as defender | ||||
| * Fixed possible crash on end of battle opening sound | ||||
| * Fixed crash on accepting battle result after winning battle as defender | ||||
| * Fixed possible crash on casting spell in battle by AI | ||||
| * Fixed multiple possible crashes on managing mods on Android | ||||
| * Fixed multiple possible crashes on importing data on Android | ||||
| * Fixed crash on refusing rewards from town building | ||||
| * Fixed possible crash on threat evaluation by NKAI | ||||
| * Fixed crash on using haptic feedback on some Android systems | ||||
| * Fixed crash on right-clicking flags area in RMG setup mode | ||||
| * Fixed crash on opening Blacksmith window and Build Structure dialogs in some localizations | ||||
| * Fixed possible crash on displaying animated main menu | ||||
| * Fixed crash on recruiting hero in town located on the border of map | ||||
|  | ||||
| # 1.2.1 -> 1.3.0 | ||||
|  | ||||
|   | ||||
							
								
								
									
										2
									
								
								Global.h
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Global.h
									
									
									
									
									
								
							| @@ -118,6 +118,7 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size."); | ||||
| #include <memory> | ||||
| #include <mutex> | ||||
| #include <numeric> | ||||
| #include <optional> | ||||
| #include <queue> | ||||
| #include <random> | ||||
| #include <set> | ||||
| @@ -126,6 +127,7 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size."); | ||||
| #include <unordered_map> | ||||
| #include <unordered_set> | ||||
| #include <utility> | ||||
| #include <variant> | ||||
| #include <vector> | ||||
|  | ||||
| //The only available version is 3, as of Boost 1.50 | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| [](https://github.com/vcmi/vcmi/actions/workflows/github.yml) | ||||
| [](https://github.com/vcmi/vcmi/releases/tag/1.3.1) | ||||
| [](https://github.com/vcmi/vcmi/releases/tag/1.3.0) | ||||
| [](https://github.com/vcmi/vcmi/releases/tag/1.3.1) | ||||
| [](https://github.com/vcmi/vcmi/releases) | ||||
| # VCMI Project | ||||
| VCMI is work-in-progress attempt to recreate engine for Heroes III, giving it new and extended possibilities. | ||||
|   | ||||
| @@ -52,6 +52,7 @@ | ||||
| #include <boost/uuid/uuid.hpp> | ||||
| #include <boost/uuid/uuid_io.hpp> | ||||
| #include <boost/uuid/uuid_generators.hpp> | ||||
| #include <boost/asio.hpp> | ||||
| #include "../lib/serializer/Cast.h" | ||||
| #include "LobbyClientNetPackVisitors.h" | ||||
|  | ||||
| @@ -86,6 +87,8 @@ template<typename T> class CApplyOnLobby : public CBaseForLobbyApply | ||||
| public: | ||||
| 	bool applyOnLobbyHandler(CServerHandler * handler, void * pack) const override | ||||
| 	{ | ||||
| 		boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim); | ||||
|  | ||||
| 		T * ptr = static_cast<T *>(pack); | ||||
| 		ApplyOnLobbyHandlerNetPackVisitor visitor(*handler); | ||||
|  | ||||
|   | ||||
| @@ -169,10 +169,10 @@ void AdventureMapInterface::tick(uint32_t msPassed) | ||||
| void AdventureMapInterface::handleMapScrollingUpdate(uint32_t timePassed) | ||||
| { | ||||
| 	/// Width of window border, in pixels, that triggers map scrolling | ||||
| 	static constexpr uint32_t borderScrollWidth = 15; | ||||
| 	static constexpr int32_t borderScrollWidth = 15; | ||||
|  | ||||
| 	uint32_t scrollSpeedPixels = settings["adventure"]["scrollSpeedPixels"].Float(); | ||||
| 	uint32_t scrollDistance = scrollSpeedPixels * timePassed / 1000; | ||||
| 	int32_t scrollSpeedPixels = settings["adventure"]["scrollSpeedPixels"].Float(); | ||||
| 	int32_t scrollDistance = scrollSpeedPixels * timePassed / 1000; | ||||
|  | ||||
| 	Point cursorPosition = GH.getCursorPosition(); | ||||
| 	Point scrollDirection; | ||||
|   | ||||
| @@ -52,7 +52,7 @@ | ||||
| 	</categories> | ||||
| 	<releases> | ||||
| 		<release version="1.4.0" date="2023-12-22" type="development" /> | ||||
| 		<release version="1.3.1" date="2023-08-18" type="development" /> | ||||
| 		<release version="1.3.1" date="2023-08-18" /> | ||||
| 		<release version="1.3.0" date="2023-08-04" /> | ||||
| 		<release version="1.2.1" date="2023-04-28" /> | ||||
| 		<release version="1.2.0" date="2023-04-14" /> | ||||
|   | ||||
| @@ -1731,6 +1731,10 @@ SpellID CBattleInfoCallback::getRandomCastedSpell(CRandomGenerator & rand,const | ||||
| 	TConstBonusListPtr bl = caster->getBonuses(Selector::type()(BonusType::SPELLCASTER)); | ||||
| 	if (!bl->size()) | ||||
| 		return SpellID::NONE; | ||||
|  | ||||
| 	if(bl->size() == 1) | ||||
| 		return SpellID(bl->front()->subtype); | ||||
|  | ||||
| 	int totalWeight = 0; | ||||
| 	for(const auto & b : *bl) | ||||
| 	{ | ||||
|   | ||||
| @@ -489,23 +489,49 @@ void CModHandler::afterLoad(bool onlyEssential) | ||||
|  | ||||
| } | ||||
|  | ||||
| void CModHandler::trySetActiveMods(const std::map<TModID, CModVersion> & modList) | ||||
| void CModHandler::trySetActiveMods(std::vector<TModID> saveActiveMods, const std::map<TModID, CModVersion> & modList) | ||||
| { | ||||
| 	std::vector<TModID> newActiveMods; | ||||
|  | ||||
| 	ModIncompatibility::ModList missingMods; | ||||
|  | ||||
| 	for(const auto & mod : modList) | ||||
| 	for(const auto & m : activeMods) | ||||
| 	{ | ||||
| 		auto m = mod.first; | ||||
| 		auto mver = mod.second; | ||||
| 		if (vstd::contains(saveActiveMods, m)) | ||||
| 			continue; | ||||
|  | ||||
| 		if(allMods.count(m) && (allMods[m].version.isNull() || mver.isNull() || allMods[m].version.compatible(mver))) | ||||
| 			allMods[m].setEnabled(true); | ||||
| 		else | ||||
| 		auto & modInfo = allMods.at(m); | ||||
| 		if(modInfo.checkModGameplayAffecting()) | ||||
| 			missingMods.emplace_back(m, modInfo.version.toString()); | ||||
| 	} | ||||
|  | ||||
| 	for(const auto & m : saveActiveMods) | ||||
| 	{ | ||||
| 		const CModVersion & mver = modList.at(m); | ||||
|  | ||||
| 		if (allMods.count(m) == 0) | ||||
| 		{ | ||||
| 			missingMods.emplace_back(m, mver.toString()); | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		auto & modInfo = allMods.at(m); | ||||
|  | ||||
| 		bool modAffectsGameplay = modInfo.checkModGameplayAffecting(); | ||||
| 		bool modVersionCompatible = modInfo.version.isNull() || mver.isNull() || modInfo.version.compatible(mver); | ||||
| 		bool modEnabledLocally = vstd::contains(activeMods, m); | ||||
| 		bool modCanBeEnabled = modEnabledLocally && modVersionCompatible; | ||||
|  | ||||
| 		allMods[m].setEnabled(modCanBeEnabled); | ||||
|  | ||||
| 		if (modCanBeEnabled) | ||||
| 			newActiveMods.push_back(m); | ||||
|  | ||||
| 		if (!modCanBeEnabled && modAffectsGameplay) | ||||
| 			missingMods.emplace_back(m, mver.toString()); | ||||
| 	} | ||||
|  | ||||
| 	if(!missingMods.empty()) | ||||
| 		throw ModIncompatibility(std::move(missingMods)); | ||||
| 	std::swap(activeMods, newActiveMods); | ||||
| } | ||||
|  | ||||
| CIdentifierStorage & CModHandler::getIdentifiers() | ||||
|   | ||||
| @@ -52,7 +52,7 @@ class DLL_LINKAGE CModHandler : boost::noncopyable | ||||
| 	CModVersion getModVersion(TModID modName) const; | ||||
|  | ||||
| 	/// Attempt to set active mods according to provided list of mods from save, throws on failure | ||||
| 	void trySetActiveMods(const std::map<TModID, CModVersion> & modList); | ||||
| 	void trySetActiveMods(std::vector<TModID> saveActiveMods, const std::map<TModID, CModVersion> & modList); | ||||
|  | ||||
| 	std::unique_ptr<CIdentifierStorage> identifiers; | ||||
|  | ||||
| @@ -100,16 +100,14 @@ public: | ||||
| 		else | ||||
| 		{ | ||||
| 			loadMods(); | ||||
| 			std::vector<TModID> newActiveMods; | ||||
| 			std::vector<TModID> saveActiveMods; | ||||
| 			std::map<TModID, CModVersion> modVersions; | ||||
| 			h & newActiveMods; | ||||
| 			h & saveActiveMods; | ||||
|  | ||||
| 			for(const auto & m : newActiveMods) | ||||
| 			for(const auto & m : saveActiveMods) | ||||
| 				h & modVersions[m]; | ||||
|  | ||||
| 			trySetActiveMods(modVersions); | ||||
|  | ||||
| 			std::swap(activeMods, newActiveMods); | ||||
| 			trySetActiveMods(saveActiveMods, modVersions); | ||||
| 		} | ||||
|  | ||||
| 		h & identifiers; | ||||
|   | ||||
| @@ -12,6 +12,7 @@ | ||||
|  | ||||
| #include "../CGeneralTextHandler.h" | ||||
| #include "../VCMI_Lib.h" | ||||
| #include "../filesystem/Filesystem.h" | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_BEGIN | ||||
|  | ||||
| @@ -128,6 +129,48 @@ void CModInfo::loadLocalData(const JsonNode & data) | ||||
| 		validation = validated ? PASSED : FAILED; | ||||
| } | ||||
|  | ||||
| bool CModInfo::checkModGameplayAffecting() const | ||||
| { | ||||
| 	if (modGameplayAffecting.has_value()) | ||||
| 		return *modGameplayAffecting; | ||||
|  | ||||
| 	static const std::vector<std::string> keysToTest = { | ||||
| 		"heroClasses", | ||||
| 		"artifacts", | ||||
| 		"creatures", | ||||
| 		"factions", | ||||
| 		"objects", | ||||
| 		"heroes", | ||||
| 		"spells", | ||||
| 		"skills", | ||||
| 		"templates", | ||||
| 		"scripts", | ||||
| 		"battlefields", | ||||
| 		"terrains", | ||||
| 		"rivers", | ||||
| 		"roads", | ||||
| 		"obstacles" | ||||
| 	}; | ||||
|  | ||||
| 	ResourceID modFileResource(CModInfo::getModFile(identifier)); | ||||
|  | ||||
| 	if(CResourceHandler::get("initial")->existsResource(modFileResource)) | ||||
| 	{ | ||||
| 		const JsonNode modConfig(modFileResource); | ||||
|  | ||||
| 		for(const auto & key : keysToTest) | ||||
| 		{ | ||||
| 			if (!modConfig[key].isNull()) | ||||
| 			{ | ||||
| 				modGameplayAffecting = true; | ||||
| 				return *modGameplayAffecting; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	modGameplayAffecting = false; | ||||
| 	return *modGameplayAffecting; | ||||
| } | ||||
|  | ||||
| bool CModInfo::isEnabled() const | ||||
| { | ||||
| 	return implicitlyEnabled && explicitlyEnabled; | ||||
|   | ||||
| @@ -18,6 +18,10 @@ using TModID = std::string; | ||||
|  | ||||
| class DLL_LINKAGE CModInfo | ||||
| { | ||||
| 	/// cached result of checkModGameplayAffecting() call | ||||
| 	/// Do not serialize - depends on local mod version, not server/save mod version | ||||
| 	mutable std::optional<bool> modGameplayAffecting; | ||||
|  | ||||
| public: | ||||
| 	enum EValidationStatus | ||||
| 	{ | ||||
| @@ -67,6 +71,9 @@ public: | ||||
| 	static std::string getModDir(const std::string & name); | ||||
| 	static std::string getModFile(const std::string & name); | ||||
|  | ||||
| 	/// return true if this mod can affect gameplay, e.g. adds or modifies any game objects | ||||
| 	bool checkModGameplayAffecting() const; | ||||
|  | ||||
| private: | ||||
| 	/// true if mod is enabled by user, e.g. in Launcher UI | ||||
| 	bool explicitlyEnabled; | ||||
|   | ||||
| @@ -413,23 +413,41 @@ bool ObjectManager::createRequiredObjects() | ||||
| 		 | ||||
| 		zone.connectPath(path); | ||||
| 		placeObject(rmgObject, guarded, true); | ||||
| 		 | ||||
| 		for(const auto & nearby : nearbyObjects) | ||||
| 	} | ||||
|  | ||||
| 	for(const auto & nearby : nearbyObjects) | ||||
| 	{ | ||||
| 		auto * targetObject = nearby.nearbyTarget; | ||||
| 		if (!targetObject || !targetObject->appearance) | ||||
| 		{ | ||||
| 			if(nearby.nearbyTarget != objInfo.obj) | ||||
| 				continue; | ||||
| 			 | ||||
| 			rmg::Object rmgNearObject(*nearby.obj); | ||||
| 			rmg::Area possibleArea(rmgObject.instances().front()->getBlockedArea().getBorderOutside()); | ||||
| 			possibleArea.intersect(zone.areaPossible()); | ||||
| 			if(possibleArea.empty()) | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		rmg::Object rmgNearObject(*nearby.obj); | ||||
| 		rmg::Area possibleArea(rmg::Area(targetObject->getBlockedPos()).getBorderOutside()); | ||||
| 		possibleArea.intersect(zone.areaPossible()); | ||||
| 		if(possibleArea.empty()) | ||||
| 		{ | ||||
| 			rmgNearObject.clear(); | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		rmgNearObject.setPosition(*RandomGeneratorUtil::nextItem(possibleArea.getTiles(), zone.getRand())); | ||||
| 		placeObject(rmgNearObject, false, false); | ||||
| 		auto path = zone.searchPath(rmgNearObject.getVisitablePosition(), false); | ||||
| 		if (path.valid()) | ||||
| 		{ | ||||
| 			zone.connectPath(path); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			for (auto* instance : rmgNearObject.instances()) | ||||
| 			{ | ||||
| 				rmgNearObject.clear(); | ||||
| 				continue; | ||||
| 				logGlobal->error("Failed to connect nearby object %s at %s", | ||||
| 					instance->object().getObjectName(), instance->getPosition(true).toString()); | ||||
| 				mapProxy->removeObject(&instance->object()); | ||||
| 			} | ||||
| 			 | ||||
| 			rmgNearObject.setPosition(*RandomGeneratorUtil::nextItem(possibleArea.getTiles(), zone.getRand())); | ||||
| 			placeObject(rmgNearObject, false, false); | ||||
| 			rmgNearObject.clear(); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
|   | ||||
| @@ -77,7 +77,6 @@ | ||||
| #define COMPLAIN_RETF(txt, FORMAT) {complain(boost::str(boost::format(txt) % FORMAT)); return false;} | ||||
|  | ||||
| CondSh<bool> battleMadeAction(false); | ||||
| boost::recursive_mutex battleActionMutex; | ||||
| CondSh<BattleResult *> battleResult(nullptr); | ||||
| template <typename T> class CApplyOnGH; | ||||
|  | ||||
|   | ||||
| @@ -102,6 +102,8 @@ class CGameHandler : public IGameCallback, public CBattleInfoCallback, public En | ||||
| 	std::unique_ptr<boost::thread> battleThread; | ||||
|  | ||||
| public: | ||||
| 	boost::recursive_mutex battleActionMutex; | ||||
|  | ||||
| 	std::unique_ptr<HeroPoolProcessor> heroPool; | ||||
|  | ||||
| 	using FireShieldInfo = std::vector<std::pair<const CStack *, int64_t>>; | ||||
|   | ||||
| @@ -244,7 +244,7 @@ bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTy | ||||
| 	hr.hid = recruitedHero->subID; | ||||
| 	hr.player = player; | ||||
| 	hr.tile = recruitedHero->convertFromVisitablePos(targetPos ); | ||||
| 	if(gameHandler->getTile(hr.tile)->isWater() && !recruitedHero->boat) | ||||
| 	if(gameHandler->getTile(targetPos)->isWater() && !recruitedHero->boat) | ||||
| 	{ | ||||
| 		//Create a new boat for hero | ||||
| 		gameHandler->createObject(targetPos , Obj::BOAT, recruitedHero->getBoatType().getNum()); | ||||
|   | ||||
| @@ -25,8 +25,6 @@ | ||||
| #include "../lib/spells/ISpellMechanics.h" | ||||
| #include "../lib/serializer/Cast.h" | ||||
|  | ||||
| extern boost::recursive_mutex battleActionMutex; | ||||
|  | ||||
| void ApplyGhNetPackVisitor::visitSaveGame(SaveGame & pack) | ||||
| { | ||||
| 	gh.save(pack.fname); | ||||
| @@ -282,7 +280,7 @@ void ApplyGhNetPackVisitor::visitQueryReply(QueryReply & pack) | ||||
|  | ||||
| void ApplyGhNetPackVisitor::visitMakeAction(MakeAction & pack) | ||||
| { | ||||
| 	boost::unique_lock lock(battleActionMutex); | ||||
| 	boost::unique_lock lock(gh.battleActionMutex); | ||||
|  | ||||
| 	const BattleInfo * b = gs.curB; | ||||
| 	if(!b) | ||||
| @@ -311,7 +309,7 @@ void ApplyGhNetPackVisitor::visitMakeAction(MakeAction & pack) | ||||
|  | ||||
| void ApplyGhNetPackVisitor::visitMakeCustomAction(MakeCustomAction & pack) | ||||
| { | ||||
| 	boost::unique_lock lock(battleActionMutex); | ||||
| 	boost::unique_lock lock(gh.battleActionMutex); | ||||
|  | ||||
| 	const BattleInfo * b = gs.curB; | ||||
| 	if(!b) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user