mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Show information on potential kills in attack tooltip
This commit is contained in:
		| @@ -52,7 +52,7 @@ int64_t AttackPossibility::calculateDamageReduce( | ||||
| 	// FIXME: provide distance info for Jousting bonus | ||||
| 	auto enemyDamageBeforeAttack = cb.battleEstimateDamage(defender, attacker, 0); | ||||
| 	auto enemiesKilled = damageDealt / defender->MaxHealth() + (damageDealt % defender->MaxHealth() >= defender->getFirstHPleft() ? 1 : 0); | ||||
| 	auto enemyDamage = averageDmg(enemyDamageBeforeAttack); | ||||
| 	auto enemyDamage = averageDmg(enemyDamageBeforeAttack.damage); | ||||
| 	auto damagePerEnemy = enemyDamage / (double)defender->getCount(); | ||||
|  | ||||
| 	return (int64_t)(damagePerEnemy * (enemiesKilled * KILL_BOUNTY + damageDealt * HEALTH_BOUNTY / (double)defender->MaxHealth())); | ||||
| @@ -85,7 +85,7 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg(const BattleAttackInfo & a | ||||
| 		auto rangeDmg = state.battleEstimateDamage(rangeAttackInfo); | ||||
| 		auto meleeDmg = state.battleEstimateDamage(meleeAttackInfo); | ||||
|  | ||||
| 		int64_t gain = averageDmg(rangeDmg) - averageDmg(meleeDmg) + 1; | ||||
| 		int64_t gain = averageDmg(rangeDmg.damage) - averageDmg(meleeDmg.damage) + 1; | ||||
| 		res += gain; | ||||
| 	} | ||||
|  | ||||
| @@ -156,16 +156,16 @@ AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInf | ||||
| 			{ | ||||
| 				int64_t damageDealt, damageReceived, defenderDamageReduce, attackerDamageReduce; | ||||
|  | ||||
| 				DamageRange retaliation; | ||||
| 				DamageEstimation retaliation; | ||||
| 				auto attackDmg = state.battleEstimateDamage(ap.attack, &retaliation); | ||||
|  | ||||
| 				vstd::amin(attackDmg.min, defenderState->getAvailableHealth()); | ||||
| 				vstd::amin(attackDmg.max, defenderState->getAvailableHealth()); | ||||
| 				vstd::amin(attackDmg.damage.min, defenderState->getAvailableHealth()); | ||||
| 				vstd::amin(attackDmg.damage.max, defenderState->getAvailableHealth()); | ||||
|  | ||||
| 				vstd::amin(retaliation.min, ap.attackerState->getAvailableHealth()); | ||||
| 				vstd::amin(retaliation.max, ap.attackerState->getAvailableHealth()); | ||||
| 				vstd::amin(retaliation.damage.min, ap.attackerState->getAvailableHealth()); | ||||
| 				vstd::amin(retaliation.damage.max, ap.attackerState->getAvailableHealth()); | ||||
|  | ||||
| 				damageDealt = averageDmg(attackDmg); | ||||
| 				damageDealt = averageDmg(attackDmg.damage); | ||||
| 				defenderDamageReduce = calculateDamageReduce(attacker, defender, damageDealt, state); | ||||
| 				ap.attackerState->afterAttack(attackInfo.shooting, false); | ||||
|  | ||||
| @@ -175,7 +175,7 @@ AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInf | ||||
|  | ||||
| 				if (!attackInfo.shooting && defenderState->ableToRetaliate() && !counterAttacksBlocked) | ||||
| 				{ | ||||
| 					damageReceived = averageDmg(retaliation); | ||||
| 					damageReceived = averageDmg(retaliation.damage); | ||||
| 					attackerDamageReduce = calculateDamageReduce(defender, attacker, damageReceived, state); | ||||
| 					defenderState->afterAttack(attackInfo.shooting, true); | ||||
| 				} | ||||
|   | ||||
| @@ -68,7 +68,7 @@ int64_t BattleExchangeVariant::trackAttack( | ||||
| 	static const auto selectorBlocksRetaliation = Selector::type()(Bonus::BLOCKS_RETALIATION); | ||||
| 	const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation); | ||||
|  | ||||
| 	DamageRange retaliation; | ||||
| 	DamageEstimation retaliation; | ||||
| 	// FIXME: provide distance info for Jousting bonus | ||||
| 	BattleAttackInfo bai(attacker.get(), defender.get(), 0, shooting); | ||||
|  | ||||
| @@ -78,7 +78,7 @@ int64_t BattleExchangeVariant::trackAttack( | ||||
| 	} | ||||
|  | ||||
| 	auto attack = cb.battleEstimateDamage(bai, &retaliation); | ||||
| 	int64_t attackDamage = (attack.min + attack.max) / 2; | ||||
| 	int64_t attackDamage = (attack.damage.min + attack.damage.max) / 2; | ||||
| 	int64_t defenderDamageReduce = AttackPossibility::calculateDamageReduce(attacker.get(), defender.get(), attackDamage, cb); | ||||
| 	int64_t attackerDamageReduce = 0; | ||||
|  | ||||
| @@ -108,9 +108,9 @@ int64_t BattleExchangeVariant::trackAttack( | ||||
|  | ||||
| 	if(defender->alive() && defender->ableToRetaliate() && !counterAttacksBlocked && !shooting) | ||||
| 	{ | ||||
| 		if(retaliation.max != 0) | ||||
| 		if(retaliation.damage.max != 0) | ||||
| 		{ | ||||
| 			auto retaliationDamage = (retaliation.min + retaliation.max) / 2; | ||||
| 			auto retaliationDamage = (retaliation.damage.min + retaliation.damage.max) / 2; | ||||
| 			attackerDamageReduce = AttackPossibility::calculateDamageReduce(defender.get(), attacker.get(), retaliationDamage, cb); | ||||
|  | ||||
| 			if(!evaluateOnly) | ||||
|   | ||||
| @@ -56,9 +56,10 @@ public: | ||||
| 	void calcDmg(const CStack * ourStack) | ||||
| 	{ | ||||
| 		// FIXME: provide distance info for Jousting bonus | ||||
| 		DamageRange retal, dmg = cbc->battleEstimateDamage(ourStack, s, 0, &retal); | ||||
| 		adi = static_cast<int>((dmg.min + dmg.max) / 2); | ||||
| 		adr = static_cast<int>((retal.min + retal.max) / 2); | ||||
| 		DamageEstimation retal; | ||||
| 		DamageEstimation dmg = cbc->battleEstimateDamage(ourStack, s, 0, &retal); | ||||
| 		adi = static_cast<int>((dmg.damage.min + dmg.damage.max) / 2); | ||||
| 		adr = static_cast<int>((retal.damage.min + retal.damage.max) / 2); | ||||
| 	} | ||||
|  | ||||
| 	bool operator==(const EnemyInfo& ei) const | ||||
|   | ||||
| @@ -84,6 +84,17 @@ | ||||
| 	 | ||||
| 	"vcmi.battleWindow.pressKeyToSkipIntro" : "Press any key to skip battle intro", | ||||
|  | ||||
| 	"vcmi.battleWindow.damageEstimation.melee" : "Attack %CREATURE (%DAMAGE).", | ||||
| 	"vcmi.battleWindow.damageEstimation.meleeKills" : "Attack %CREATURE (%DAMAGE, %KILLS).", | ||||
| 	"vcmi.battleWindow.damageEstimation.ranged" : "Shoot %CREATURE (%SHOTS, %DAMAGE).", | ||||
| 	"vcmi.battleWindow.damageEstimation.rangedKills" : "Shoot %CREATURE (%SHOTS, %DAMAGE, %KILLS).", | ||||
| 	"vcmi.battleWindow.damageEstimation.shots" : "%d shots left", | ||||
| 	"vcmi.battleWindow.damageEstimation.shots.1" : "%d shot left", | ||||
| 	"vcmi.battleWindow.damageEstimation.damage" : "%d damage", | ||||
| 	"vcmi.battleWindow.damageEstimation.damage.1" : "%d damage", | ||||
| 	"vcmi.battleWindow.damageEstimation.kills" : "%d will perish", | ||||
| 	"vcmi.battleWindow.damageEstimation.kills.1" : "%d will perish", | ||||
|  | ||||
| 	"vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Show Available Creatures", | ||||
| 	"vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Show Available Creatures}\n\n Shows creatures available to purchase instead of their growth in town summary (bottom-left corner).", | ||||
| 	"vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover" : "Show Weekly Growth of Creatures", | ||||
|   | ||||
| @@ -84,6 +84,17 @@ | ||||
| 	"vcmi.battleOptions.skipBattleIntroMusic.help": "{Пропускати вступну музику}\n\n Пропускати коротку музику, яка грає на початку кожної битви перед початком дії. Також можна пропустити, натиснувши клавішу ESC.", | ||||
| 	 | ||||
| 	"vcmi.battleWindow.pressKeyToSkipIntro" : "Натисніть будь-яку клавішу, щоб розпочати бій", | ||||
| 	 | ||||
| 	"vcmi.battleWindow.damageEstimation.melee" : "Атакувати %CREATURE (%DAMAGE).", | ||||
| 	"vcmi.battleWindow.damageEstimation.meleeKills" : "Атакувати %CREATURE (%DAMAGE, %KILLS).", | ||||
| 	"vcmi.battleWindow.damageEstimation.ranged" : "Стріляти в %CREATURE (%SHOTS, %DAMAGE).", | ||||
| 	"vcmi.battleWindow.damageEstimation.rangedKills" : "Стріляти в %CREATURE (%SHOTS, %DAMAGE, %KILLS).", | ||||
| 	"vcmi.battleWindow.damageEstimation.shots" : "%d пострілів залишилось", | ||||
| 	"vcmi.battleWindow.damageEstimation.shots.1" : "%d постріл залишився", | ||||
| 	"vcmi.battleWindow.damageEstimation.damage" : "%d одиниць пошкоджень", | ||||
| 	"vcmi.battleWindow.damageEstimation.damage.1" : "%d одиниця пошкодження", | ||||
| 	"vcmi.battleWindow.damageEstimation.kills" : "%d загинуть", | ||||
| 	"vcmi.battleWindow.damageEstimation.kills.1" : "%d загине", | ||||
|  | ||||
| 	"vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Показувати доступних істот", | ||||
| 	"vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Показувати доступних істот}\n\n Показує істот, яких можна придбати, замість їхнього приросту у зведенні по місту (нижній лівий кут).", | ||||
|   | ||||
| @@ -25,19 +25,90 @@ | ||||
| #include "../windows/CCreatureWindow.h" | ||||
|  | ||||
| #include "../../CCallback.h" | ||||
| #include "../../lib/CConfigHandler.h" | ||||
| #include "../../lib/CGeneralTextHandler.h" | ||||
| #include "../../lib/CStack.h" | ||||
| #include "../../lib/battle/BattleAction.h" | ||||
| #include "../../lib/spells/CSpellHandler.h" | ||||
| #include "../../lib/spells/ISpellMechanics.h" | ||||
| #include "../../lib/spells/Problem.h" | ||||
| #include "../../lib/CGeneralTextHandler.h" | ||||
|  | ||||
| static std::string formatDmgRange(DamageRange dmgRange) | ||||
| struct TextReplacement | ||||
| { | ||||
| 	if (dmgRange.min != dmgRange.max) | ||||
| 		return (boost::format("%d - %d") % dmgRange.min % dmgRange.max).str(); | ||||
| 	else | ||||
| 		return (boost::format("%d") % dmgRange.min).str(); | ||||
| 	std::string placeholder; | ||||
| 	std::string replacement; | ||||
| }; | ||||
|  | ||||
| using TextReplacementList = std::vector<TextReplacement>; | ||||
|  | ||||
| static std::string replacePlaceholders(std::string input, const TextReplacementList & format ) | ||||
| { | ||||
| 	for(const auto & entry : format) | ||||
| 		boost::replace_all(input, entry.placeholder, entry.replacement); | ||||
|  | ||||
| 	return input; | ||||
| } | ||||
|  | ||||
| static std::string translatePlural(int amount, const std::string& baseTextID) | ||||
| { | ||||
| 	if(amount == 1) | ||||
| 		return CGI->generaltexth->translate(baseTextID + ".1"); | ||||
| 	return CGI->generaltexth->translate(baseTextID); | ||||
| } | ||||
|  | ||||
| static std::string formatPluralImpl(int amount, const std::string & amountString, const std::string & baseTextID) | ||||
| { | ||||
| 	std::string baseString = translatePlural(amount, baseTextID); | ||||
| 	TextReplacementList replacements { | ||||
| 		{ "%d", amountString } | ||||
| 	}; | ||||
|  | ||||
| 	return replacePlaceholders(baseString, replacements); | ||||
| } | ||||
|  | ||||
| static std::string formatPlural(int amount, const std::string & baseTextID) | ||||
| { | ||||
| 	return formatPluralImpl(amount, std::to_string(amount), baseTextID); | ||||
| } | ||||
|  | ||||
| static std::string formatPlural(DamageRange range, const std::string & baseTextID) | ||||
| { | ||||
| 	if (range.min == range.max) | ||||
| 		return formatPlural(range.min, baseTextID); | ||||
|  | ||||
| 	std::string rangeString = std::to_string(range.min) + " - " + std::to_string(range.max); | ||||
|  | ||||
| 	return formatPluralImpl(range.max, rangeString, baseTextID); | ||||
| } | ||||
|  | ||||
| static std::string formatAttack(const DamageEstimation & estimation, const std::string & creatureName, const std::string & baseTextID, int shotsLeft) | ||||
| { | ||||
| 	TextReplacementList replacements = { | ||||
| 		{ "%CREATURE", creatureName }, | ||||
| 		{ "%DAMAGE", formatPlural(estimation.damage, "vcmi.battleWindow.damageEstimation.damage") }, | ||||
| 		{ "%SHOTS", formatPlural(shotsLeft, "vcmi.battleWindow.damageEstimation.shots") }, | ||||
| 		{ "%KILLS", formatPlural(estimation.kills, "vcmi.battleWindow.damageEstimation.kills") }, | ||||
| 	}; | ||||
|  | ||||
| 	return replacePlaceholders(CGI->generaltexth->translate(baseTextID), replacements); | ||||
| } | ||||
|  | ||||
| static std::string formatMeleeAttack(const DamageEstimation & estimation, const std::string & creatureName) | ||||
| { | ||||
| 	std::string baseTextID = estimation.kills.max == 0 ? | ||||
| 		"vcmi.battleWindow.damageEstimation.melee" : | ||||
| 		"vcmi.battleWindow.damageEstimation.meleeKills"; | ||||
|  | ||||
| 	return formatAttack(estimation, creatureName, baseTextID, 0); | ||||
| } | ||||
|  | ||||
| static std::string formatRangedAttack(const DamageEstimation & estimation, const std::string & creatureName, int shotsLeft) | ||||
| { | ||||
| 	std::string baseTextID = estimation.kills.max == 0 ? | ||||
| 		"vcmi.battleWindow.damageEstimation.ranged" : | ||||
| 		"vcmi.battleWindow.damageEstimation.rangedKills"; | ||||
|  | ||||
| 	return formatAttack(estimation, creatureName, baseTextID, shotsLeft); | ||||
| } | ||||
|  | ||||
| BattleActionsController::BattleActionsController(BattleInterface & owner): | ||||
| @@ -356,19 +427,17 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle | ||||
| 		case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return | ||||
| 			{ | ||||
| 				BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(targetHex); | ||||
| 				DamageRange damage = owner.curInt->cb->battleEstimateDamage(owner.stacksController->getActiveStack(), targetStack, attackFromHex); | ||||
| 				std::string estDmgText = formatDmgRange(damage); //calculating estimated dmg | ||||
| 				return (boost::format(CGI->generaltexth->allTexts[36]) % targetStack->getName() % estDmgText).str(); //Attack %s (%s damage) | ||||
| 				DamageEstimation estimation = owner.curInt->cb->battleEstimateDamage(owner.stacksController->getActiveStack(), targetStack, attackFromHex); | ||||
| 				return formatMeleeAttack(estimation, targetStack->getName()); | ||||
| 			} | ||||
|  | ||||
| 		case PossiblePlayerBattleAction::SHOOT: | ||||
| 		{ | ||||
| 			auto const * shooter = owner.stacksController->getActiveStack(); | ||||
| 			const auto * shooter = owner.stacksController->getActiveStack(); | ||||
|  | ||||
| 			DamageRange damage = owner.curInt->cb->battleEstimateDamage(shooter, targetStack, shooter->getPosition()); | ||||
| 			std::string estDmgText = formatDmgRange(damage); //calculating estimated dmg | ||||
| 			//printing - Shoot %s (%d shots left, %s damage) | ||||
| 			return (boost::format(CGI->generaltexth->allTexts[296]) % targetStack->getName() % shooter->shots.available() % estDmgText).str(); | ||||
| 			DamageEstimation estimation = owner.curInt->cb->battleEstimateDamage(shooter, targetStack, shooter->getPosition()); | ||||
|  | ||||
| 			return formatRangedAttack(estimation, targetStack->getName(), shooter->shots.available()); | ||||
| 		} | ||||
|  | ||||
| 		case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE: | ||||
|   | ||||
| @@ -715,58 +715,64 @@ bool CBattleInfoCallback::battleCanShoot(const battle::Unit * attacker, BattleHe | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| DamageRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo & info) const | ||||
| DamageEstimation CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo & info) const | ||||
| { | ||||
| 	DamageCalculator calculator(*this, info); | ||||
|  | ||||
| 	return calculator.calculateDmgRange(); | ||||
| } | ||||
|  | ||||
| DamageRange CBattleInfoCallback::battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPosition, DamageRange * retaliationDmg) const | ||||
| DamageEstimation CBattleInfoCallback::battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPosition, DamageEstimation * retaliationDmg) const | ||||
| { | ||||
| 	RETURN_IF_NOT_BATTLE({0, 0}); | ||||
| 	RETURN_IF_NOT_BATTLE({}); | ||||
| 	auto reachability = battleGetDistances(attacker, attacker->getPosition()); | ||||
| 	int movementDistance = reachability[attackerPosition]; | ||||
| 	return battleEstimateDamage(attacker, defender, movementDistance, retaliationDmg); | ||||
| } | ||||
|  | ||||
| DamageRange CBattleInfoCallback::battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int movementDistance, DamageRange * retaliationDmg) const | ||||
| DamageEstimation CBattleInfoCallback::battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int movementDistance, DamageEstimation * retaliationDmg) const | ||||
| { | ||||
| 	RETURN_IF_NOT_BATTLE({0, 0}); | ||||
| 	RETURN_IF_NOT_BATTLE({}); | ||||
| 	const bool shooting = battleCanShoot(attacker, defender->getPosition()); | ||||
| 	const BattleAttackInfo bai(attacker, defender, movementDistance, shooting); | ||||
| 	return battleEstimateDamage(bai, retaliationDmg); | ||||
| } | ||||
|  | ||||
| DamageRange CBattleInfoCallback::battleEstimateDamage(const BattleAttackInfo & bai, DamageRange * retaliationDmg) const | ||||
| DamageEstimation CBattleInfoCallback::battleEstimateDamage(const BattleAttackInfo & bai, DamageEstimation * retaliationDmg) const | ||||
| { | ||||
| 	RETURN_IF_NOT_BATTLE({0, 0}); | ||||
| 	RETURN_IF_NOT_BATTLE({}); | ||||
|  | ||||
| 	DamageRange ret = calculateDmgRange(bai); | ||||
| 	DamageEstimation ret = calculateDmgRange(bai); | ||||
|  | ||||
| 	if(retaliationDmg) | ||||
| 	{ | ||||
| 		if(bai.shooting) | ||||
| 		{ | ||||
| 			//FIXME: handle RANGED_RETALIATION | ||||
| 			retaliationDmg->min = 0; | ||||
| 			retaliationDmg->max = 0; | ||||
| 			*retaliationDmg = DamageEstimation(); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			//TODO: rewrite using boost::numeric::interval | ||||
| 			//TODO: rewire once more using interval-based fuzzy arithmetic | ||||
|  | ||||
| 			int64_t DamageRange::* pairElems[] = {&DamageRange::min, &DamageRange::max}; | ||||
| 			for (int i=0; i<2; ++i) | ||||
| 			auto const & estimateRetaliation = [&]( int64_t damage) | ||||
| 			{ | ||||
| 				auto retaliationAttack = bai.reverse(); | ||||
| 				int64_t dmg = ret.*pairElems[i]; | ||||
| 				auto state = retaliationAttack.attacker->acquireState(); | ||||
| 				state->damage(dmg); | ||||
| 				state->damage(damage); | ||||
| 				retaliationAttack.attacker = state.get(); | ||||
| 				retaliationDmg->*pairElems[!i] = calculateDmgRange(retaliationAttack).*pairElems[!i]; | ||||
| 			} | ||||
| 				return calculateDmgRange(retaliationAttack); | ||||
| 			}; | ||||
|  | ||||
| 			DamageEstimation retaliationMin = estimateRetaliation(ret.damage.min); | ||||
| 			DamageEstimation retaliationMax = estimateRetaliation(ret.damage.min); | ||||
|  | ||||
| 			retaliationDmg->damage.min = std::min(retaliationMin.damage.min, retaliationMax.damage.min); | ||||
| 			retaliationDmg->damage.max = std::max(retaliationMin.damage.max, retaliationMax.damage.max); | ||||
|  | ||||
| 			retaliationDmg->kills.min = std::min(retaliationMin.kills.min, retaliationMax.kills.min); | ||||
| 			retaliationDmg->kills.max = std::max(retaliationMin.kills.max, retaliationMax.kills.max); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -117,14 +117,14 @@ public: | ||||
| 	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; | ||||
|  | ||||
| 	DamageRange calculateDmgRange(const BattleAttackInfo & info) const; | ||||
| 	DamageEstimation calculateDmgRange(const BattleAttackInfo & info) const; | ||||
|  | ||||
| 	/// estimates damage dealt by attacker to defender; | ||||
| 	/// only non-random bonuses are considered in estimation | ||||
| 	/// returns pair <min dmg, max dmg> | ||||
| 	DamageRange battleEstimateDamage(const BattleAttackInfo & bai, DamageRange * retaliationDmg = nullptr) const; | ||||
| 	DamageRange battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPosition, DamageRange * retaliationDmg = nullptr) const; | ||||
| 	DamageRange battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int movementDistance, DamageRange * retaliationDmg = nullptr) const; | ||||
| 	DamageEstimation battleEstimateDamage(const BattleAttackInfo & bai, DamageEstimation * retaliationDmg = nullptr) const; | ||||
| 	DamageEstimation battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPosition, DamageEstimation * retaliationDmg = nullptr) const; | ||||
| 	DamageEstimation battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int movementDistance, DamageEstimation * retaliationDmg = nullptr) const; | ||||
|  | ||||
| 	bool battleHasDistancePenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const; | ||||
| 	bool battleHasWallPenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const; | ||||
|   | ||||
| @@ -112,7 +112,10 @@ public: | ||||
| 	int32_t getFirstHPleft() const; | ||||
| 	int32_t getResurrected() const; | ||||
|  | ||||
| 	/// returns total remaining health | ||||
| 	int64_t available() const; | ||||
|  | ||||
| 	/// returns total initial health | ||||
| 	int64_t total() const; | ||||
|  | ||||
| 	void takeResurrected(); | ||||
|   | ||||
| @@ -450,6 +450,25 @@ std::vector<double> DamageCalculator::getDefenseFactors() const | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| DamageRange DamageCalculator::getCasualties(const DamageRange & damageDealt) const | ||||
| { | ||||
| 	return { | ||||
| 		getCasualties(damageDealt.min), | ||||
| 		getCasualties(damageDealt.max), | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| int64_t DamageCalculator::getCasualties(int64_t damageDealt) const | ||||
| { | ||||
| 	if (damageDealt < info.defender->getFirstHPleft()) | ||||
| 		return 0; | ||||
|  | ||||
| 	int64_t damageLeft = damageDealt - info.defender->getFirstHPleft(); | ||||
| 	int64_t killsLeft = damageLeft / info.defender->MaxHealth(); | ||||
|  | ||||
| 	return 1 + killsLeft; | ||||
| } | ||||
|  | ||||
| int DamageCalculator::battleBonusValue(const IBonusBearer * bearer, const CSelector & selector) const | ||||
| { | ||||
| 	auto noLimit = Selector::effectRange()(Bonus::NO_LIMIT); | ||||
| @@ -461,9 +480,9 @@ int DamageCalculator::battleBonusValue(const IBonusBearer * bearer, const CSelec | ||||
| 	return bearer->getBonuses(selector, noLimit.Or(limitMatches))->totalValue(); | ||||
| }; | ||||
|  | ||||
| DamageRange DamageCalculator::calculateDmgRange() const | ||||
| DamageEstimation DamageCalculator::calculateDmgRange() const | ||||
| { | ||||
| 	DamageRange result = getBaseDamageStack(); | ||||
| 	DamageRange damageBase = getBaseDamageStack(); | ||||
|  | ||||
| 	auto attackFactors = getAttackFactors(); | ||||
| 	auto defenseFactors = getDefenseFactors(); | ||||
| @@ -485,10 +504,16 @@ DamageRange DamageCalculator::calculateDmgRange() const | ||||
|  | ||||
| 	double resultingFactor = std::min(8.0, attackFactorTotal) * std::max( 0.01, defenseFactorTotal); | ||||
|  | ||||
| 	return { | ||||
| 		std::max<int64_t>( 1.0, std::floor(result.min * resultingFactor)), | ||||
| 		std::max<int64_t>( 1.0, std::floor(result.max * resultingFactor)) | ||||
| 	info.defender->getTotalHealth(); | ||||
|  | ||||
| 	DamageRange damageDealt { | ||||
| 		std::max<int64_t>( 1.0, std::floor(damageBase.min * resultingFactor)), | ||||
| 		std::max<int64_t>( 1.0, std::floor(damageBase.max * resultingFactor)) | ||||
| 	}; | ||||
|  | ||||
| 	DamageRange killsDealt = getCasualties(damageDealt); | ||||
|  | ||||
| 	return DamageEstimation{damageDealt, killsDealt}; | ||||
| } | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_END | ||||
|   | ||||
| @@ -19,6 +19,7 @@ class IBonusBearer; | ||||
| class CSelector; | ||||
| struct BattleAttackInfo; | ||||
| struct DamageRange; | ||||
| struct DamageEstimation; | ||||
|  | ||||
| class DLL_LINKAGE DamageCalculator | ||||
| { | ||||
| @@ -27,6 +28,9 @@ class DLL_LINKAGE DamageCalculator | ||||
|  | ||||
| 	int battleBonusValue(const IBonusBearer * bearer, const CSelector & selector) const; | ||||
|  | ||||
| 	DamageRange getCasualties(const DamageRange & damageDealt) const; | ||||
| 	int64_t getCasualties(int64_t damageDealt) const; | ||||
|  | ||||
| 	DamageRange getBaseDamageSingle() const; | ||||
| 	DamageRange getBaseDamageBlessCurse() const; | ||||
| 	DamageRange getBaseDamageStack() const; | ||||
| @@ -67,7 +71,7 @@ public: | ||||
| 		info(info) | ||||
| 	{} | ||||
|  | ||||
| 	DamageRange calculateDmgRange() const; | ||||
| 	DamageEstimation calculateDmgRange() const; | ||||
| }; | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_END | ||||
|   | ||||
| @@ -32,6 +32,12 @@ struct DamageRange | ||||
| 	int64_t max = 0; | ||||
| }; | ||||
|  | ||||
| struct DamageEstimation | ||||
| { | ||||
| 	DamageRange damage; | ||||
| 	DamageRange kills; | ||||
| }; | ||||
|  | ||||
| #if SCRIPTING_ENABLED | ||||
| namespace scripting | ||||
| { | ||||
|   | ||||
| @@ -70,10 +70,19 @@ public: | ||||
| 	virtual bool canShoot() const = 0; | ||||
| 	virtual bool isShooter() const = 0; | ||||
|  | ||||
| 	/// returns initial size of this unit | ||||
| 	virtual int32_t getCount() const = 0; | ||||
|  | ||||
| 	/// returns remaining health of first unit | ||||
| 	virtual int32_t getFirstHPleft() const = 0; | ||||
|  | ||||
| 	/// returns total amount of killed in this unit | ||||
| 	virtual int32_t getKilled() const = 0; | ||||
|  | ||||
| 	/// returns total health that unit still has | ||||
| 	virtual int64_t getAvailableHealth() const = 0; | ||||
|  | ||||
| 	/// returns total health that unit had initially | ||||
| 	virtual int64_t getTotalHealth() const = 0; | ||||
|  | ||||
| 	virtual int getTotalAttacks(bool ranged) const = 0; | ||||
|   | ||||
| @@ -1233,7 +1233,7 @@ int64_t CGameHandler::applyBattleEffects(BattleAttack & bat, std::shared_ptr<bat | ||||
| 		bai.unluckyStrike  = bat.unlucky(); | ||||
|  | ||||
| 		auto range = gs->curB->calculateDmgRange(bai); | ||||
| 		bsa.damageAmount = gs->curB->getActualDamage(range, attackerState->getCount(), getRandomGenerator()); | ||||
| 		bsa.damageAmount = gs->curB->getActualDamage(range.damage, attackerState->getCount(), getRandomGenerator()); | ||||
| 		CStack::prepareAttacked(bsa, getRandomGenerator(), bai.defender->acquireState()); //calculate casualties | ||||
| 	} | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user