|
|
@@ -268,7 +268,9 @@ bool BattleActionProcessor::doAttackAction(const CBattleInfoCallback & battle, c
|
|
|
totalAttacks += attackingHero->valOfBonuses(BonusType::HERO_GRANTS_ATTACKS, BonusSubtypeID(stack->creatureId()));
|
|
|
}
|
|
|
|
|
|
- const bool firstStrike = destinationStack->hasBonusOfType(BonusType::FIRST_STRIKE);
|
|
|
+ static const auto firstStrikeSelector = Selector::typeSubtype(BonusType::FIRST_STRIKE, BonusCustomSubtype::damageTypeAll).Or(Selector::typeSubtype(BonusType::FIRST_STRIKE, BonusCustomSubtype::damageTypeMelee));
|
|
|
+ const bool firstStrike = destinationStack->hasBonus(firstStrikeSelector);
|
|
|
+
|
|
|
const bool retaliation = destinationStack->ableToRetaliate();
|
|
|
bool ferocityApplied = false;
|
|
|
int32_t defenderInitialQuantity = destinationStack->getCount();
|
|
|
@@ -276,7 +278,7 @@ bool BattleActionProcessor::doAttackAction(const CBattleInfoCallback & battle, c
|
|
|
for (int i = 0; i < totalAttacks; ++i)
|
|
|
{
|
|
|
//first strike
|
|
|
- if(i == 0 && firstStrike && retaliation)
|
|
|
+ if(i == 0 && firstStrike && retaliation && !stack->hasBonusOfType(BonusType::BLOCKS_RETALIATION))
|
|
|
{
|
|
|
makeAttack(battle, destinationStack, stack, 0, stack->getPosition(), true, false, true);
|
|
|
}
|
|
|
@@ -353,7 +355,11 @@ bool BattleActionProcessor::doShootAction(const CBattleInfoCallback & battle, co
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- makeAttack(battle, stack, destinationStack, 0, destination, true, true, false);
|
|
|
+ static const auto firstStrikeSelector = Selector::typeSubtype(BonusType::FIRST_STRIKE, BonusCustomSubtype::damageTypeAll).Or(Selector::typeSubtype(BonusType::FIRST_STRIKE, BonusCustomSubtype::damageTypeRanged));
|
|
|
+ const bool firstStrike = destinationStack->hasBonus(firstStrikeSelector);
|
|
|
+
|
|
|
+ if (!firstStrike)
|
|
|
+ makeAttack(battle, stack, destinationStack, 0, destination, true, true, false);
|
|
|
|
|
|
//ranged counterattack
|
|
|
if (destinationStack->hasBonusOfType(BonusType::RANGED_RETALIATION)
|
|
|
@@ -375,7 +381,7 @@ bool BattleActionProcessor::doShootAction(const CBattleInfoCallback & battle, co
|
|
|
totalRangedAttacks += attackingHero->valOfBonuses(BonusType::HERO_GRANTS_ATTACKS, BonusSubtypeID(stack->creatureId()));
|
|
|
}
|
|
|
|
|
|
- for(int i = 1; i < totalRangedAttacks; ++i)
|
|
|
+ for(int i = firstStrike ? 0:1; i < totalRangedAttacks; ++i)
|
|
|
{
|
|
|
if(
|
|
|
stack->alive()
|
|
|
@@ -1234,7 +1240,7 @@ void BattleActionProcessor::handleAttackBeforeCasting(const CBattleInfoCallback
|
|
|
attackCasting(battle, ranged, BonusType::SPELL_BEFORE_ATTACK, attacker, defender); //no death stare / acid breath needed?
|
|
|
}
|
|
|
|
|
|
-void BattleActionProcessor::HandleDeathStareAndPirateShot(const CBattleInfoCallback & battle, bool ranged, const CStack * attacker, const CStack * defender)
|
|
|
+void BattleActionProcessor::handleDeathStare(const CBattleInfoCallback & battle, bool ranged, const CStack * attacker, const CStack * defender)
|
|
|
{
|
|
|
// mechanics of Death Stare as in H3:
|
|
|
// each gorgon have 10% chance to kill (counted separately in H3) -> binomial distribution
|
|
|
@@ -1246,28 +1252,30 @@ void BattleActionProcessor::HandleDeathStareAndPirateShot(const CBattleInfoCallb
|
|
|
* X = 3 multiplier for shooting without penalty and X = 2 if shooting with penalty. Ability doesn't work if shooting at creatures behind walls.
|
|
|
*/
|
|
|
|
|
|
- auto bonus = attacker->getBonus(Selector::type()(BonusType::DEATH_STARE));
|
|
|
- if(bonus == nullptr)
|
|
|
- bonus = attacker->getBonus(Selector::type()(BonusType::ACCURATE_SHOT));
|
|
|
+ auto subtype = BonusCustomSubtype::deathStareGorgon;
|
|
|
|
|
|
- if(bonus->type == BonusType::ACCURATE_SHOT) //should not work from behind walls, except when being defender or under effect of golden bow etc.
|
|
|
+ if (ranged)
|
|
|
{
|
|
|
- if(!ranged)
|
|
|
- return;
|
|
|
- if(battle.battleHasWallPenalty(attacker, attacker->getPosition(), defender->getPosition()))
|
|
|
- return;
|
|
|
- }
|
|
|
+ bool rangePenalty = battle.battleHasDistancePenalty(attacker, attacker->getPosition(), defender->getPosition());
|
|
|
+ bool obstaclePenalty = battle.battleHasWallPenalty(attacker, attacker->getPosition(), defender->getPosition());
|
|
|
|
|
|
- int singleCreatureKillChancePercent;
|
|
|
- if(bonus->type == BonusType::ACCURATE_SHOT)
|
|
|
- {
|
|
|
- singleCreatureKillChancePercent = attacker->valOfBonuses(BonusType::ACCURATE_SHOT);
|
|
|
- if(battle.battleHasDistancePenalty(attacker, attacker->getPosition(), defender->getPosition()))
|
|
|
- singleCreatureKillChancePercent = (singleCreatureKillChancePercent * 2) / 3;
|
|
|
+ if(rangePenalty)
|
|
|
+ {
|
|
|
+ if(obstaclePenalty)
|
|
|
+ subtype = BonusCustomSubtype::deathStareRangeObstaclePenalty;
|
|
|
+ else
|
|
|
+ subtype = BonusCustomSubtype::deathStareRangePenalty;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if(obstaclePenalty)
|
|
|
+ subtype = BonusCustomSubtype::deathStareObstaclePenalty;
|
|
|
+ else
|
|
|
+ subtype = BonusCustomSubtype::deathStareNoRangePenalty;
|
|
|
+ }
|
|
|
}
|
|
|
- else //DEATH_STARE
|
|
|
- singleCreatureKillChancePercent = attacker->valOfBonuses(BonusType::DEATH_STARE, BonusCustomSubtype::deathStareGorgon);
|
|
|
|
|
|
+ int singleCreatureKillChancePercent = attacker->valOfBonuses(BonusType::DEATH_STARE, subtype);
|
|
|
double chanceToKill = singleCreatureKillChancePercent / 100.0;
|
|
|
vstd::amin(chanceToKill, 1); //cap at 100%
|
|
|
std::binomial_distribution<> distribution(attacker->getCount(), chanceToKill);
|
|
|
@@ -1276,16 +1284,16 @@ void BattleActionProcessor::HandleDeathStareAndPirateShot(const CBattleInfoCallb
|
|
|
int maxToKill = (attacker->getCount() * singleCreatureKillChancePercent + 99) / 100;
|
|
|
vstd::amin(killedCreatures, maxToKill);
|
|
|
|
|
|
- if(bonus->type == BonusType::DEATH_STARE)
|
|
|
- killedCreatures += (attacker->level() * attacker->valOfBonuses(BonusType::DEATH_STARE, BonusCustomSubtype::deathStareCommander)) / defender->level();
|
|
|
+ killedCreatures += (attacker->level() * attacker->valOfBonuses(BonusType::DEATH_STARE, BonusCustomSubtype::deathStareCommander)) / defender->level();
|
|
|
|
|
|
if(killedCreatures)
|
|
|
{
|
|
|
//TODO: death stare or accurate shot was not originally available for multiple-hex attacks, but...
|
|
|
|
|
|
SpellID spellID = SpellID(SpellID::DEATH_STARE); //also used as fallback spell for ACCURATE_SHOT
|
|
|
- if(bonus->type == BonusType::ACCURATE_SHOT && bonus->subtype.as<SpellID>() != SpellID::NONE)
|
|
|
- spellID = bonus->subtype.as<SpellID>();
|
|
|
+ auto bonus = attacker->getBonus(Selector::typeSubtype(BonusType::DEATH_STARE, subtype));
|
|
|
+ if(bonus && bonus->additionalInfo[0] != SpellID::NONE)
|
|
|
+ spellID = SpellID(bonus->additionalInfo[0]);
|
|
|
|
|
|
const CSpell * spell = spellID.toSpell();
|
|
|
spells::AbilityCaster caster(attacker, 0);
|
|
|
@@ -1311,10 +1319,8 @@ void BattleActionProcessor::handleAfterAttackCasting(const CBattleInfoCallback &
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- if(attacker->hasBonusOfType(BonusType::DEATH_STARE) || attacker->hasBonusOfType(BonusType::ACCURATE_SHOT))
|
|
|
- {
|
|
|
- HandleDeathStareAndPirateShot(battle, ranged, attacker, defender);
|
|
|
- }
|
|
|
+ if(attacker->hasBonusOfType(BonusType::DEATH_STARE))
|
|
|
+ handleDeathStare(battle, ranged, attacker, defender);
|
|
|
|
|
|
if(!defender->alive())
|
|
|
return;
|