| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221 | 
							- /*
 
-  * AttackPossibility.cpp, part of VCMI engine
 
-  *
 
-  * Authors: listed in file AUTHORS in main folder
 
-  *
 
-  * License: GNU General Public License v2.0 or later
 
-  * Full text of license available in license.txt file, in main folder
 
-  *
 
-  */
 
- #include "StdInc.h"
 
- #include "AttackPossibility.h"
 
- #include "../../lib/CStack.h" // TODO: remove
 
-                               // Eventually only IBattleInfoCallback and battle::Unit should be used, 
 
-                               // CUnitState should be private and CStack should be removed completely
 
- uint64_t averageDmg(const TDmgRange & range)
 
- {
 
- 	return (range.first + range.second) / 2;
 
- }
 
- AttackPossibility::AttackPossibility(BattleHex from, BattleHex dest, const BattleAttackInfo & attack)
 
- 	: from(from), dest(dest), attack(attack)
 
- {
 
- }
 
- int64_t AttackPossibility::damageDiff() const
 
- {
 
- 	return defenderDamageReduce - attackerDamageReduce - collateralDamageReduce + shootersBlockedDmg;
 
- }
 
- int64_t AttackPossibility::attackValue() const
 
- {
 
- 	return damageDiff();
 
- }
 
- /// <summary>
 
- /// How enemy damage will be reduced by this attack
 
- /// Half bounty for kill, half for making damage equal to enemy health
 
- /// Bounty - the killed creature average damage calculated against attacker
 
- /// </summary>
 
- int64_t AttackPossibility::calculateDamageReduce(
 
- 	const battle::Unit * attacker,
 
- 	const battle::Unit * defender,
 
- 	uint64_t damageDealt,
 
- 	const CBattleInfoCallback & cb)
 
- {
 
- 	const float HEALTH_BOUNTY = 0.5;
 
- 	const float KILL_BOUNTY = 1.0 - HEALTH_BOUNTY;
 
- 	vstd::amin(damageDealt, defender->getAvailableHealth());
 
- 	auto enemyDamageBeforeAttack = cb.battleEstimateDamage(BattleAttackInfo(defender, attacker, defender->canShoot()));
 
- 	auto enemiesKilled = damageDealt / defender->MaxHealth() + (damageDealt % defender->MaxHealth() >= defender->getFirstHPleft() ? 1 : 0);
 
- 	auto enemyDamage = averageDmg(enemyDamageBeforeAttack);
 
- 	auto damagePerEnemy = enemyDamage / (double)defender->getCount();
 
- 	return (int64_t)(damagePerEnemy * (enemiesKilled * KILL_BOUNTY + damageDealt * HEALTH_BOUNTY / (double)defender->MaxHealth()));
 
- }
 
- int64_t AttackPossibility::evaluateBlockedShootersDmg(const BattleAttackInfo & attackInfo, BattleHex hex, const HypotheticBattle & state)
 
- {
 
- 	int64_t res = 0;
 
- 	if(attackInfo.shooting)
 
- 		return 0;
 
- 	auto attacker = attackInfo.attacker;
 
- 	auto hexes = attacker->getSurroundingHexes(hex);
 
- 	for(BattleHex tile : hexes)
 
- 	{
 
- 		auto st = state.battleGetUnitByPos(tile, true);
 
- 		if(!st || !state.battleMatchOwner(st, attacker))
 
- 			continue;
 
- 		if(!state.battleCanShoot(st))
 
- 			continue;
 
- 		BattleAttackInfo rangeAttackInfo(st, attacker, true);
 
- 		rangeAttackInfo.defenderPos = hex;
 
- 		BattleAttackInfo meleeAttackInfo(st, attacker, false);
 
- 		meleeAttackInfo.defenderPos = hex;
 
- 		auto rangeDmg = state.battleEstimateDamage(rangeAttackInfo);
 
- 		auto meleeDmg = state.battleEstimateDamage(meleeAttackInfo);
 
- 		int64_t gain = averageDmg(rangeDmg) - averageDmg(meleeDmg) + 1;
 
- 		res += gain;
 
- 	}
 
- 	return res;
 
- }
 
- AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInfo, BattleHex hex, const HypotheticBattle & state)
 
- {
 
- 	auto attacker = attackInfo.attacker;
 
- 	auto defender = attackInfo.defender;
 
- 	const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION";
 
- 	static const auto selectorBlocksRetaliation = Selector::type()(Bonus::BLOCKS_RETALIATION);
 
- 	const auto attackerSide = state.playerToSide(state.battleGetOwner(attacker));
 
- 	const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);
 
- 	AttackPossibility bestAp(hex, BattleHex::INVALID, attackInfo);
 
- 	std::vector<BattleHex> defenderHex;
 
- 	if(attackInfo.shooting)
 
- 		defenderHex = defender->getHexes();
 
- 	else
 
- 		defenderHex = CStack::meleeAttackHexes(attacker, defender, hex);
 
- 	for(BattleHex defHex : defenderHex)
 
- 	{
 
- 		if(defHex == hex) // should be impossible but check anyway
 
- 			continue;
 
- 		AttackPossibility ap(hex, defHex, attackInfo);
 
- 		ap.attackerState = attacker->acquireState();
 
- 		ap.shootersBlockedDmg = bestAp.shootersBlockedDmg;
 
- 		const int totalAttacks = ap.attackerState->getTotalAttacks(attackInfo.shooting);
 
- 		if (!attackInfo.shooting)
 
- 			ap.attackerState->setPosition(hex);
 
- 		std::vector<const battle::Unit*> units;
 
- 		if (attackInfo.shooting)
 
- 			units = state.getAttackedBattleUnits(attacker, defHex, true, BattleHex::INVALID);
 
- 		else
 
- 			units = state.getAttackedBattleUnits(attacker, defHex, false, hex);
 
- 		// ensure the defender is also affected
 
- 		bool addDefender = true;
 
- 		for(auto unit : units)
 
- 		{
 
- 			if (unit->unitId() == defender->unitId())
 
- 			{
 
- 				addDefender = false;
 
- 				break;
 
- 			}
 
- 		}
 
- 		if(addDefender)
 
- 			units.push_back(defender);
 
- 		for(auto u : units)
 
- 		{
 
- 			if(!ap.attackerState->alive())
 
- 				break;
 
- 			auto defenderState = u->acquireState();
 
- 			ap.affectedUnits.push_back(defenderState);
 
- 			for(int i = 0; i < totalAttacks; i++)
 
- 			{
 
- 				int64_t damageDealt, damageReceived, defenderDamageReduce, attackerDamageReduce;
 
- 				TDmgRange retaliation(0, 0);
 
- 				auto attackDmg = state.battleEstimateDamage(ap.attack, &retaliation);
 
- 				vstd::amin(attackDmg.first, defenderState->getAvailableHealth());
 
- 				vstd::amin(attackDmg.second, defenderState->getAvailableHealth());
 
- 				vstd::amin(retaliation.first, ap.attackerState->getAvailableHealth());
 
- 				vstd::amin(retaliation.second, ap.attackerState->getAvailableHealth());
 
- 				damageDealt = averageDmg(attackDmg);
 
- 				defenderDamageReduce = calculateDamageReduce(attacker, defender, damageDealt, state);
 
- 				ap.attackerState->afterAttack(attackInfo.shooting, false);
 
- 				//FIXME: use ranged retaliation
 
- 				damageReceived = 0;
 
- 				attackerDamageReduce = 0;
 
- 				if (!attackInfo.shooting && defenderState->ableToRetaliate() && !counterAttacksBlocked)
 
- 				{
 
- 					damageReceived = averageDmg(retaliation);
 
- 					attackerDamageReduce = calculateDamageReduce(defender, attacker, damageReceived, state);
 
- 					defenderState->afterAttack(attackInfo.shooting, true);
 
- 				}
 
- 				bool isEnemy = state.battleMatchOwner(attacker, u);
 
- 				// this includes enemy units as well as attacker units under enemy's mind control
 
- 				if(isEnemy)
 
- 					ap.defenderDamageReduce += defenderDamageReduce;
 
- 				// damaging attacker's units (even those under enemy's mind control) is considered friendly fire
 
- 				if(attackerSide == u->unitSide())
 
- 					ap.collateralDamageReduce += defenderDamageReduce;
 
- 				if(u->unitId() == defender->unitId() || 
 
- 					(!attackInfo.shooting && CStack::isMeleeAttackPossible(u, attacker, hex)))
 
- 				{
 
- 					//FIXME: handle RANGED_RETALIATION ?
 
- 					ap.attackerDamageReduce += attackerDamageReduce;
 
- 				}
 
- 				ap.attackerState->damage(damageReceived);
 
- 				defenderState->damage(damageDealt);
 
- 				if (!ap.attackerState->alive() || !defenderState->alive())
 
- 					break;
 
- 			}
 
- 		}
 
- 		if(!bestAp.dest.isValid() || ap.attackValue() > bestAp.attackValue())
 
- 			bestAp = ap;
 
- 	}
 
- 	// check how much damage we gain from blocking enemy shooters on this hex
 
- 	bestAp.shootersBlockedDmg = evaluateBlockedShootersDmg(attackInfo, hex, state);
 
- 	logAi->debug("BattleAI best AP: %s -> %s at %d from %d, affects %d units: d:%lld a:%lld c:%lld s:%lld",
 
- 		attackInfo.attacker->unitType()->identifier,
 
- 		attackInfo.defender->unitType()->identifier,
 
- 		(int)bestAp.dest, (int)bestAp.from, (int)bestAp.affectedUnits.size(),
 
- 		bestAp.defenderDamageReduce, bestAp.attackerDamageReduce, bestAp.collateralDamageReduce, bestAp.shootersBlockedDmg);
 
- 	//TODO other damage related to attack (eg. fire shield and other abilities)
 
- 	return bestAp;
 
- }
 
 
  |