1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-26 03:52:01 +02:00

Merge pull request #1759 from IvanSavenko/kills_in_tooltip

Show potential kill amount in attack tooltip
This commit is contained in:
Ivan Savenko 2023-03-25 20:03:27 +02:00 committed by GitHub
commit 6f631a0aa7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 244 additions and 91 deletions

View File

@ -13,9 +13,9 @@
// 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)
uint64_t averageDmg(const DamageRange & range)
{
return (range.first + range.second) / 2;
return (range.min + range.max) / 2;
}
AttackPossibility::AttackPossibility(BattleHex from, BattleHex dest, const BattleAttackInfo & attack)
@ -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;
TDmgRange retaliation(0, 0);
DamageEstimation retaliation;
auto attackDmg = state.battleEstimateDamage(ap.attack, &retaliation);
vstd::amin(attackDmg.first, defenderState->getAvailableHealth());
vstd::amin(attackDmg.second, defenderState->getAvailableHealth());
vstd::amin(attackDmg.damage.min, defenderState->getAvailableHealth());
vstd::amin(attackDmg.damage.max, defenderState->getAvailableHealth());
vstd::amin(retaliation.first, ap.attackerState->getAvailableHealth());
vstd::amin(retaliation.second, 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);
}

View File

@ -68,7 +68,7 @@ int64_t BattleExchangeVariant::trackAttack(
static const auto selectorBlocksRetaliation = Selector::type()(Bonus::BLOCKS_RETALIATION);
const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);
TDmgRange 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.first + attack.second) / 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.second != 0)
if(retaliation.damage.max != 0)
{
auto retaliationDamage = (retaliation.first + retaliation.second) / 2;
auto retaliationDamage = (retaliation.damage.min + retaliation.damage.max) / 2;
attackerDamageReduce = AttackPossibility::calculateDamageReduce(defender.get(), attacker.get(), retaliationDamage, cb);
if(!evaluateOnly)

View File

@ -428,9 +428,9 @@ uint32_t HypotheticBattle::nextUnitId() const
return nextId++;
}
int64_t HypotheticBattle::getActualDamage(const TDmgRange & damage, int32_t attackerCount, vstd::RNG & rng) const
int64_t HypotheticBattle::getActualDamage(const DamageRange & damage, int32_t attackerCount, vstd::RNG & rng) const
{
return (damage.first + damage.second) / 2;
return (damage.min + damage.max) / 2;
}
int64_t HypotheticBattle::getTreeVersion() const

View File

@ -138,7 +138,7 @@ public:
uint32_t nextUnitId() const override;
int64_t getActualDamage(const TDmgRange & damage, int32_t attackerCount, vstd::RNG & rng) const override;
int64_t getActualDamage(const DamageRange & damage, int32_t attackerCount, vstd::RNG & rng) const override;
int64_t getTreeVersion() const;

View File

@ -56,9 +56,10 @@ public:
void calcDmg(const CStack * ourStack)
{
// FIXME: provide distance info for Jousting bonus
TDmgRange retal, dmg = cbc->battleEstimateDamage(ourStack, s, 0, &retal);
adi = static_cast<int>((dmg.first + dmg.second) / 2);
adr = static_cast<int>((retal.first + retal.second) / 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

View File

@ -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",

View File

@ -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 Показує істот, яких можна придбати, замість їхнього приросту у зведенні по місту (нижній лівий кут).",

View File

@ -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(std::pair<ui32, ui32> dmgRange)
struct TextReplacement
{
if (dmgRange.first != dmgRange.second)
return (boost::format("%d - %d") % dmgRange.first % dmgRange.second).str();
else
return (boost::format("%d") % dmgRange.first).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);
TDmgRange damage = owner.curInt->cb->battleEstimateDamage(owner.stacksController->getActiveStack(), targetStack, attackFromHex);
std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //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();
TDmgRange damage = owner.curInt->cb->battleEstimateDamage(shooter, targetStack, shooter->getPosition());
std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //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:

View File

@ -1295,7 +1295,6 @@ enum class EHealPower : ui8
// Typedef declarations
typedef ui8 TFaction;
typedef si64 TExpType;
typedef std::pair<si64, si64> TDmgRange;
typedef si32 TBonusSubtype;
typedef si32 TQuantity;

View File

@ -663,14 +663,14 @@ const IBonusBearer * BattleInfo::asBearer() const
return this;
}
int64_t BattleInfo::getActualDamage(const TDmgRange & damage, int32_t attackerCount, vstd::RNG & rng) const
int64_t BattleInfo::getActualDamage(const DamageRange & damage, int32_t attackerCount, vstd::RNG & rng) const
{
if(damage.first != damage.second)
if(damage.min != damage.max)
{
int64_t sum = 0;
auto howManyToAv = std::min<int32_t>(10, attackerCount);
auto rangeGen = rng.getInt64Range(damage.first, damage.second);
auto rangeGen = rng.getInt64Range(damage.min, damage.max);
for(int32_t g = 0; g < howManyToAv; ++g)
sum += rangeGen();
@ -679,7 +679,7 @@ int64_t BattleInfo::getActualDamage(const TDmgRange & damage, int32_t attackerCo
}
else
{
return damage.first;
return damage.min;
}
}

View File

@ -97,7 +97,7 @@ public:
uint32_t nextUnitId() const override;
int64_t getActualDamage(const TDmgRange & damage, int32_t attackerCount, vstd::RNG & rng) const override;
int64_t getActualDamage(const DamageRange & damage, int32_t attackerCount, vstd::RNG & rng) const override;
//////////////////////////////////////////////////////////////////////////
// IBattleState

View File

@ -715,57 +715,64 @@ bool CBattleInfoCallback::battleCanShoot(const battle::Unit * attacker, BattleHe
return false;
}
TDmgRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo & info) const
DamageEstimation CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo & info) const
{
DamageCalculator calculator(*this, info);
return calculator.calculateDmgRange();
}
TDmgRange CBattleInfoCallback::battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPosition, TDmgRange * retaliationDmg) const
DamageEstimation CBattleInfoCallback::battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPosition, DamageEstimation * retaliationDmg) const
{
RETURN_IF_NOT_BATTLE(std::make_pair(0, 0));
RETURN_IF_NOT_BATTLE({});
auto reachability = battleGetDistances(attacker, attacker->getPosition());
int movementDistance = reachability[attackerPosition];
return battleEstimateDamage(attacker, defender, movementDistance, retaliationDmg);
}
TDmgRange CBattleInfoCallback::battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int movementDistance, TDmgRange * retaliationDmg) const
DamageEstimation CBattleInfoCallback::battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int movementDistance, DamageEstimation * retaliationDmg) const
{
RETURN_IF_NOT_BATTLE(std::make_pair(0, 0));
RETURN_IF_NOT_BATTLE({});
const bool shooting = battleCanShoot(attacker, defender->getPosition());
const BattleAttackInfo bai(attacker, defender, movementDistance, shooting);
return battleEstimateDamage(bai, retaliationDmg);
}
TDmgRange CBattleInfoCallback::battleEstimateDamage(const BattleAttackInfo & bai, TDmgRange * retaliationDmg) const
DamageEstimation CBattleInfoCallback::battleEstimateDamage(const BattleAttackInfo & bai, DamageEstimation * retaliationDmg) const
{
RETURN_IF_NOT_BATTLE(std::make_pair(0, 0));
RETURN_IF_NOT_BATTLE({});
TDmgRange ret = calculateDmgRange(bai);
DamageEstimation ret = calculateDmgRange(bai);
if(retaliationDmg)
{
if(bai.shooting)
{
//FIXME: handle RANGED_RETALIATION
retaliationDmg->first = retaliationDmg->second = 0;
*retaliationDmg = DamageEstimation();
}
else
{
//TODO: rewrite using boost::numeric::interval
//TODO: rewire once more using interval-based fuzzy arithmetic
int64_t TDmgRange::* pairElems[] = {&TDmgRange::first, &TDmgRange::second};
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);
}
}

View File

@ -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;
TDmgRange calculateDmgRange(const BattleAttackInfo & info) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair <min dmg, max dmg>
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>
TDmgRange battleEstimateDamage(const BattleAttackInfo & bai, TDmgRange * retaliationDmg = nullptr) const;
TDmgRange battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPosition, TDmgRange * retaliationDmg = nullptr) const;
TDmgRange battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int movementDistance, TDmgRange * 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;

View File

@ -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();

View File

@ -20,10 +20,10 @@
VCMI_LIB_NAMESPACE_BEGIN
TDmgRange DamageCalculator::getBaseDamageSingle() const
DamageRange DamageCalculator::getBaseDamageSingle() const
{
double minDmg = 0.0;
double maxDmg = 0.0;
int64_t minDmg = 0.0;
int64_t maxDmg = 0.0;
minDmg = info.attacker->getMinDamage(info.shooting);
maxDmg = info.attacker->getMaxDamage(info.shooting);
@ -63,7 +63,7 @@ TDmgRange DamageCalculator::getBaseDamageSingle() const
return { minDmg, maxDmg };
}
TDmgRange DamageCalculator::getBaseDamageBlessCurse() const
DamageRange DamageCalculator::getBaseDamageBlessCurse() const
{
const std::string cachingStrForcedMinDamage = "type_ALWAYS_MINIMUM_DAMAGE";
static const auto selectorForcedMinDamage = Selector::type()(Bonus::ALWAYS_MINIMUM_DAMAGE);
@ -76,10 +76,10 @@ TDmgRange DamageCalculator::getBaseDamageBlessCurse() const
int curseBlessAdditiveModifier = blessEffects->totalValue() - curseEffects->totalValue();
TDmgRange baseDamage = getBaseDamageSingle();
TDmgRange modifiedDamage = {
std::max(static_cast<int64_t>(1), baseDamage.first + curseBlessAdditiveModifier),
std::max(static_cast<int64_t>(1), baseDamage.second + curseBlessAdditiveModifier)
DamageRange baseDamage = getBaseDamageSingle();
DamageRange modifiedDamage = {
std::max(static_cast<int64_t>(1), baseDamage.min + curseBlessAdditiveModifier),
std::max(static_cast<int64_t>(1), baseDamage.max + curseBlessAdditiveModifier)
};
if(curseEffects->size() && blessEffects->size() )
@ -91,29 +91,29 @@ TDmgRange DamageCalculator::getBaseDamageBlessCurse() const
if(curseEffects->size())
{
return {
modifiedDamage.first,
modifiedDamage.first
modifiedDamage.min,
modifiedDamage.min
};
}
if(blessEffects->size())
{
return {
modifiedDamage.second,
modifiedDamage.second
modifiedDamage.max,
modifiedDamage.max
};
}
return modifiedDamage;
}
TDmgRange DamageCalculator::getBaseDamageStack() const
DamageRange DamageCalculator::getBaseDamageStack() const
{
auto stackSize = info.attacker->getCount();
auto baseDamage = getBaseDamageBlessCurse();
return {
baseDamage.first * stackSize,
baseDamage.second * stackSize
baseDamage.min * stackSize,
baseDamage.max * stackSize
};
}
@ -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();
};
TDmgRange DamageCalculator::calculateDmgRange() const
DamageEstimation DamageCalculator::calculateDmgRange() const
{
TDmgRange result = getBaseDamageStack();
DamageRange damageBase = getBaseDamageStack();
auto attackFactors = getAttackFactors();
auto defenseFactors = getDefenseFactors();
@ -485,10 +504,16 @@ TDmgRange DamageCalculator::calculateDmgRange() const
double resultingFactor = std::min(8.0, attackFactorTotal) * std::max( 0.01, defenseFactorTotal);
return {
std::max( 1.0, std::floor(result.first * resultingFactor)),
std::max( 1.0, std::floor(result.second * 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

View File

@ -18,6 +18,8 @@ class CBattleInfoCallback;
class IBonusBearer;
class CSelector;
struct BattleAttackInfo;
struct DamageRange;
struct DamageEstimation;
class DLL_LINKAGE DamageCalculator
{
@ -26,9 +28,12 @@ class DLL_LINKAGE DamageCalculator
int battleBonusValue(const IBonusBearer * bearer, const CSelector & selector) const;
TDmgRange getBaseDamageSingle() const;
TDmgRange getBaseDamageBlessCurse() const;
TDmgRange getBaseDamageStack() const;
DamageRange getCasualties(const DamageRange & damageDealt) const;
int64_t getCasualties(int64_t damageDealt) const;
DamageRange getBaseDamageSingle() const;
DamageRange getBaseDamageBlessCurse() const;
DamageRange getBaseDamageStack() const;
int getActorAttackBase() const;
int getActorAttackEffective() const;
@ -66,7 +71,7 @@ public:
info(info)
{}
TDmgRange calculateDmgRange() const;
DamageEstimation calculateDmgRange() const;
};
VCMI_LIB_NAMESPACE_END

View File

@ -26,6 +26,18 @@ namespace battle
using UnitFilter = std::function<bool(const Unit *)>;
}
struct DamageRange
{
int64_t min = 0;
int64_t max = 0;
};
struct DamageEstimation
{
DamageRange damage;
DamageRange kills;
};
#if SCRIPTING_ENABLED
namespace scripting
{

View File

@ -66,7 +66,7 @@ public:
virtual uint32_t nextUnitId() const = 0;
virtual int64_t getActualDamage(const TDmgRange & damage, int32_t attackerCount, vstd::RNG & rng) const = 0;
virtual int64_t getActualDamage(const DamageRange & damage, int32_t attackerCount, vstd::RNG & rng) const = 0;
};
class DLL_LINKAGE IBattleState : public IBattleInfo

View File

@ -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;

View File

@ -12,7 +12,7 @@
#include "CGTownInstance.h"
#include "CObjectClassesHandler.h"
#include "../spells/CSpellHandler.h"
#include "../battle/IBattleInfoCallback.h"
#include "../NetPacks.h"
#include "../CConfigHandler.h"
#include "../CGeneralTextHandler.h"
@ -807,7 +807,7 @@ void CGTownInstance::addTownBonuses()
}
}
TDmgRange CGTownInstance::getTowerDamageRange() const
DamageRange CGTownInstance::getTowerDamageRange() const
{
assert(hasBuilt(BuildingID::CASTLE));
@ -825,7 +825,7 @@ TDmgRange CGTownInstance::getTowerDamageRange() const
};
}
TDmgRange CGTownInstance::getKeepDamageRange() const
DamageRange CGTownInstance::getKeepDamageRange() const
{
assert(hasBuilt(BuildingID::CITADEL));

View File

@ -20,6 +20,7 @@ VCMI_LIB_NAMESPACE_BEGIN
class CCastleEvent;
class CGTownInstance;
class CGDwelling;
struct DamageRange;
class DLL_LINKAGE CSpecObjInfo
{
@ -332,10 +333,10 @@ public:
void deleteTownBonus(BuildingID::EBuildingID bid);
/// Returns damage range for secondary towers of this town
TDmgRange getTowerDamageRange() const;
DamageRange getTowerDamageRange() const;
/// Returns damage range for central tower(keep) of this town
TDmgRange getKeepDamageRange() const;
DamageRange getKeepDamageRange() const;
const CTown * getTown() const ;

View File

@ -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
}