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