mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Proper fix for #1712 - Building requirement tests are now fixed
- added minimize() method in logical expressions that will remove excessive elements - replaced forEach() with morph() that allows replacing types in variant
This commit is contained in:
		| @@ -1358,13 +1358,9 @@ std::string CBuildWindow::getTextForState(int state) | ||||
| 			{ | ||||
| 				return town->town->buildings.at(build)->Name(); | ||||
| 			}; | ||||
| 			/*auto toBool = [&](const BuildingID build) | ||||
| 			{ | ||||
| 				return town->hasBuilt(build); | ||||
| 			};*/ | ||||
|  | ||||
| 			ret = CGI->generaltexth->allTexts[52]; | ||||
| 			ret += "\n" + building->requirements.toString(toStr); | ||||
| 			ret += "\n" + town->genBuildingRequirements(building->bid).toString(toStr); | ||||
| 			break; | ||||
| 		} | ||||
| 	case EBuildingState::MISSING_BASE: | ||||
|   | ||||
| @@ -403,33 +403,12 @@ EBuildingState::EBuildingState CGameInfoCallback::canBuildStructure( const CGTow | ||||
| 			return EBuildingState::NO_WATER; //lack of water | ||||
| 	} | ||||
|  | ||||
| 	/// returns true if building prerequisites are fulfilled | ||||
| 	std::function<bool(BuildingID)> buildTest; | ||||
|  | ||||
| 	auto dependTest = [&](BuildingID id) -> bool | ||||
| 	auto buildTest = [&](BuildingID id) -> bool | ||||
| 	{ | ||||
| 		const CBuilding * build = t->town->buildings.at(id); | ||||
|  | ||||
| 		if (build->upgrade != BuildingID::NONE) | ||||
| 		{ | ||||
| 			if (!t->hasBuilt(build->upgrade)) | ||||
| 				return false; | ||||
|  | ||||
| 			if (!t->town->buildings.at(build->upgrade)->requirements.test(buildTest)) | ||||
| 				return false; | ||||
| 		} | ||||
|  | ||||
| 		if (!build->requirements.test(buildTest)) | ||||
| 			return false; | ||||
| 		return true; | ||||
| 		return t->hasBuilt(id); | ||||
| 	}; | ||||
|  | ||||
| 	buildTest = [&](BuildingID bid) | ||||
| 	{ | ||||
| 		return t->hasBuilt(bid) && dependTest(bid); | ||||
| 	}; | ||||
|  | ||||
| 	if (!dependTest(ID)) | ||||
| 	if (!t->genBuildingRequirements(ID).test(buildTest)) | ||||
| 		return EBuildingState::PREREQUIRES; | ||||
|  | ||||
| 	if(t->builded >= VLC->modh->settings.MAX_BUILDING_PER_TURN) | ||||
|   | ||||
| @@ -45,6 +45,11 @@ namespace LogicalExpressionDetail | ||||
|  | ||||
| 			std::vector<Variant> expressions; | ||||
|  | ||||
| 			bool operator == (const Element & other) const | ||||
| 			{ | ||||
| 				return expressions == other.expressions; | ||||
| 			} | ||||
|  | ||||
| 			template <typename Handler> | ||||
| 			void serialize(Handler & h, const int version) | ||||
| 			{ | ||||
| @@ -147,39 +152,73 @@ namespace LogicalExpressionDetail | ||||
|  | ||||
| 	/// Simple foreach visitor | ||||
| 	template <typename ContainedClass> | ||||
| 	class ForEachVisitor : public boost::static_visitor<void> | ||||
| 	class ForEachVisitor : public boost::static_visitor<typename ExpressionBase<ContainedClass>::Variant> | ||||
| 	{ | ||||
| 		typedef ExpressionBase<ContainedClass> Base; | ||||
|  | ||||
| 		std::function<void(typename Base::Value &)> visitor; | ||||
| 		std::function<typename Base::Variant(const typename Base::Value &)> visitor; | ||||
|  | ||||
| 	public: | ||||
| 		ForEachVisitor(std::function<void(typename Base::Value &)> visitor): | ||||
| 		ForEachVisitor(std::function<typename Base::Variant(const typename Base::Value &)> visitor): | ||||
| 			visitor(visitor) | ||||
| 		{} | ||||
|  | ||||
| 		//FIXME: duplicated code | ||||
| 		void operator()(typename Base::OperatorAny & element) const | ||||
| 		typename Base::Variant operator()(const typename Base::Value & element) const | ||||
| 		{ | ||||
| 			for (auto & entry : element.expressions) | ||||
| 				boost::apply_visitor(*this, entry); | ||||
| 			return visitor(element); | ||||
| 		} | ||||
|  | ||||
| 		void operator()(typename Base::OperatorAll & element) const | ||||
| 		template <typename Type> | ||||
| 		typename Base::Variant operator()(Type element) const | ||||
| 		{ | ||||
| 			for (auto & entry : element.expressions) | ||||
| 				boost::apply_visitor(*this, entry); | ||||
| 				entry = boost::apply_visitor(*this, entry); | ||||
| 			return element; | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
| 	/// Minimizing visitor that removes all redundant elements from variant (e.g. AllOf inside another AllOf can be merged safely) | ||||
| 	template <typename ContainedClass> | ||||
| 	class MinimizingVisitor : public boost::static_visitor<typename ExpressionBase<ContainedClass>::Variant> | ||||
| 	{ | ||||
| 		typedef ExpressionBase<ContainedClass> Base; | ||||
|  | ||||
| 	public: | ||||
| 		typename Base::Variant operator()(const typename Base::Value & element) const | ||||
| 		{ | ||||
| 			return element; | ||||
| 		} | ||||
|  | ||||
| 		void operator()(typename Base::OperatorNone & element) const | ||||
| 		template <typename Type> | ||||
| 		typename Base::Variant operator()(const Type & element) const | ||||
| 		{ | ||||
| 			for (auto & entry : element.expressions) | ||||
| 				boost::apply_visitor(*this, entry); | ||||
| 		} | ||||
| 			Type ret; | ||||
|  | ||||
| 		void operator()(typename Base::Value & element) const | ||||
| 		{ | ||||
| 			visitor(element); | ||||
| 			for (auto & entryRO : element.expressions) | ||||
| 			{ | ||||
| 				auto entry = boost::apply_visitor(*this, entryRO); | ||||
|  | ||||
| 				try | ||||
| 				{ | ||||
| 					// copy entries from child of this type | ||||
| 					auto sublist = boost::get<Type>(entry).expressions; | ||||
| 					std::move(sublist.begin(), sublist.end(), std::back_inserter(ret.expressions)); | ||||
| 				} | ||||
| 				catch (boost::bad_get &) | ||||
| 				{ | ||||
| 					// different type (e.g. allOf vs oneOf) just copy | ||||
| 					ret.expressions.push_back(entry); | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			for ( auto it = ret.expressions.begin(); it != ret.expressions.end();) | ||||
| 			{ | ||||
| 				if (std::find(ret.expressions.begin(), it, *it) != it) | ||||
| 					it = ret.expressions.erase(it); // erase duplicate | ||||
| 				else | ||||
| 					it++; // goto next | ||||
| 			} | ||||
| 			return ret; | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
| @@ -370,16 +409,23 @@ public: | ||||
| 		std::swap(data, expr.data); | ||||
| 	} | ||||
|  | ||||
| 	Variant get() | ||||
| 	Variant get() const | ||||
| 	{ | ||||
| 		return data; | ||||
| 	} | ||||
|  | ||||
| 	/// Simple visitor that visits all entries in expression | ||||
| 	void forEach(std::function<void(Value &)> visitor) | ||||
| 	Variant morph(std::function<Variant(const Value &)> morpher) const | ||||
| 	{ | ||||
| 		LogicalExpressionDetail::ForEachVisitor<Value> testVisitor(visitor); | ||||
| 		boost::apply_visitor(testVisitor, data); | ||||
| 		LogicalExpressionDetail::ForEachVisitor<Value> visitor(morpher); | ||||
| 		return boost::apply_visitor(visitor, data); | ||||
| 	} | ||||
|  | ||||
| 	/// Minimizes expression, removing any redundant elements | ||||
| 	void minimize() | ||||
| 	{ | ||||
| 		LogicalExpressionDetail::MinimizingVisitor<Value> visitor; | ||||
| 		data = boost::apply_visitor(visitor, data); | ||||
| 	} | ||||
|  | ||||
| 	/// calculates if expression evaluates to "true". | ||||
|   | ||||
| @@ -396,7 +396,7 @@ DLL_LINKAGE void RemoveObject::applyGs( CGameState *gs ) | ||||
|  | ||||
| 	for (TriggeredEvent & event : gs->map->triggeredEvents) | ||||
| 	{ | ||||
| 		auto patcher = [&](EventCondition & cond) | ||||
| 		auto patcher = [&](EventCondition cond) -> EventExpression::Variant | ||||
| 		{ | ||||
| 			if (cond.object == obj) | ||||
| 			{ | ||||
| @@ -411,8 +411,9 @@ DLL_LINKAGE void RemoveObject::applyGs( CGameState *gs ) | ||||
| 					cond.value = 0; // destroyed object, from now on can not be fulfilled | ||||
| 				} | ||||
| 			} | ||||
| 			return cond; | ||||
| 		}; | ||||
| 		event.trigger.forEach(patcher); | ||||
| 		event.trigger = event.trigger.morph(patcher); | ||||
| 	} | ||||
|  | ||||
| 	gs->map->objects[id.getNum()].dellNull(); | ||||
|   | ||||
| @@ -1004,6 +1004,40 @@ bool CGTownInstance::hasBuilt(BuildingID buildingID) const | ||||
| 	return vstd::contains(builtBuildings, buildingID); | ||||
| } | ||||
|  | ||||
| CBuilding::TRequired CGTownInstance::genBuildingRequirements(BuildingID buildID) const | ||||
| { | ||||
| 	const CBuilding * building = town->buildings.at(buildID); | ||||
|  | ||||
| 	std::function<CBuilding::TRequired::Variant(const BuildingID &)> dependTest = | ||||
| 	[&](const BuildingID & id) -> CBuilding::TRequired::Variant | ||||
| 	{ | ||||
| 		const CBuilding * build = town->buildings.at(id); | ||||
|  | ||||
| 		if (!hasBuilt(id)) | ||||
| 			return id; | ||||
|  | ||||
| 		if (build->upgrade != BuildingID::NONE && !hasBuilt(build->upgrade)) | ||||
| 			return build->upgrade; | ||||
|  | ||||
| 		return build->requirements.morph(dependTest); | ||||
| 	}; | ||||
|  | ||||
| 	CBuilding::TRequired::OperatorAll requirements; | ||||
| 	if (building->upgrade != BuildingID::NONE) | ||||
| 	{ | ||||
| 		const CBuilding * upgr = town->buildings.at(building->upgrade); | ||||
|  | ||||
| 		requirements.expressions.push_back(upgr->bid); | ||||
| 		requirements.expressions.push_back(upgr->requirements.morph(dependTest)); | ||||
| 	} | ||||
| 	requirements.expressions.push_back(building->requirements.morph(dependTest)); | ||||
|  | ||||
| 	CBuilding::TRequired::Variant variant(requirements); | ||||
| 	CBuilding::TRequired ret(variant); | ||||
| 	ret.minimize(); | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| void CGTownInstance::addHeroToStructureVisitors( const CGHeroInstance *h, si32 structureInstanceID ) const | ||||
| { | ||||
| 	if(visitingHero == h) | ||||
|   | ||||
| @@ -232,6 +232,8 @@ public: | ||||
| 	bool armedGarrison() const; //true if town has creatures in garrison or garrisoned hero | ||||
| 	int getTownLevel() const; | ||||
|  | ||||
| 	CBuilding::TRequired genBuildingRequirements(BuildingID build) const; | ||||
|  | ||||
| 	void removeCapitols (PlayerColor owner) const; | ||||
| 	void addHeroToStructureVisitors(const CGHeroInstance *h, si32 structureInstanceID) const; //hero must be visiting or garrisoned in town | ||||
|  | ||||
|   | ||||
| @@ -441,7 +441,7 @@ void CMap::checkForObjectives() | ||||
| 	// NOTE: probably should be moved to MapFormatH3M.cpp | ||||
| 	for (TriggeredEvent & event : triggeredEvents) | ||||
| 	{ | ||||
| 		auto patcher = [&](EventCondition & cond) | ||||
| 		auto patcher = [&](EventCondition cond) -> EventExpression::Variant | ||||
| 		{ | ||||
| 			switch (cond.condition) | ||||
| 			{ | ||||
| @@ -491,8 +491,9 @@ void CMap::checkForObjectives() | ||||
| 				//break; case EventCondition::DAYS_WITHOUT_TOWN: | ||||
| 				//break; case EventCondition::STANDARD_WIN: | ||||
| 			} | ||||
| 			return cond; | ||||
| 		}; | ||||
| 		event.trigger.forEach(patcher); | ||||
| 		event.trigger = event.trigger.morph(patcher); | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -676,16 +676,17 @@ void CMapLoaderH3M::readAllowedArtifacts() | ||||
| 	// Messy, but needed | ||||
| 	for (TriggeredEvent & event : map->triggeredEvents) | ||||
| 	{ | ||||
| 		auto patcher = [&](EventCondition & cond) | ||||
| 		auto patcher = [&](EventCondition cond) -> EventExpression::Variant | ||||
| 		{ | ||||
| 			if (cond.condition == EventCondition::HAVE_ARTIFACT || | ||||
| 				cond.condition == EventCondition::TRANSPORT) | ||||
| 			{ | ||||
| 				map->allowedArtifact[cond.objectType] = false; | ||||
| 			} | ||||
| 			return cond; | ||||
| 		}; | ||||
|  | ||||
| 		event.trigger.forEach(patcher); | ||||
| 		event.trigger = event.trigger.morph(patcher); | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user