mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Merge pull request #5250 from IvanSavenko/optimize_ai
BattleAI optimizations
This commit is contained in:
		| @@ -92,8 +92,8 @@ void DamageCache::buildDamageCache(std::shared_ptr<HypotheticBattle> hb, BattleS | |||||||
| 			return u->isValidTarget(); | 			return u->isValidTarget(); | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| 	std::vector<const battle::Unit *> ourUnits; | 	battle::Units ourUnits; | ||||||
| 	std::vector<const battle::Unit *> enemyUnits; | 	battle::Units enemyUnits; | ||||||
|  |  | ||||||
| 	for(auto stack : stacks) | 	for(auto stack : stacks) | ||||||
| 	{ | 	{ | ||||||
| @@ -346,9 +346,9 @@ AttackPossibility AttackPossibility::evaluate( | |||||||
| 		if (!attackInfo.shooting) | 		if (!attackInfo.shooting) | ||||||
| 			ap.attackerState->setPosition(hex); | 			ap.attackerState->setPosition(hex); | ||||||
|  |  | ||||||
| 		std::vector<const battle::Unit *> defenderUnits; | 		battle::Units defenderUnits; | ||||||
| 		std::vector<const battle::Unit *> retaliatedUnits = {attacker}; | 		battle::Units retaliatedUnits = {attacker}; | ||||||
| 		std::vector<const battle::Unit *> affectedUnits; | 		battle::Units affectedUnits; | ||||||
|  |  | ||||||
| 		if (attackInfo.shooting) | 		if (attackInfo.shooting) | ||||||
| 			defenderUnits = state->getAttackedBattleUnits(attacker, defender, defHex, true, hex, defender->getPosition()); | 			defenderUnits = state->getAttackedBattleUnits(attacker, defender, defHex, true, hex, defender->getPosition()); | ||||||
| @@ -384,7 +384,9 @@ AttackPossibility AttackPossibility::evaluate( | |||||||
| 		affectedUnits = defenderUnits; | 		affectedUnits = defenderUnits; | ||||||
| 		vstd::concatenate(affectedUnits, retaliatedUnits); | 		vstd::concatenate(affectedUnits, retaliatedUnits); | ||||||
|  |  | ||||||
|  | #if BATTLE_TRACE_LEVEL>=1 | ||||||
| 		logAi->trace("Attacked battle units count %d, %d->%d", affectedUnits.size(), hex, defHex); | 		logAi->trace("Attacked battle units count %d, %d->%d", affectedUnits.size(), hex, defHex); | ||||||
|  | #endif | ||||||
|  |  | ||||||
| 		std::map<uint32_t, std::shared_ptr<battle::CUnitState>> defenderStates; | 		std::map<uint32_t, std::shared_ptr<battle::CUnitState>> defenderStates; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -756,7 +756,9 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack) | |||||||
|  |  | ||||||
| 					auto updatedAttack = AttackPossibility::evaluate(updatedBai, cachedAttack.ap->from, innerCache, state); | 					auto updatedAttack = AttackPossibility::evaluate(updatedBai, cachedAttack.ap->from, innerCache, state); | ||||||
|  |  | ||||||
| 					stackActionScore = scoreEvaluator.evaluateExchange(updatedAttack, cachedAttack.turn, *targets, innerCache, state); | 					BattleExchangeEvaluator innerEvaluator(scoreEvaluator); | ||||||
|  |  | ||||||
|  | 					stackActionScore = innerEvaluator.evaluateExchange(updatedAttack, cachedAttack.turn, *targets, innerCache, state); | ||||||
| 				} | 				} | ||||||
| 				for(const auto & unit : allUnits) | 				for(const auto & unit : allUnits) | ||||||
| 				{ | 				{ | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ | |||||||
| #include "BattleExchangeVariant.h" | #include "BattleExchangeVariant.h" | ||||||
| #include "BattleEvaluator.h" | #include "BattleEvaluator.h" | ||||||
| #include "../../lib/CStack.h" | #include "../../lib/CStack.h" | ||||||
|  | #include "tbb/parallel_for.h" | ||||||
|  |  | ||||||
| AttackerValue::AttackerValue() | AttackerValue::AttackerValue() | ||||||
| 	: value(0), | 	: value(0), | ||||||
| @@ -470,10 +471,10 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable( | |||||||
| 	return result; | 	return result; | ||||||
| } | } | ||||||
|  |  | ||||||
| std::vector<const battle::Unit *> BattleExchangeEvaluator::getAdjacentUnits(const battle::Unit * blockerUnit) const | battle::Units BattleExchangeEvaluator::getAdjacentUnits(const battle::Unit * blockerUnit) const | ||||||
| { | { | ||||||
| 	std::queue<const battle::Unit *> queue; | 	std::queue<const battle::Unit *> queue; | ||||||
| 	std::vector<const battle::Unit *> checkedStacks; | 	battle::Units checkedStacks; | ||||||
|  |  | ||||||
| 	queue.push(blockerUnit); | 	queue.push(blockerUnit); | ||||||
|  |  | ||||||
| @@ -505,7 +506,7 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits( | |||||||
| 	uint8_t turn, | 	uint8_t turn, | ||||||
| 	PotentialTargets & targets, | 	PotentialTargets & targets, | ||||||
| 	std::shared_ptr<HypotheticBattle> hb, | 	std::shared_ptr<HypotheticBattle> hb, | ||||||
| 	std::vector<const battle::Unit *> additionalUnits) const | 	battle::Units additionalUnits) const | ||||||
| { | { | ||||||
| 	ReachabilityData result; | 	ReachabilityData result; | ||||||
|  |  | ||||||
| @@ -514,18 +515,18 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits( | |||||||
| 	if(!ap.attack.shooting)  | 	if(!ap.attack.shooting)  | ||||||
| 		hexes.insert(ap.from); | 		hexes.insert(ap.from); | ||||||
|  |  | ||||||
| 	std::vector<const battle::Unit *> allReachableUnits = additionalUnits; | 	battle::Units allReachableUnits = additionalUnits; | ||||||
| 	 | 	 | ||||||
| 	for(auto hex : hexes) | 	for(auto hex : hexes) | ||||||
| 	{ | 	{ | ||||||
| 		vstd::concatenate(allReachableUnits, turn == 0 ? reachabilityMap.at(hex) : getOneTurnReachableUnits(turn, hex)); | 		vstd::concatenate(allReachableUnits, getOneTurnReachableUnits(turn, hex)); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if(!ap.attack.attacker->isTurret()) | 	if(!ap.attack.attacker->isTurret()) | ||||||
| 	{ | 	{ | ||||||
| 		for(auto hex : ap.attack.attacker->getHexes()) | 		for(auto hex : ap.attack.attacker->getHexes()) | ||||||
| 		{ | 		{ | ||||||
| 			auto unitsReachingAttacker = turn == 0 ? reachabilityMap.at(hex) : getOneTurnReachableUnits(turn, hex); | 			auto unitsReachingAttacker = getOneTurnReachableUnits(turn, hex); | ||||||
| 			for(auto unit : unitsReachingAttacker) | 			for(auto unit : unitsReachingAttacker) | ||||||
| 			{ | 			{ | ||||||
| 				if(unit->unitSide() != ap.attack.attacker->unitSide()) | 				if(unit->unitSide() != ap.attack.attacker->unitSide()) | ||||||
| @@ -635,7 +636,7 @@ BattleScore BattleExchangeEvaluator::calculateExchange( | |||||||
| 	PotentialTargets & targets, | 	PotentialTargets & targets, | ||||||
| 	DamageCache & damageCache, | 	DamageCache & damageCache, | ||||||
| 	std::shared_ptr<HypotheticBattle> hb, | 	std::shared_ptr<HypotheticBattle> hb, | ||||||
| 	std::vector<const battle::Unit *> additionalUnits) const | 	battle::Units additionalUnits) const | ||||||
| { | { | ||||||
| #if BATTLE_TRACE_LEVEL>=1 | #if BATTLE_TRACE_LEVEL>=1 | ||||||
| 	logAi->trace("Battle exchange at %d", ap.attack.shooting ? ap.dest.hex : ap.from.hex); | 	logAi->trace("Battle exchange at %d", ap.attack.shooting ? ap.dest.hex : ap.from.hex); | ||||||
| @@ -648,8 +649,8 @@ BattleScore BattleExchangeEvaluator::calculateExchange( | |||||||
| 		return BattleScore(EvaluationResult::INEFFECTIVE_SCORE, 0); | 		return BattleScore(EvaluationResult::INEFFECTIVE_SCORE, 0); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	std::vector<const battle::Unit *> ourStacks; | 	battle::Units ourStacks; | ||||||
| 	std::vector<const battle::Unit *> enemyStacks; | 	battle::Units enemyStacks; | ||||||
|  |  | ||||||
| 	if(hb->battleGetUnitByID(ap.attack.defender->unitId())->alive()) | 	if(hb->battleGetUnitByID(ap.attack.defender->unitId())->alive()) | ||||||
| 		enemyStacks.push_back(ap.attack.defender); | 		enemyStacks.push_back(ap.attack.defender); | ||||||
| @@ -799,7 +800,9 @@ BattleScore BattleExchangeEvaluator::calculateExchange( | |||||||
| 							if(!u->getPosition().isValid()) | 							if(!u->getPosition().isValid()) | ||||||
| 								return false; // e.g. tower shooters | 								return false; // e.g. tower shooters | ||||||
|  |  | ||||||
| 							return vstd::contains_if(reachabilityMap.at(u->getPosition()), [&attacker](const battle::Unit * other) -> bool | 							const auto & reachableUnits = getOneTurnReachableUnits(0, u->getPosition()); | ||||||
|  |  | ||||||
|  | 							return vstd::contains_if(reachableUnits, [&attacker](const battle::Unit * other) -> bool | ||||||
| 								{ | 								{ | ||||||
| 									return attacker->unitId() == other->unitId(); | 									return attacker->unitId() == other->unitId(); | ||||||
| 								}); | 								}); | ||||||
| @@ -886,7 +889,7 @@ bool BattleExchangeEvaluator::canBeHitThisTurn(const AttackPossibility & ap) | |||||||
| { | { | ||||||
| 	for(auto pos : ap.attack.attacker->getSurroundingHexes()) | 	for(auto pos : ap.attack.attacker->getSurroundingHexes()) | ||||||
| 	{ | 	{ | ||||||
| 		for(auto u : reachabilityMap[pos]) | 		for(auto u : getOneTurnReachableUnits(0, pos)) | ||||||
| 		{ | 		{ | ||||||
| 			if(u->unitSide() != ap.attack.attacker->unitSide()) | 			if(u->unitSide() != ap.attack.attacker->unitSide()) | ||||||
| 			{ | 			{ | ||||||
| @@ -898,6 +901,22 @@ bool BattleExchangeEvaluator::canBeHitThisTurn(const AttackPossibility & ap) | |||||||
| 	return false; | 	return false; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void ReachabilityMapCache::update(const std::vector<battle::Units> & turnOrder, std::shared_ptr<HypotheticBattle> hb) | ||||||
|  | { | ||||||
|  | 	for(auto turn : turnOrder) | ||||||
|  | 	{ | ||||||
|  | 		for(auto u : turn) | ||||||
|  | 		{ | ||||||
|  | 			if(!vstd::contains(unitReachabilityMap, u->unitId())) | ||||||
|  | 			{ | ||||||
|  | 				unitReachabilityMap[u->unitId()] = hb->getReachability(u); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	hexReachabilityPerTurn.clear(); | ||||||
|  | } | ||||||
|  |  | ||||||
| void BattleExchangeEvaluator::updateReachabilityMap(std::shared_ptr<HypotheticBattle> hb) | void BattleExchangeEvaluator::updateReachabilityMap(std::shared_ptr<HypotheticBattle> hb) | ||||||
| { | { | ||||||
| 	const int TURN_DEPTH = 2; | 	const int TURN_DEPTH = 2; | ||||||
| @@ -905,26 +924,25 @@ void BattleExchangeEvaluator::updateReachabilityMap(std::shared_ptr<HypotheticBa | |||||||
| 	turnOrder.clear(); | 	turnOrder.clear(); | ||||||
|  |  | ||||||
| 	hb->battleGetTurnOrder(turnOrder, std::numeric_limits<int>::max(), TURN_DEPTH); | 	hb->battleGetTurnOrder(turnOrder, std::numeric_limits<int>::max(), TURN_DEPTH); | ||||||
|  | 	reachabilityMap.update(turnOrder, hb); | ||||||
| 	for(auto turn : turnOrder) |  | ||||||
| 	{ |  | ||||||
| 		for(auto u : turn) |  | ||||||
| 		{ |  | ||||||
| 			if(!vstd::contains(reachabilityCache, u->unitId())) |  | ||||||
| 			{ |  | ||||||
| 				reachabilityCache[u->unitId()] = hb->getReachability(u); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); ++hex) |  | ||||||
| 	{ |  | ||||||
| 		reachabilityMap[hex] = getOneTurnReachableUnits(0, hex); |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
| std::vector<const battle::Unit *> BattleExchangeEvaluator::getOneTurnReachableUnits(uint8_t turn, BattleHex hex) const | const battle::Units & ReachabilityMapCache::getOneTurnReachableUnits(std::shared_ptr<CBattleInfoCallback> cb, std::shared_ptr<Environment> env, const std::vector<battle::Units> & turnOrder, uint8_t turn, BattleHex hex) | ||||||
| { | { | ||||||
| 	std::vector<const battle::Unit *> result; | 	auto & turnData = hexReachabilityPerTurn[turn]; | ||||||
|  |  | ||||||
|  | 	if (!turnData.isValid[hex.toInt()]) | ||||||
|  | 	{ | ||||||
|  | 		turnData.hexes[hex.toInt()] = computeOneTurnReachableUnits(cb, env, turnOrder, turn, hex); | ||||||
|  | 		turnData.isValid.set(hex.toInt()); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return turnData.hexes[hex.toInt()]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | battle::Units ReachabilityMapCache::computeOneTurnReachableUnits(std::shared_ptr<CBattleInfoCallback> cb, std::shared_ptr<Environment> env, const std::vector<battle::Units> & turnOrder, uint8_t turn, BattleHex hex) | ||||||
|  | { | ||||||
|  | 	battle::Units result; | ||||||
|  |  | ||||||
| 	for(int i = 0; i < turnOrder.size(); i++, turn++) | 	for(int i = 0; i < turnOrder.size(); i++, turn++) | ||||||
| 	{ | 	{ | ||||||
| @@ -946,10 +964,10 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getOneTurnReachableUn | |||||||
| 			auto unitSpeed = unit->getMovementRange(turn); | 			auto unitSpeed = unit->getMovementRange(turn); | ||||||
| 			auto radius = unitSpeed * (turn + 1); | 			auto radius = unitSpeed * (turn + 1); | ||||||
|  |  | ||||||
| 			auto reachabilityIter = reachabilityCache.find(unit->unitId()); | 			auto reachabilityIter = unitReachabilityMap.find(unit->unitId()); | ||||||
| 			assert(reachabilityIter != reachabilityCache.end()); // missing updateReachabilityMap call? | 			assert(reachabilityIter != unitReachabilityMap.end()); // missing updateReachabilityMap call? | ||||||
|  |  | ||||||
| 			ReachabilityInfo unitReachability = reachabilityIter != reachabilityCache.end() ? reachabilityIter->second : turnBattle.getReachability(unit); | 			ReachabilityInfo unitReachability = reachabilityIter != unitReachabilityMap.end() ? reachabilityIter->second : turnBattle.getReachability(unit); | ||||||
|  |  | ||||||
| 			bool reachable = unitReachability.distances.at(hex.toInt()) <= radius; | 			bool reachable = unitReachability.distances.at(hex.toInt()) <= radius; | ||||||
|  |  | ||||||
| @@ -978,6 +996,11 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getOneTurnReachableUn | |||||||
| 	return result; | 	return result; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | const battle::Units & BattleExchangeEvaluator::getOneTurnReachableUnits(uint8_t turn, BattleHex hex) const | ||||||
|  | { | ||||||
|  | 	return reachabilityMap.getOneTurnReachableUnits(cb, env, turnOrder, turn, hex); | ||||||
|  | } | ||||||
|  |  | ||||||
| // avoid blocking path for stronger stack by weaker stack | // avoid blocking path for stronger stack by weaker stack | ||||||
| bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb, const battle::Unit * activeUnit, BattleHex position) | bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb, const battle::Unit * activeUnit, BattleHex position) | ||||||
| { | { | ||||||
| @@ -1029,9 +1052,11 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb | |||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				if(!reachable && std::count(reachabilityMap[hex].begin(), reachabilityMap[hex].end(), unit) > 1) | 				if(!reachable) | ||||||
| 				{ | 				{ | ||||||
| 					blockingScore += ratio * (enemyUnit ? BLOCKING_OWN_ATTACK_PENALTY : BLOCKING_OWN_MOVE_PENALTY); | 					auto reachableUnits = getOneTurnReachableUnits(0, hex); | ||||||
|  | 					if (std::count(reachableUnits.begin(), reachableUnits.end(), unit) > 1) | ||||||
|  | 						blockingScore += ratio * (enemyUnit ? BLOCKING_OWN_ATTACK_PENALTY : BLOCKING_OWN_MOVE_PENALTY); | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -112,24 +112,40 @@ private: | |||||||
|  |  | ||||||
| struct ReachabilityData | struct ReachabilityData | ||||||
| { | { | ||||||
| 	std::map<int, std::vector<const battle::Unit *>> units; | 	std::map<int, battle::Units> units; | ||||||
|  |  | ||||||
| 	// shooters which are within mellee attack and mellee units | 	// shooters which are within mellee attack and mellee units | ||||||
| 	std::vector<const battle::Unit *> melleeAccessible; | 	battle::Units melleeAccessible; | ||||||
|  |  | ||||||
| 	// far shooters | 	// far shooters | ||||||
| 	std::vector<const battle::Unit *> shooters; | 	battle::Units shooters; | ||||||
|  |  | ||||||
| 	std::set<uint32_t> enemyUnitsReachingAttacker; | 	std::set<uint32_t> enemyUnitsReachingAttacker; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | class ReachabilityMapCache | ||||||
|  | { | ||||||
|  | 	struct PerTurnData{ | ||||||
|  | 		std::bitset<GameConstants::BFIELD_SIZE> isValid; | ||||||
|  | 		std::array<battle::Units, GameConstants::BFIELD_SIZE> hexes; | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	std::map<uint32_t, ReachabilityInfo> unitReachabilityMap; // unit ID -> reachability | ||||||
|  | 	std::map<uint32_t, PerTurnData> hexReachabilityPerTurn; | ||||||
|  |  | ||||||
|  | 	//const ReachabilityInfo & update(); | ||||||
|  | 	battle::Units computeOneTurnReachableUnits(std::shared_ptr<CBattleInfoCallback> cb, std::shared_ptr<Environment> env, const std::vector<battle::Units> & turnOrder, uint8_t turn, BattleHex hex); | ||||||
|  | public: | ||||||
|  | 	const battle::Units & getOneTurnReachableUnits(std::shared_ptr<CBattleInfoCallback> cb, std::shared_ptr<Environment> env, const std::vector<battle::Units> & turnOrder, uint8_t turn, BattleHex hex); | ||||||
|  | 	void update(const std::vector<battle::Units> & turnOrder, std::shared_ptr<HypotheticBattle> hb); | ||||||
|  | }; | ||||||
|  |  | ||||||
| class BattleExchangeEvaluator | class BattleExchangeEvaluator | ||||||
| { | { | ||||||
| private: | private: | ||||||
| 	std::shared_ptr<CBattleInfoCallback> cb; | 	std::shared_ptr<CBattleInfoCallback> cb; | ||||||
| 	std::shared_ptr<Environment> env; | 	std::shared_ptr<Environment> env; | ||||||
| 	std::map<uint32_t, ReachabilityInfo> reachabilityCache; | 	mutable ReachabilityMapCache reachabilityMap; | ||||||
| 	std::map<BattleHex, std::vector<const battle::Unit *>> reachabilityMap; |  | ||||||
| 	std::vector<battle::Units> turnOrder; | 	std::vector<battle::Units> turnOrder; | ||||||
| 	float negativeEffectMultiplier; | 	float negativeEffectMultiplier; | ||||||
| 	int simulationTurnsCount; | 	int simulationTurnsCount; | ||||||
| @@ -142,7 +158,7 @@ private: | |||||||
| 		PotentialTargets & targets, | 		PotentialTargets & targets, | ||||||
| 		DamageCache & damageCache, | 		DamageCache & damageCache, | ||||||
| 		std::shared_ptr<HypotheticBattle> hb, | 		std::shared_ptr<HypotheticBattle> hb, | ||||||
| 		std::vector<const battle::Unit *> additionalUnits = {}) const; | 		battle::Units additionalUnits = {}) const; | ||||||
|  |  | ||||||
| 	bool canBeHitThisTurn(const AttackPossibility & ap); | 	bool canBeHitThisTurn(const AttackPossibility & ap); | ||||||
|  |  | ||||||
| @@ -169,7 +185,7 @@ public: | |||||||
| 		DamageCache & damageCache, | 		DamageCache & damageCache, | ||||||
| 		std::shared_ptr<HypotheticBattle> hb) const; | 		std::shared_ptr<HypotheticBattle> hb) const; | ||||||
|  |  | ||||||
| 	std::vector<const battle::Unit *> getOneTurnReachableUnits(uint8_t turn, BattleHex hex) const; | 	const battle::Units & getOneTurnReachableUnits(uint8_t turn, BattleHex hex) const; | ||||||
| 	void updateReachabilityMap(std::shared_ptr<HypotheticBattle> hb); | 	void updateReachabilityMap(std::shared_ptr<HypotheticBattle> hb); | ||||||
|  |  | ||||||
| 	ReachabilityData getExchangeUnits( | 	ReachabilityData getExchangeUnits( | ||||||
| @@ -177,7 +193,7 @@ public: | |||||||
| 		uint8_t turn, | 		uint8_t turn, | ||||||
| 		PotentialTargets & targets, | 		PotentialTargets & targets, | ||||||
| 		std::shared_ptr<HypotheticBattle> hb, | 		std::shared_ptr<HypotheticBattle> hb, | ||||||
| 		std::vector<const battle::Unit *> additionalUnits = {}) const; | 		battle::Units additionalUnits = {}) const; | ||||||
|  |  | ||||||
| 	bool checkPositionBlocksOurStacks(HypotheticBattle & hb, const battle::Unit * unit, BattleHex position); | 	bool checkPositionBlocksOurStacks(HypotheticBattle & hb, const battle::Unit * unit, BattleHex position); | ||||||
|  |  | ||||||
| @@ -187,7 +203,7 @@ public: | |||||||
| 		DamageCache & damageCache, | 		DamageCache & damageCache, | ||||||
| 		std::shared_ptr<HypotheticBattle> hb); | 		std::shared_ptr<HypotheticBattle> hb); | ||||||
|  |  | ||||||
| 	std::vector<const battle::Unit *> getAdjacentUnits(const battle::Unit * unit) const; | 	battle::Units getAdjacentUnits(const battle::Unit * unit) const; | ||||||
|  |  | ||||||
| 	float getPositiveEffectMultiplier() const { return 1; } | 	float getPositiveEffectMultiplier() const { return 1; } | ||||||
| 	float getNegativeEffectMultiplier() const { return negativeEffectMultiplier; } | 	float getNegativeEffectMultiplier() const { return negativeEffectMultiplier; } | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ class PotentialTargets | |||||||
| { | { | ||||||
| public: | public: | ||||||
| 	std::vector<AttackPossibility> possibleAttacks; | 	std::vector<AttackPossibility> possibleAttacks; | ||||||
| 	std::vector<const battle::Unit *> unreachableEnemies; | 	battle::Units unreachableEnemies; | ||||||
|  |  | ||||||
| 	PotentialTargets(){}; | 	PotentialTargets(){}; | ||||||
| 	PotentialTargets( | 	PotentialTargets( | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								Global.h
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								Global.h
									
									
									
									
									
								
							| @@ -670,15 +670,15 @@ namespace vstd | |||||||
| 		return false; | 		return false; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	template<typename T> | 	template <typename Container> | ||||||
| 	void removeDuplicates(std::vector<T> &vec) | 	void removeDuplicates(Container &vec) | ||||||
| 	{ | 	{ | ||||||
| 		std::sort(vec.begin(), vec.end()); | 		std::sort(vec.begin(), vec.end()); | ||||||
| 		vec.erase(std::unique(vec.begin(), vec.end()), vec.end()); | 		vec.erase(std::unique(vec.begin(), vec.end()), vec.end()); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	template <typename T> | 	template <typename Container> | ||||||
| 	void concatenate(std::vector<T> &dest, const std::vector<T> &src) | 	void concatenate(Container &dest, const Container &src) | ||||||
| 	{ | 	{ | ||||||
| 		dest.reserve(dest.size() + src.size()); | 		dest.reserve(dest.size() + src.size()); | ||||||
| 		dest.insert(dest.end(), src.begin(), src.end()); | 		dest.insert(dest.end(), src.begin(), src.end()); | ||||||
|   | |||||||
| @@ -22,6 +22,7 @@ class SpellSchool; | |||||||
| namespace battle | namespace battle | ||||||
| { | { | ||||||
| 	class Unit; | 	class Unit; | ||||||
|  | 	using Units = boost::container::small_vector<const Unit *, 4>; | ||||||
| } | } | ||||||
|  |  | ||||||
| namespace spells | namespace spells | ||||||
| @@ -65,7 +66,7 @@ public: | |||||||
| 	virtual void getCasterName(MetaString & text) const = 0; | 	virtual void getCasterName(MetaString & text) const = 0; | ||||||
|  |  | ||||||
| 	///full default text | 	///full default text | ||||||
| 	virtual void getCastDescription(const Spell * spell, const std::vector<const battle::Unit *> & attacked, MetaString & text) const = 0; | 	virtual void getCastDescription(const Spell * spell, const battle::Units & attacked, MetaString & text) const = 0; | ||||||
|  |  | ||||||
| 	virtual void spendMana(ServerCallback * server, const int32_t spellCost) const = 0; | 	virtual void spendMana(ServerCallback * server, const int32_t spellCost) const = 0; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -93,7 +93,9 @@ public: | |||||||
| 		h & static_cast<CArtifactSet&>(*this); | 		h & static_cast<CArtifactSet&>(*this); | ||||||
| 		h & _armyObj; | 		h & _armyObj; | ||||||
| 		h & experience; | 		h & experience; | ||||||
| 		BONUS_TREE_DESERIALIZATION_FIX |  | ||||||
|  | 		if(!h.saving) | ||||||
|  | 			deserializationFix(); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	void serializeJson(JsonSerializeFormat & handler); | 	void serializeJson(JsonSerializeFormat & handler); | ||||||
|   | |||||||
| @@ -35,6 +35,7 @@ CStack::CStack(const CStackInstance * Base, const PlayerColor & O, int I, Battle | |||||||
| 	side(Side) | 	side(Side) | ||||||
| { | { | ||||||
| 	health.init(); //??? | 	health.init(); //??? | ||||||
|  | 	doubleWideCached = battle::CUnitState::doubleWide(); | ||||||
| } | } | ||||||
|  |  | ||||||
| CStack::CStack(): | CStack::CStack(): | ||||||
| @@ -55,6 +56,7 @@ CStack::CStack(const CStackBasicDescriptor * stack, const PlayerColor & O, int I | |||||||
| 	side(Side) | 	side(Side) | ||||||
| { | { | ||||||
| 	health.init(); //??? | 	health.init(); //??? | ||||||
|  | 	doubleWideCached = battle::CUnitState::doubleWide(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void CStack::localInit(BattleInfo * battleInfo) | void CStack::localInit(BattleInfo * battleInfo) | ||||||
| @@ -296,7 +298,7 @@ BattleHexArray CStack::meleeAttackHexes(const battle::Unit * attacker, const bat | |||||||
|  |  | ||||||
| bool CStack::isMeleeAttackPossible(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos, BattleHex defenderPos) | bool CStack::isMeleeAttackPossible(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos, BattleHex defenderPos) | ||||||
| { | { | ||||||
| 	if(defender->hasBonusOfType(BonusType::INVINCIBLE)) | 	if(defender->isInvincible()) | ||||||
| 		return false; | 		return false; | ||||||
| 		 | 		 | ||||||
| 	return !meleeAttackHexes(attacker, defender, attackerPos, defenderPos).empty(); | 	return !meleeAttackHexes(attacker, defender, attackerPos, defenderPos).empty(); | ||||||
| @@ -404,4 +406,30 @@ void CStack::spendMana(ServerCallback * server, const int spellCost) const | |||||||
| 	server->apply(ssp); | 	server->apply(ssp); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void CStack::postDeserialize(const CArmedInstance * army, const SlotID & extSlot) | ||||||
|  | { | ||||||
|  | 	if(extSlot == SlotID::COMMANDER_SLOT_PLACEHOLDER) | ||||||
|  | 	{ | ||||||
|  | 		const auto * hero = dynamic_cast<const CGHeroInstance *>(army); | ||||||
|  | 		assert(hero); | ||||||
|  | 		base = hero->commander; | ||||||
|  | 	} | ||||||
|  | 	else if(slot == SlotID::SUMMONED_SLOT_PLACEHOLDER || slot == SlotID::ARROW_TOWERS_SLOT || slot == SlotID::WAR_MACHINES_SLOT) | ||||||
|  | 	{ | ||||||
|  | 		//no external slot possible, so no base stack | ||||||
|  | 		base = nullptr; | ||||||
|  | 	} | ||||||
|  | 	else if(!army || extSlot == SlotID() || !army->hasStackAtSlot(extSlot)) | ||||||
|  | 	{ | ||||||
|  | 		base = nullptr; | ||||||
|  | 		logGlobal->warn("%s doesn't have a base stack!", typeID.toEntity(VLC)->getNameSingularTranslated()); | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 	{ | ||||||
|  | 		base = &army->getStack(extSlot); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	doubleWideCached = battle::CUnitState::doubleWide(); | ||||||
|  | } | ||||||
|  |  | ||||||
| VCMI_LIB_NAMESPACE_END | VCMI_LIB_NAMESPACE_END | ||||||
|   | |||||||
							
								
								
									
										29
									
								
								lib/CStack.h
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								lib/CStack.h
									
									
									
									
									
								
							| @@ -23,7 +23,7 @@ struct BattleStackAttacked; | |||||||
| class BattleInfo; | class BattleInfo; | ||||||
|  |  | ||||||
| //Represents STACK_BATTLE nodes | //Represents STACK_BATTLE nodes | ||||||
| class DLL_LINKAGE CStack : public CBonusSystemNode, public battle::CUnitState, public battle::IUnitEnvironment | class DLL_LINKAGE CStack final : public CBonusSystemNode, public battle::CUnitState, public battle::IUnitEnvironment | ||||||
| { | { | ||||||
| private: | private: | ||||||
| 	ui32 ID = -1; //unique ID of stack | 	ui32 ID = -1; //unique ID of stack | ||||||
| @@ -36,6 +36,9 @@ private: | |||||||
|  |  | ||||||
| 	SlotID slot;  //slot - position in garrison (may be 255 for neutrals/called creatures) | 	SlotID slot;  //slot - position in garrison (may be 255 for neutrals/called creatures) | ||||||
|  |  | ||||||
|  | 	bool doubleWideCached = false; | ||||||
|  |  | ||||||
|  | 	void postDeserialize(const CArmedInstance * army, const SlotID & extSlot); | ||||||
| public: | public: | ||||||
| 	const CStackInstance * base = nullptr; //garrison slot from which stack originates (nullptr for war machines, summoned cres, etc) | 	const CStackInstance * base = nullptr; //garrison slot from which stack originates (nullptr for war machines, summoned cres, etc) | ||||||
| 	 | 	 | ||||||
| @@ -77,6 +80,7 @@ public: | |||||||
| 	BattleSide unitSide() const override; | 	BattleSide unitSide() const override; | ||||||
| 	PlayerColor unitOwner() const override; | 	PlayerColor unitOwner() const override; | ||||||
| 	SlotID unitSlot() const override; | 	SlotID unitSlot() const override; | ||||||
|  | 	bool doubleWide() const override { return doubleWideCached;}; | ||||||
|  |  | ||||||
| 	std::string getDescription() const override; | 	std::string getDescription() const override; | ||||||
|  |  | ||||||
| @@ -119,26 +123,7 @@ public: | |||||||
| 			h & army; | 			h & army; | ||||||
| 			h & extSlot; | 			h & extSlot; | ||||||
|  |  | ||||||
| 			if(extSlot == SlotID::COMMANDER_SLOT_PLACEHOLDER) | 			postDeserialize(army, extSlot); | ||||||
| 			{ |  | ||||||
| 				const auto * hero = dynamic_cast<const CGHeroInstance *>(army); |  | ||||||
| 				assert(hero); |  | ||||||
| 				base = hero->commander; |  | ||||||
| 			} |  | ||||||
| 			else if(slot == SlotID::SUMMONED_SLOT_PLACEHOLDER || slot == SlotID::ARROW_TOWERS_SLOT || slot == SlotID::WAR_MACHINES_SLOT) |  | ||||||
| 			{ |  | ||||||
| 				//no external slot possible, so no base stack |  | ||||||
| 				base = nullptr; |  | ||||||
| 			} |  | ||||||
| 			else if(!army || extSlot == SlotID() || !army->hasStackAtSlot(extSlot)) |  | ||||||
| 			{ |  | ||||||
| 				base = nullptr; |  | ||||||
| 				logGlobal->warn("%s doesn't have a base stack!", typeID.toEntity(VLC)->getNameSingularTranslated()); |  | ||||||
| 			} |  | ||||||
| 			else |  | ||||||
| 			{ |  | ||||||
| 				base = &army->getStack(extSlot); |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -146,4 +131,4 @@ private: | |||||||
| 	const BattleInfo * battle; //do not serialize | 	const BattleInfo * battle; //do not serialize | ||||||
| }; | }; | ||||||
|  |  | ||||||
| VCMI_LIB_NAMESPACE_END | VCMI_LIB_NAMESPACE_END | ||||||
|   | |||||||
| @@ -113,11 +113,11 @@ public: | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	void clear() noexcept; | 	void clear() noexcept; | ||||||
| 	inline void erase(size_type index) noexcept | 	inline void erase(BattleHex target) noexcept | ||||||
| 	{ | 	{ | ||||||
| 		assert(index < totalSize); | 		assert(contains(target)); | ||||||
| 		internalStorage[index] = BattleHex::INVALID; | 		vstd::erase(internalStorage, target); | ||||||
| 		presenceFlags[index] = 0; | 		presenceFlags[target.toInt()] = 0; | ||||||
| 	} | 	} | ||||||
| 	void erase(iterator first, iterator last) noexcept; | 	void erase(iterator first, iterator last) noexcept; | ||||||
| 	inline void pop_back() noexcept | 	inline void pop_back() noexcept | ||||||
| @@ -160,17 +160,23 @@ public: | |||||||
| 	/// get (precomputed) all possible surrounding tiles | 	/// get (precomputed) all possible surrounding tiles | ||||||
| 	static const BattleHexArray & getAllNeighbouringTiles(BattleHex hex) noexcept | 	static const BattleHexArray & getAllNeighbouringTiles(BattleHex hex) noexcept | ||||||
| 	{ | 	{ | ||||||
| 		assert(hex.isValid()); | 		static const BattleHexArray invalid; | ||||||
|  |  | ||||||
| 		return allNeighbouringTiles[hex.toInt()]; | 		if (hex.isValid()) | ||||||
|  | 			return allNeighbouringTiles[hex.toInt()]; | ||||||
|  | 		else | ||||||
|  | 			return invalid; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	/// get (precomputed) only valid and available surrounding tiles | 	/// get (precomputed) only valid and available surrounding tiles | ||||||
| 	static const BattleHexArray & getNeighbouringTiles(BattleHex hex) noexcept | 	static const BattleHexArray & getNeighbouringTiles(BattleHex hex) noexcept | ||||||
| 	{ | 	{ | ||||||
| 		assert(hex.isValid()); | 		static const BattleHexArray invalid; | ||||||
|  |  | ||||||
| 		return neighbouringTiles[hex.toInt()]; | 		if (hex.isValid()) | ||||||
|  | 			return neighbouringTiles[hex.toInt()]; | ||||||
|  | 		else | ||||||
|  | 			return invalid; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	/// get (precomputed) only valid and available surrounding tiles for double wide creatures | 	/// get (precomputed) only valid and available surrounding tiles for double wide creatures | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ BattleStateInfoForRetreat::BattleStateInfoForRetreat(): | |||||||
| { | { | ||||||
| } | } | ||||||
|  |  | ||||||
| uint64_t getFightingStrength(const std::vector<const battle::Unit *> & stacks, const CGHeroInstance * hero = nullptr) | uint64_t getFightingStrength(const battle::Units & stacks, const CGHeroInstance * hero = nullptr) | ||||||
| { | { | ||||||
| 	uint64_t result = 0; | 	uint64_t result = 0; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ VCMI_LIB_NAMESPACE_BEGIN | |||||||
| namespace battle | namespace battle | ||||||
| { | { | ||||||
| 	class Unit; | 	class Unit; | ||||||
|  | 	using Units = boost::container::small_vector<const Unit *, 4>; | ||||||
| } | } | ||||||
|  |  | ||||||
| class CGHeroInstance; | class CGHeroInstance; | ||||||
| @@ -27,8 +28,8 @@ public: | |||||||
| 	bool canSurrender; | 	bool canSurrender; | ||||||
| 	bool isLastTurnBeforeDie; | 	bool isLastTurnBeforeDie; | ||||||
| 	BattleSide ourSide; | 	BattleSide ourSide; | ||||||
| 	std::vector<const battle::Unit *> ourStacks; | 	battle::Units ourStacks; | ||||||
| 	std::vector<const battle::Unit *> enemyStacks; | 	battle::Units enemyStacks; | ||||||
| 	const CGHeroInstance * ourHero; | 	const CGHeroInstance * ourHero; | ||||||
| 	const CGHeroInstance * enemyHero; | 	const CGHeroInstance * enemyHero; | ||||||
| 	int turnsSkippedByDefense; | 	int turnsSkippedByDefense; | ||||||
|   | |||||||
| @@ -383,11 +383,9 @@ battle::Units CBattleInfoCallback::battleAliveUnits(BattleSide side) const | |||||||
|  |  | ||||||
| using namespace battle; | using namespace battle; | ||||||
|  |  | ||||||
| //T is battle::Unit descendant | static const battle::Unit * takeOneUnit(battle::Units & allUnits, const int turn, BattleSide & sideThatLastMoved, int phase) | ||||||
| template <typename T> |  | ||||||
| const T * takeOneUnit(std::vector<const T*> & allUnits, const int turn, BattleSide & sideThatLastMoved, int phase) |  | ||||||
| { | { | ||||||
| 	const T * returnedUnit = nullptr; | 	const battle::Unit * returnedUnit = nullptr; | ||||||
| 	size_t currentUnitIndex = 0; | 	size_t currentUnitIndex = 0; | ||||||
|  |  | ||||||
| 	for(size_t i = 0; i < allUnits.size(); i++) | 	for(size_t i = 0; i < allUnits.size(); i++) | ||||||
| @@ -677,7 +675,7 @@ bool CBattleInfoCallback::battleCanAttack(const battle::Unit * stack, const batt | |||||||
| 	if (!stack || !target) | 	if (!stack || !target) | ||||||
| 		return false; | 		return false; | ||||||
|  |  | ||||||
| 	if(target->hasBonusOfType(BonusType::INVINCIBLE)) | 	if(target->isInvincible()) | ||||||
| 		return false; | 		return false; | ||||||
|  |  | ||||||
| 	if(!battleMatchOwner(stack, target)) | 	if(!battleMatchOwner(stack, target)) | ||||||
| @@ -746,7 +744,7 @@ bool CBattleInfoCallback::battleCanShoot(const battle::Unit * attacker, BattleHe | |||||||
| 		if(!defender) | 		if(!defender) | ||||||
| 			return false; | 			return false; | ||||||
|  |  | ||||||
| 		if(defender->hasBonusOfType(BonusType::INVINCIBLE)) | 		if(defender->isInvincible()) | ||||||
| 			return false; | 			return false; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -812,7 +810,7 @@ DamageEstimation CBattleInfoCallback::battleEstimateDamage(const BattleAttackInf | |||||||
| 	if (!bai.defender->ableToRetaliate()) | 	if (!bai.defender->ableToRetaliate()) | ||||||
| 		return ret; | 		return ret; | ||||||
|  |  | ||||||
| 	if (bai.attacker->hasBonusOfType(BonusType::BLOCKS_RETALIATION) || bai.attacker->hasBonusOfType(BonusType::INVINCIBLE)) | 	if (bai.attacker->hasBonusOfType(BonusType::BLOCKS_RETALIATION) || bai.attacker->isInvincible()) | ||||||
| 		return ret; | 		return ret; | ||||||
|  |  | ||||||
| 	//TODO: rewrite using boost::numeric::interval | 	//TODO: rewrite using boost::numeric::interval | ||||||
| @@ -1168,7 +1166,7 @@ std::pair<const battle::Unit *, BattleHex> CBattleInfoCallback::getNearestStack( | |||||||
|  |  | ||||||
| 	std::vector<DistStack> stackPairs; | 	std::vector<DistStack> stackPairs; | ||||||
|  |  | ||||||
| 	std::vector<const battle::Unit *> possible = battleGetUnitsIf([=](const battle::Unit * unit) | 	battle::Units possible = battleGetUnitsIf([=](const battle::Unit * unit) | ||||||
| 	{ | 	{ | ||||||
| 		return unit->isValidTarget(false) && unit != closest; | 		return unit->isValidTarget(false) && unit != closest; | ||||||
| 	}); | 	}); | ||||||
| @@ -1355,14 +1353,9 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes( | |||||||
| 	if(attacker->hasBonusOfType(BonusType::WIDE_BREATH)) | 	if(attacker->hasBonusOfType(BonusType::WIDE_BREATH)) | ||||||
| 	{ | 	{ | ||||||
| 		BattleHexArray hexes = destinationTile.getNeighbouringTiles(); | 		BattleHexArray hexes = destinationTile.getNeighbouringTiles(); | ||||||
| 		for(int i = 0; i < hexes.size(); i++) | 		if (hexes.contains(attackOriginHex)) | ||||||
| 		{ | 			hexes.erase(attackOriginHex); | ||||||
| 			if(hexes.at(i) == attackOriginHex) |  | ||||||
| 			{ |  | ||||||
| 				hexes.erase(i); |  | ||||||
| 				i = 0; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		for(BattleHex tile : hexes) | 		for(BattleHex tile : hexes) | ||||||
| 		{ | 		{ | ||||||
| 			//friendly stacks can also be damaged by Dragon Breath | 			//friendly stacks can also be damaged by Dragon Breath | ||||||
| @@ -1436,7 +1429,7 @@ AttackableTiles CBattleInfoCallback::getPotentiallyShootableHexes(const battle:: | |||||||
| 	return at; | 	return at; | ||||||
| } | } | ||||||
|  |  | ||||||
| std::vector<const battle::Unit*> CBattleInfoCallback::getAttackedBattleUnits( | battle::Units CBattleInfoCallback::getAttackedBattleUnits( | ||||||
| 	const battle::Unit * attacker, | 	const battle::Unit * attacker, | ||||||
| 	const  battle::Unit * defender, | 	const  battle::Unit * defender, | ||||||
| 	BattleHex destinationTile, | 	BattleHex destinationTile, | ||||||
| @@ -1444,7 +1437,7 @@ std::vector<const battle::Unit*> CBattleInfoCallback::getAttackedBattleUnits( | |||||||
| 	BattleHex attackerPos, | 	BattleHex attackerPos, | ||||||
| 	BattleHex defenderPos) const | 	BattleHex defenderPos) const | ||||||
| { | { | ||||||
| 	std::vector<const battle::Unit*> units; | 	battle::Units units; | ||||||
| 	RETURN_IF_NOT_BATTLE(units); | 	RETURN_IF_NOT_BATTLE(units); | ||||||
|  |  | ||||||
| 	if(attackerPos == BattleHex::INVALID) | 	if(attackerPos == BattleHex::INVALID) | ||||||
| @@ -1716,18 +1709,22 @@ bool CBattleInfoCallback::battleIsUnitBlocked(const battle::Unit * unit) const | |||||||
| 	return false; | 	return false; | ||||||
| } | } | ||||||
|  |  | ||||||
| std::set<const battle::Unit *> CBattleInfoCallback::battleAdjacentUnits(const battle::Unit * unit) const | battle::Units CBattleInfoCallback::battleAdjacentUnits(const battle::Unit * unit) const | ||||||
| { | { | ||||||
| 	std::set<const battle::Unit *> ret; | 	RETURN_IF_NOT_BATTLE({}); | ||||||
| 	RETURN_IF_NOT_BATTLE(ret); |  | ||||||
|  |  | ||||||
| 	for(auto hex : unit->getSurroundingHexes()) | 	const auto & hexes = unit->getSurroundingHexes(); | ||||||
|  |  | ||||||
|  | 	const auto & units = battleGetUnitsIf([=](const battle::Unit * unit) | ||||||
| 	{ | 	{ | ||||||
| 		if(const auto * neighbour = battleGetUnitByPos(hex, true)) | 		const auto & unitHexes = unit->getHexes(); | ||||||
| 			ret.insert(neighbour); | 		for (const auto & hex : unitHexes) | ||||||
| 	} | 			if (hexes.contains(hex)) | ||||||
|  | 				return true; | ||||||
|  | 		return false; | ||||||
|  | 	}); | ||||||
|  |  | ||||||
| 	return ret; | 	return units; | ||||||
| } | } | ||||||
|  |  | ||||||
| SpellID CBattleInfoCallback::getRandomBeneficialSpell(vstd::RNG & rand, const battle::Unit * caster, const battle::Unit * subject) const | SpellID CBattleInfoCallback::getRandomBeneficialSpell(vstd::RNG & rand, const battle::Unit * caster, const battle::Unit * subject) const | ||||||
|   | |||||||
| @@ -95,7 +95,7 @@ public: | |||||||
| 	bool battleCanShoot(const battle::Unit * attacker, BattleHex dest) const; //determines if stack with given ID shoot at the selected destination | 	bool battleCanShoot(const battle::Unit * attacker, BattleHex dest) const; //determines if stack with given ID shoot at the selected destination | ||||||
| 	bool battleCanShoot(const battle::Unit * attacker) const; //determines if stack with given ID shoot in principle | 	bool battleCanShoot(const battle::Unit * attacker) const; //determines if stack with given ID shoot in principle | ||||||
| 	bool battleIsUnitBlocked(const battle::Unit * unit) const; //returns true if there is neighboring enemy stack | 	bool battleIsUnitBlocked(const battle::Unit * unit) const; //returns true if there is neighboring enemy stack | ||||||
| 	std::set<const battle::Unit *> battleAdjacentUnits(const battle::Unit * unit) const; | 	battle::Units battleAdjacentUnits(const battle::Unit * unit) const; | ||||||
|  |  | ||||||
| 	DamageEstimation calculateDmgRange(const BattleAttackInfo & info) const; | 	DamageEstimation calculateDmgRange(const BattleAttackInfo & info) const; | ||||||
|  |  | ||||||
| @@ -147,7 +147,7 @@ public: | |||||||
|  |  | ||||||
| 	AttackableTiles getPotentiallyShootableHexes(const  battle::Unit* attacker, BattleHex destinationTile, BattleHex attackerPos) const; | 	AttackableTiles getPotentiallyShootableHexes(const  battle::Unit* attacker, BattleHex destinationTile, BattleHex attackerPos) const; | ||||||
|  |  | ||||||
| 	std::vector<const battle::Unit *> getAttackedBattleUnits( | 	battle::Units getAttackedBattleUnits( | ||||||
| 		const battle::Unit* attacker, | 		const battle::Unit* attacker, | ||||||
| 		const  battle::Unit * defender, | 		const  battle::Unit * defender, | ||||||
| 		BattleHex destinationTile, | 		BattleHex destinationTile, | ||||||
| @@ -173,4 +173,4 @@ protected: | |||||||
| 	BattleHexArray getStoppers(BattleSide whichSidePerspective) const; //get hexes with stopping obstacles (quicksands) | 	BattleHexArray getStoppers(BattleSide whichSidePerspective) const; //get hexes with stopping obstacles (quicksands) | ||||||
| }; | }; | ||||||
|  |  | ||||||
| VCMI_LIB_NAMESPACE_END | VCMI_LIB_NAMESPACE_END | ||||||
|   | |||||||
| @@ -464,7 +464,7 @@ void CUnitState::getCasterName(MetaString & text) const | |||||||
| 	addNameReplacement(text, true); | 	addNameReplacement(text, true); | ||||||
| } | } | ||||||
|  |  | ||||||
| void CUnitState::getCastDescription(const spells::Spell * spell, const std::vector<const Unit *> & attacked, MetaString & text) const | void CUnitState::getCastDescription(const spells::Spell * spell, const battle::Units & attacked, MetaString & text) const | ||||||
| { | { | ||||||
| 	text.appendLocalString(EMetaText::GENERAL_TXT, 565);//The %s casts %s | 	text.appendLocalString(EMetaText::GENERAL_TXT, 565);//The %s casts %s | ||||||
| 	//todo: use text 566 for single creature | 	//todo: use text 566 for single creature | ||||||
| @@ -700,6 +700,11 @@ bool CUnitState::isHypnotized() const | |||||||
| 	return bonusCache.getBonusValue(UnitBonusValuesProxy::HYPNOTIZED); | 	return bonusCache.getBonusValue(UnitBonusValuesProxy::HYPNOTIZED); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | bool CUnitState::isInvincible() const | ||||||
|  | { | ||||||
|  | 	return bonusCache.getBonusValue(UnitBonusValuesProxy::INVINCIBLE); | ||||||
|  | } | ||||||
|  |  | ||||||
| int CUnitState::getTotalAttacks(bool ranged) const | int CUnitState::getTotalAttacks(bool ranged) const | ||||||
| { | { | ||||||
| 	return 1 + (ranged ? | 	return 1 + (ranged ? | ||||||
|   | |||||||
| @@ -183,7 +183,7 @@ public: | |||||||
| 	PlayerColor getCasterOwner() const override; | 	PlayerColor getCasterOwner() const override; | ||||||
| 	const CGHeroInstance * getHeroCaster() const override; | 	const CGHeroInstance * getHeroCaster() const override; | ||||||
| 	void getCasterName(MetaString & text) const override; | 	void getCasterName(MetaString & text) const override; | ||||||
| 	void getCastDescription(const spells::Spell * spell, const std::vector<const Unit *> & attacked, MetaString & text) const override; | 	void getCastDescription(const spells::Spell * spell, const battle::Units & attacked, MetaString & text) const override; | ||||||
| 	int32_t manaLimit() const override; | 	int32_t manaLimit() const override; | ||||||
|  |  | ||||||
| 	bool ableToRetaliate() const override; | 	bool ableToRetaliate() const override; | ||||||
| @@ -193,6 +193,7 @@ public: | |||||||
| 	bool isValidTarget(bool allowDead = false) const override; | 	bool isValidTarget(bool allowDead = false) const override; | ||||||
|  |  | ||||||
| 	bool isHypnotized() const override; | 	bool isHypnotized() const override; | ||||||
|  | 	bool isInvincible() const override; | ||||||
|  |  | ||||||
| 	bool isClone() const override; | 	bool isClone() const override; | ||||||
| 	bool hasClone() const override; | 	bool hasClone() const override; | ||||||
| @@ -269,7 +270,7 @@ private: | |||||||
| 	void reset(); | 	void reset(); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class DLL_LINKAGE CUnitStateDetached : public CUnitState | class DLL_LINKAGE CUnitStateDetached final : public CUnitState | ||||||
| { | { | ||||||
| public: | public: | ||||||
| 	explicit CUnitStateDetached(const IUnitInfo * unit_, const IBonusBearer * bonus_); | 	explicit CUnitStateDetached(const IUnitInfo * unit_, const IBonusBearer * bonus_); | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ namespace battle | |||||||
| { | { | ||||||
| 	class IUnitInfo; | 	class IUnitInfo; | ||||||
| 	class Unit; | 	class Unit; | ||||||
| 	using Units = std::vector<const Unit *>; | 	using Units = boost::container::small_vector<const Unit *, 4>; | ||||||
| 	using UnitFilter = std::function<bool(const Unit *)>; | 	using UnitFilter = std::function<bool(const Unit *)>; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -107,25 +107,51 @@ const BattleHexArray & Unit::getHexes(BattleHex assumedPos) const | |||||||
| 	return getHexes(assumedPos, doubleWide(), unitSide()); | 	return getHexes(assumedPos, doubleWide(), unitSide()); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | BattleHexArray::ArrayOfBattleHexArrays Unit::precomputeUnitHexes(BattleSide side, bool twoHex) | ||||||
|  | { | ||||||
|  | 	BattleHexArray::ArrayOfBattleHexArrays result; | ||||||
|  |  | ||||||
|  | 	for (BattleHex assumedPos = 0; assumedPos < GameConstants::BFIELD_SIZE; ++assumedPos) | ||||||
|  | 	{ | ||||||
|  | 		BattleHexArray hexes; | ||||||
|  | 		hexes.insert(assumedPos); | ||||||
|  |  | ||||||
|  | 		if(twoHex) | ||||||
|  | 			hexes.insert(occupiedHex(assumedPos, twoHex, side)); | ||||||
|  |  | ||||||
|  | 		result[assumedPos.toInt()] = std::move(hexes); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return result; | ||||||
|  | } | ||||||
|  |  | ||||||
| const BattleHexArray & Unit::getHexes(BattleHex assumedPos, bool twoHex, BattleSide side) | const BattleHexArray & Unit::getHexes(BattleHex assumedPos, bool twoHex, BattleSide side) | ||||||
| { | { | ||||||
| 	static BattleHexArray::ArrayOfBattleHexArrays precomputed[4]; | 	static const std::array<BattleHexArray::ArrayOfBattleHexArrays, 4> precomputed = { | ||||||
| 	int index = side == BattleSide::ATTACKER ? 0 : 2; | 		precomputeUnitHexes(BattleSide::ATTACKER, false), | ||||||
|  | 		precomputeUnitHexes(BattleSide::ATTACKER, true), | ||||||
|  | 		precomputeUnitHexes(BattleSide::DEFENDER, false), | ||||||
|  | 		precomputeUnitHexes(BattleSide::DEFENDER, true), | ||||||
|  | 	}; | ||||||
|  |  | ||||||
| 	if(!precomputed[index + twoHex][assumedPos.toInt()].empty()) | 	static const std::array<BattleHexArray, 5> invalidHexes = { | ||||||
|  | 		BattleHexArray({BattleHex( 0)}), | ||||||
|  | 		BattleHexArray({BattleHex(-1)}), | ||||||
|  | 		BattleHexArray({BattleHex(-2)}), | ||||||
|  | 		BattleHexArray({BattleHex(-3)}), | ||||||
|  | 		BattleHexArray({BattleHex(-4)}) | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	if (assumedPos.isValid()) | ||||||
|  | 	{ | ||||||
|  | 		int index = side == BattleSide::ATTACKER ? 0 : 2; | ||||||
| 		return precomputed[index + twoHex][assumedPos.toInt()]; | 		return precomputed[index + twoHex][assumedPos.toInt()]; | ||||||
|  | 	} | ||||||
| 	// first run, compute | 	else | ||||||
|  | 	{ | ||||||
| 	BattleHexArray hexes; | 		// Towers and such | ||||||
| 	hexes.insert(assumedPos); | 		return invalidHexes.at(-assumedPos.toInt()); | ||||||
|  | 	} | ||||||
| 	if(twoHex) |  | ||||||
| 		hexes.insert(occupiedHex(assumedPos, twoHex, side)); |  | ||||||
|  |  | ||||||
| 	precomputed[index + twoHex][assumedPos.toInt()] = std::move(hexes); |  | ||||||
|  |  | ||||||
| 	return precomputed[index + twoHex][assumedPos.toInt()]; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| BattleHex Unit::occupiedHex() const | BattleHex Unit::occupiedHex() const | ||||||
|   | |||||||
| @@ -64,6 +64,8 @@ class CUnitState; | |||||||
|  |  | ||||||
| class DLL_LINKAGE Unit : public IUnitInfo, public spells::Caster, public virtual IBonusBearer, public ACreature | class DLL_LINKAGE Unit : public IUnitInfo, public spells::Caster, public virtual IBonusBearer, public ACreature | ||||||
| { | { | ||||||
|  | 	static BattleHexArray::ArrayOfBattleHexArrays precomputeUnitHexes(BattleSide side, bool twoHex); | ||||||
|  |  | ||||||
| public: | public: | ||||||
| 	virtual ~Unit(); | 	virtual ~Unit(); | ||||||
|  |  | ||||||
| @@ -85,6 +87,7 @@ public: | |||||||
| 	virtual bool isValidTarget(bool allowDead = false) const = 0; //non-turret non-ghost stacks (can be attacked or be object of magic effect) | 	virtual bool isValidTarget(bool allowDead = false) const = 0; //non-turret non-ghost stacks (can be attacked or be object of magic effect) | ||||||
|  |  | ||||||
| 	virtual bool isHypnotized() const = 0; | 	virtual bool isHypnotized() const = 0; | ||||||
|  | 	virtual bool isInvincible() const = 0; | ||||||
|  |  | ||||||
| 	virtual bool isClone() const = 0; | 	virtual bool isClone() const = 0; | ||||||
| 	virtual bool hasClone() const = 0; | 	virtual bool hasClone() const = 0; | ||||||
|   | |||||||
| @@ -203,6 +203,7 @@ const UnitBonusValuesProxy::SelectorsArray * UnitBonusValuesProxy::generateSelec | |||||||
| 		Selector::type()(BonusType::FORGETFULL),//FORGETFULL, | 		Selector::type()(BonusType::FORGETFULL),//FORGETFULL, | ||||||
| 		Selector::type()(BonusType::FREE_SHOOTING).Or(Selector::type()(BonusType::SIEGE_WEAPON)),//HAS_FREE_SHOOTING, | 		Selector::type()(BonusType::FREE_SHOOTING).Or(Selector::type()(BonusType::SIEGE_WEAPON)),//HAS_FREE_SHOOTING, | ||||||
| 		Selector::type()(BonusType::STACK_HEALTH),//STACK_HEALTH, | 		Selector::type()(BonusType::STACK_HEALTH),//STACK_HEALTH, | ||||||
|  | 		Selector::type()(BonusType::INVINCIBLE),//INVINCIBLE, | ||||||
| 		Selector::type()(BonusType::NONE).And(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(SpellID(SpellID::CLONE)))) | 		Selector::type()(BonusType::NONE).And(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(SpellID(SpellID::CLONE)))) | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -116,6 +116,7 @@ public: | |||||||
| 		FORGETFULL, | 		FORGETFULL, | ||||||
| 		HAS_FREE_SHOOTING, | 		HAS_FREE_SHOOTING, | ||||||
| 		STACK_HEALTH, | 		STACK_HEALTH, | ||||||
|  | 		INVINCIBLE, | ||||||
|  |  | ||||||
| 		CLONE_MARKER, | 		CLONE_MARKER, | ||||||
|  |  | ||||||
|   | |||||||
| @@ -856,7 +856,7 @@ void CGHeroInstance::getCasterName(MetaString & text) const | |||||||
| 	text.replaceRawString(getNameTranslated()); | 	text.replaceRawString(getNameTranslated()); | ||||||
| } | } | ||||||
|  |  | ||||||
| void CGHeroInstance::getCastDescription(const spells::Spell * spell, const std::vector<const battle::Unit *> & attacked, MetaString & text) const | void CGHeroInstance::getCastDescription(const spells::Spell * spell, const battle::Units & attacked, MetaString & text) const | ||||||
| { | { | ||||||
| 	const bool singleTarget = attacked.size() == 1; | 	const bool singleTarget = attacked.size() == 1; | ||||||
| 	const int textIndex = singleTarget ? 195 : 196; | 	const int textIndex = singleTarget ? 195 : 196; | ||||||
|   | |||||||
| @@ -309,7 +309,7 @@ public: | |||||||
| 	const CGHeroInstance * getHeroCaster() const override; | 	const CGHeroInstance * getHeroCaster() const override; | ||||||
|  |  | ||||||
| 	void getCasterName(MetaString & text) const override; | 	void getCasterName(MetaString & text) const override; | ||||||
| 	void getCastDescription(const spells::Spell * spell, const std::vector<const battle::Unit *> & attacked, MetaString & text) const override; | 	void getCastDescription(const spells::Spell * spell, const battle::Units & attacked, MetaString & text) const override; | ||||||
| 	void spendMana(ServerCallback * server, const int spellCost) const override; | 	void spendMana(ServerCallback * server, const int spellCost) const override; | ||||||
|  |  | ||||||
| 	void attachToBoat(CGBoat* newBoat); | 	void attachToBoat(CGBoat* newBoat); | ||||||
|   | |||||||
| @@ -1503,6 +1503,11 @@ void NewObject::applyGs(CGameState *gs) | |||||||
| 	gs->map->addBlockVisTiles(newObject); | 	gs->map->addBlockVisTiles(newObject); | ||||||
| 	gs->map->calculateGuardingGreaturePositions(); | 	gs->map->calculateGuardingGreaturePositions(); | ||||||
|  |  | ||||||
|  | 	// attach newly spawned wandering monster to global bonus system node | ||||||
|  | 	auto newArmy = dynamic_cast<CArmedInstance*>(newObject); | ||||||
|  | 	if (newArmy) | ||||||
|  | 		newArmy->whatShouldBeAttached().attachTo(newArmy->whereShouldBeAttached(gs)); | ||||||
|  |  | ||||||
| 	logGlobal->debug("Added object id=%d; name=%s", newObject->id, newObject->getObjectName()); | 	logGlobal->debug("Added object id=%d; name=%s", newObject->id, newObject->getObjectName()); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -49,7 +49,7 @@ int32_t AbilityCaster::getEffectLevel(const Spell * spell) const | |||||||
| 	return getSpellSchoolLevel(spell); | 	return getSpellSchoolLevel(spell); | ||||||
| } | } | ||||||
|  |  | ||||||
| void AbilityCaster::getCastDescription(const Spell * spell, const std::vector<const battle::Unit*> & attacked, MetaString & text) const | void AbilityCaster::getCastDescription(const Spell * spell, const battle::Units & attacked, MetaString & text) const | ||||||
| { | { | ||||||
| 	//do nothing | 	//do nothing | ||||||
| } | } | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ public: | |||||||
|  |  | ||||||
| 	int32_t getSpellSchoolLevel(const Spell * spell, SpellSchool * outSelectedSchool = nullptr) const override; | 	int32_t getSpellSchoolLevel(const Spell * spell, SpellSchool * outSelectedSchool = nullptr) const override; | ||||||
| 	int32_t getEffectLevel(const Spell * spell) const override; | 	int32_t getEffectLevel(const Spell * spell) const override; | ||||||
| 	void getCastDescription(const Spell * spell, const std::vector<const battle::Unit *> & attacked, MetaString & text) const override; | 	void getCastDescription(const Spell * spell, const battle::Units & attacked, MetaString & text) const override; | ||||||
| 	void spendMana(ServerCallback * server, const int32_t spellCost) const override; | 	void spendMana(ServerCallback * server, const int32_t spellCost) const override; | ||||||
|  |  | ||||||
| private: | private: | ||||||
|   | |||||||
| @@ -231,7 +231,7 @@ bool BattleSpellMechanics::canBeCastAt(const Target & target, Problem & problem) | |||||||
| 		if(mainTarget && mainTarget == caster) | 		if(mainTarget && mainTarget == caster) | ||||||
| 			return false; // can't cast on self | 			return false; // can't cast on self | ||||||
|  |  | ||||||
| 		if(mainTarget && mainTarget->hasBonusOfType(BonusType::INVINCIBLE) && !getSpell()->getPositiveness()) | 		if(mainTarget && mainTarget->isInvincible() && !getSpell()->getPositiveness()) | ||||||
| 			return false; | 			return false; | ||||||
| 	} | 	} | ||||||
| 	else if(getSpell()->canCastOnlyOnSelf()) | 	else if(getSpell()->canCastOnlyOnSelf()) | ||||||
| @@ -259,7 +259,7 @@ std::vector<const CStack *> BattleSpellMechanics::getAffectedStacks(const Target | |||||||
|  |  | ||||||
| 	for(const Destination & dest : all) | 	for(const Destination & dest : all) | ||||||
| 	{ | 	{ | ||||||
| 		if(dest.unitValue && !dest.unitValue->hasBonusOfType(BonusType::INVINCIBLE)) | 		if(dest.unitValue && !dest.unitValue->isInvincible()) | ||||||
| 		{ | 		{ | ||||||
| 			//FIXME: remove and return battle::Unit | 			//FIXME: remove and return battle::Unit | ||||||
| 			stacks.insert(battle()->battleGetStackByID(dest.unitValue->unitId(), false)); | 			stacks.insert(battle()->battleGetStackByID(dest.unitValue->unitId(), false)); | ||||||
| @@ -473,7 +473,7 @@ std::set<const battle::Unit *> BattleSpellMechanics::collectTargets() const | |||||||
| 	return result; | 	return result; | ||||||
| } | } | ||||||
|  |  | ||||||
| void BattleSpellMechanics::doRemoveEffects(ServerCallback * server, const std::vector<const battle::Unit *> & targets, const CSelector & selector) | void BattleSpellMechanics::doRemoveEffects(ServerCallback * server, const battle::Units & targets, const CSelector & selector) | ||||||
| { | { | ||||||
| 	SetStackEffect sse; | 	SetStackEffect sse; | ||||||
| 	sse.battleID = battle()->getBattle()->getBattleID(); | 	sse.battleID = battle()->getBattle()->getBattleID(); | ||||||
|   | |||||||
| @@ -18,6 +18,11 @@ VCMI_LIB_NAMESPACE_BEGIN | |||||||
|  |  | ||||||
| struct BattleSpellCast; | struct BattleSpellCast; | ||||||
|  |  | ||||||
|  | namespace battle | ||||||
|  | { | ||||||
|  | 	using Units = boost::container::small_vector<const Unit *, 4>; | ||||||
|  | } | ||||||
|  |  | ||||||
| namespace spells | namespace spells | ||||||
| { | { | ||||||
|  |  | ||||||
| @@ -66,14 +71,14 @@ private: | |||||||
| 	std::shared_ptr<effects::Effects> effects; | 	std::shared_ptr<effects::Effects> effects; | ||||||
| 	std::shared_ptr<IReceptiveCheck> targetCondition; | 	std::shared_ptr<IReceptiveCheck> targetCondition; | ||||||
|  |  | ||||||
| 	std::vector<const battle::Unit *> affectedUnits; | 	battle::Units affectedUnits; | ||||||
| 	effects::Effects::EffectsToApply effectsToApply; | 	effects::Effects::EffectsToApply effectsToApply; | ||||||
|  |  | ||||||
| 	void beforeCast(BattleSpellCast & sc, vstd::RNG & rng, const Target & target); | 	void beforeCast(BattleSpellCast & sc, vstd::RNG & rng, const Target & target); | ||||||
|  |  | ||||||
| 	std::set<const battle::Unit *> collectTargets() const; | 	std::set<const battle::Unit *> collectTargets() const; | ||||||
|  |  | ||||||
| 	void doRemoveEffects(ServerCallback * server, const std::vector<const battle::Unit *> & targets, const CSelector & selector); | 	void doRemoveEffects(ServerCallback * server, const battle::Units & targets, const CSelector & selector); | ||||||
|  |  | ||||||
| 	BattleHexArray spellRangeInHexes(BattleHex centralHex) const; | 	BattleHexArray spellRangeInHexes(BattleHex centralHex) const; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -57,7 +57,7 @@ void BonusCaster::getCasterName(MetaString & text) const | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| void BonusCaster::getCastDescription(const Spell * spell, const std::vector<const battle::Unit*> & attacked, MetaString & text) const | void BonusCaster::getCastDescription(const Spell * spell, const battle::Units & attacked, MetaString & text) const | ||||||
| { | { | ||||||
| 	const bool singleTarget = attacked.size() == 1; | 	const bool singleTarget = attacked.size() == 1; | ||||||
| 	const int textIndex = singleTarget ? 195 : 196; | 	const int textIndex = singleTarget ? 195 : 196; | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ public: | |||||||
| 	virtual ~BonusCaster(); | 	virtual ~BonusCaster(); | ||||||
|  |  | ||||||
| 	void getCasterName(MetaString & text) const override; | 	void getCasterName(MetaString & text) const override; | ||||||
| 	void getCastDescription(const Spell * spell, const std::vector<const battle::Unit *> & attacked, MetaString & text) const override; | 	void getCastDescription(const Spell * spell, const battle::Units & attacked, MetaString & text) const override; | ||||||
| 	void spendMana(ServerCallback * server, const int spellCost) const override; | 	void spendMana(ServerCallback * server, const int spellCost) const override; | ||||||
|  |  | ||||||
| private: | private: | ||||||
|   | |||||||
| @@ -434,7 +434,7 @@ int64_t CSpell::adjustRawDamage(const spells::Caster * caster, const battle::Uni | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		//invincible | 		//invincible | ||||||
| 		if(bearer->hasBonusOfType(BonusType::INVINCIBLE)) | 		if(affectedCreature->isInvincible()) | ||||||
| 			ret = 0; | 			ret = 0; | ||||||
| 	} | 	} | ||||||
| 	ret = caster->getSpellBonus(this, ret, affectedCreature); | 	ret = caster->getSpellBonus(this, ret, affectedCreature); | ||||||
|   | |||||||
| @@ -75,7 +75,7 @@ void SilentCaster::getCasterName(MetaString & text) const | |||||||
| 	logGlobal->debug("Unexpected call to SilentCaster::getCasterName"); | 	logGlobal->debug("Unexpected call to SilentCaster::getCasterName"); | ||||||
| } | } | ||||||
|  |  | ||||||
| void SilentCaster::getCastDescription(const Spell * spell, const std::vector<const battle::Unit *> & attacked, MetaString & text) const | void SilentCaster::getCastDescription(const Spell * spell, const battle::Units & attacked, MetaString & text) const | ||||||
| { | { | ||||||
| 		//do nothing | 		//do nothing | ||||||
| } | } | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ public: | |||||||
| 	SilentCaster(PlayerColor owner_, const Caster * caster); | 	SilentCaster(PlayerColor owner_, const Caster * caster); | ||||||
|  |  | ||||||
| 	void getCasterName(MetaString & text) const override; | 	void getCasterName(MetaString & text) const override; | ||||||
| 	void getCastDescription(const Spell * spell, const std::vector<const battle::Unit *> & attacked, MetaString & text) const override; | 	void getCastDescription(const Spell * spell, const battle::Units & attacked, MetaString & text) const override; | ||||||
| 	void spendMana(ServerCallback * server, const int spellCost) const override; | 	void spendMana(ServerCallback * server, const int spellCost) const override; | ||||||
| 	PlayerColor getCasterOwner() const override; | 	PlayerColor getCasterOwner() const override; | ||||||
| 	int32_t manaLimit() const override; | 	int32_t manaLimit() const override; | ||||||
|   | |||||||
| @@ -106,7 +106,7 @@ void ProxyCaster::getCasterName(MetaString & text) const | |||||||
| 		actualCaster->getCasterName(text); | 		actualCaster->getCasterName(text); | ||||||
| } | } | ||||||
|  |  | ||||||
| void ProxyCaster::getCastDescription(const Spell * spell, const std::vector<const battle::Unit*> & attacked, MetaString & text) const | void ProxyCaster::getCastDescription(const Spell * spell, const battle::Units & attacked, MetaString & text) const | ||||||
| { | { | ||||||
| 	if(actualCaster) | 	if(actualCaster) | ||||||
| 		actualCaster->getCastDescription(spell, attacked, text); | 		actualCaster->getCastDescription(spell, attacked, text); | ||||||
|   | |||||||
| @@ -33,7 +33,7 @@ public: | |||||||
| 	int64_t getEffectValue(const Spell * spell) const override; | 	int64_t getEffectValue(const Spell * spell) const override; | ||||||
| 	PlayerColor getCasterOwner() const override; | 	PlayerColor getCasterOwner() const override; | ||||||
| 	void getCasterName(MetaString & text) const override; | 	void getCasterName(MetaString & text) const override; | ||||||
| 	void getCastDescription(const Spell * spell, const std::vector<const battle::Unit *> & attacked, MetaString & text) const override; | 	void getCastDescription(const Spell * spell, const battle::Units & attacked, MetaString & text) const override; | ||||||
| 	void spendMana(ServerCallback * server, const int32_t spellCost) const override; | 	void spendMana(ServerCallback * server, const int32_t spellCost) const override; | ||||||
| 	const CGHeroInstance * getHeroCaster() const override; | 	const CGHeroInstance * getHeroCaster() const override; | ||||||
| 	int32_t manaLimit() const override; | 	int32_t manaLimit() const override; | ||||||
|   | |||||||
| @@ -223,7 +223,8 @@ EffectTarget UnitEffect::transformTargetByChain(const Mechanics * m, const Targe | |||||||
| 			effectTarget.emplace_back(); | 			effectTarget.emplace_back(); | ||||||
|  |  | ||||||
| 		for(auto hex : battle::Unit::getHexes(unit->getPosition(), unit->doubleWide(), unit->unitSide())) | 		for(auto hex : battle::Unit::getHexes(unit->getPosition(), unit->doubleWide(), unit->unitSide())) | ||||||
| 			possibleHexes.erase(hex.toInt()); | 			if (possibleHexes.contains(hex)) | ||||||
|  | 				possibleHexes.erase(hex); | ||||||
|  |  | ||||||
| 		if(possibleHexes.empty()) | 		if(possibleHexes.empty()) | ||||||
| 			break; | 			break; | ||||||
| @@ -278,4 +279,4 @@ void UnitEffect::serializeJsonEffect(JsonSerializeFormat & handler) | |||||||
| } | } | ||||||
| } | } | ||||||
|  |  | ||||||
| VCMI_LIB_NAMESPACE_END | VCMI_LIB_NAMESPACE_END | ||||||
|   | |||||||
| @@ -276,7 +276,7 @@ bool BattleActionProcessor::doAttackAction(const CBattleInfoCallback & battle, c | |||||||
| 	for (int i = 0; i < totalAttacks; ++i) | 	for (int i = 0; i < totalAttacks; ++i) | ||||||
| 	{ | 	{ | ||||||
| 		//first strike | 		//first strike | ||||||
| 		if(i == 0 && firstStrike && retaliation && !stack->hasBonusOfType(BonusType::BLOCKS_RETALIATION) && !stack->hasBonusOfType(BonusType::INVINCIBLE)) | 		if(i == 0 && firstStrike && retaliation && !stack->hasBonusOfType(BonusType::BLOCKS_RETALIATION) && !stack->isInvincible()) | ||||||
| 		{ | 		{ | ||||||
| 			makeAttack(battle, destinationStack, stack, 0, stack->getPosition(), true, false, true); | 			makeAttack(battle, destinationStack, stack, 0, stack->getPosition(), true, false, true); | ||||||
| 		} | 		} | ||||||
| @@ -303,7 +303,7 @@ bool BattleActionProcessor::doAttackAction(const CBattleInfoCallback & battle, c | |||||||
| 		//we check retaliation twice, so if it unblocked during attack it will work only on next attack | 		//we check retaliation twice, so if it unblocked during attack it will work only on next attack | ||||||
| 		if(stack->alive() | 		if(stack->alive() | ||||||
| 			&& !stack->hasBonusOfType(BonusType::BLOCKS_RETALIATION) | 			&& !stack->hasBonusOfType(BonusType::BLOCKS_RETALIATION) | ||||||
| 			&& !stack->hasBonusOfType(BonusType::INVINCIBLE) | 			&& !stack->isInvincible() | ||||||
| 			&& (i == 0 && !firstStrike) | 			&& (i == 0 && !firstStrike) | ||||||
| 			&& retaliation && destinationStack->ableToRetaliate()) | 			&& retaliation && destinationStack->ableToRetaliate()) | ||||||
| 		{ | 		{ | ||||||
|   | |||||||
| @@ -76,6 +76,11 @@ public: | |||||||
| 		return hasBonusOfType(BonusType::HYPNOTIZED); | 		return hasBonusOfType(BonusType::HYPNOTIZED); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	bool isInvincible() const override | ||||||
|  | 	{ | ||||||
|  | 		return hasBonusOfType(BonusType::INVINCIBLE); | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	void redirectBonusesToFake() | 	void redirectBonusesToFake() | ||||||
| 	{ | 	{ | ||||||
| 		ON_CALL(*this, getAllBonuses(_, _, _)).WillByDefault(Invoke(&bonusFake, &BonusBearerMock::getAllBonuses)); | 		ON_CALL(*this, getAllBonuses(_, _, _)).WillByDefault(Invoke(&bonusFake, &BonusBearerMock::getAllBonuses)); | ||||||
|   | |||||||
| @@ -28,7 +28,7 @@ public: | |||||||
| 	MOCK_CONST_METHOD1(getEffectValue, int64_t(const spells::Spell *)); | 	MOCK_CONST_METHOD1(getEffectValue, int64_t(const spells::Spell *)); | ||||||
| 	MOCK_CONST_METHOD0(getCasterOwner, PlayerColor()); | 	MOCK_CONST_METHOD0(getCasterOwner, PlayerColor()); | ||||||
| 	MOCK_CONST_METHOD1(getCasterName, void(MetaString &)); | 	MOCK_CONST_METHOD1(getCasterName, void(MetaString &)); | ||||||
| 	MOCK_CONST_METHOD3(getCastDescription, void(const spells::Spell *, const std::vector<const battle::Unit *> &, MetaString &)); | 	MOCK_CONST_METHOD3(getCastDescription, void(const spells::Spell *, const battle::Units &, MetaString &)); | ||||||
| 	MOCK_CONST_METHOD2(spendMana, void(ServerCallback *, const int32_t)); | 	MOCK_CONST_METHOD2(spendMana, void(ServerCallback *, const int32_t)); | ||||||
| 	MOCK_CONST_METHOD0(manaLimit, int32_t()); | 	MOCK_CONST_METHOD0(manaLimit, int32_t()); | ||||||
| 	MOCK_CONST_METHOD0(getHeroCaster, CGHeroInstance*()); | 	MOCK_CONST_METHOD0(getHeroCaster, CGHeroInstance*()); | ||||||
| @@ -58,6 +58,7 @@ public: | |||||||
| 	MOCK_CONST_METHOD1(isValidTarget, bool(bool)); | 	MOCK_CONST_METHOD1(isValidTarget, bool(bool)); | ||||||
|  |  | ||||||
| 	MOCK_CONST_METHOD0(isHypnotized, bool()); | 	MOCK_CONST_METHOD0(isHypnotized, bool()); | ||||||
|  | 	MOCK_CONST_METHOD0(isInvincible, bool()); | ||||||
| 	MOCK_CONST_METHOD0(isClone, bool()); | 	MOCK_CONST_METHOD0(isClone, bool()); | ||||||
| 	MOCK_CONST_METHOD0(hasClone, bool()); | 	MOCK_CONST_METHOD0(hasClone, bool()); | ||||||
| 	MOCK_CONST_METHOD0(canCast, bool()); | 	MOCK_CONST_METHOD0(canCast, bool()); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user