1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-03-29 21:56:54 +02:00

Merge pull request #3500 from IvanSavenko/fix_dendroid_bind

[1.4.3] Fix handling of Dendroid's Bind ability
This commit is contained in:
Ivan Savenko 2024-01-15 12:02:06 +02:00 committed by GitHub
commit a582cb554e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 32 additions and 41 deletions

View File

@ -147,7 +147,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
(int)bestAttack.from, (int)bestAttack.from,
(int)bestAttack.attack.attacker->getPosition().hex, (int)bestAttack.attack.attacker->getPosition().hex,
bestAttack.attack.chargeDistance, bestAttack.attack.chargeDistance,
bestAttack.attack.attacker->speed(0, true), bestAttack.attack.attacker->getMovementRange(0),
bestAttack.defenderDamageReduce, bestAttack.defenderDamageReduce,
bestAttack.attackerDamageReduce, bestAttack.attackerDamageReduce,
score score
@ -553,7 +553,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
auto needFullEval = vstd::contains_if(allUnits, [&](const battle::Unit * u) -> bool auto needFullEval = vstd::contains_if(allUnits, [&](const battle::Unit * u) -> bool
{ {
auto original = cb->getBattle(battleID)->battleGetUnitByID(u->unitId()); auto original = cb->getBattle(battleID)->battleGetUnitByID(u->unitId());
return !original || u->speed() != original->speed(); return !original || u->getMovementRange() != original->getMovementRange();
}); });
DamageCache safeCopy = damageCache; DamageCache safeCopy = damageCache;

View File

@ -297,7 +297,7 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
if(targets.unreachableEnemies.empty()) if(targets.unreachableEnemies.empty())
return result; return result;
auto speed = activeStack->speed(); auto speed = activeStack->getMovementRange();
if(speed == 0) if(speed == 0)
return result; return result;
@ -324,7 +324,7 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
auto turnsToRich = (distance - 1) / speed + 1; auto turnsToRich = (distance - 1) / speed + 1;
auto hexes = closestStack->getSurroundingHexes(); auto hexes = closestStack->getSurroundingHexes();
auto enemySpeed = closestStack->speed(); auto enemySpeed = closestStack->getMovementRange();
auto speedRatio = speed / static_cast<float>(enemySpeed); auto speedRatio = speed / static_cast<float>(enemySpeed);
auto multiplier = speedRatio > 1 ? 1 : speedRatio; auto multiplier = speedRatio > 1 ? 1 : speedRatio;
@ -753,7 +753,7 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getOneTurnReachableUn
continue; continue;
} }
auto unitSpeed = unit->speed(turn); auto unitSpeed = unit->getMovementRange(turn);
auto radius = unitSpeed * (turn + 1); auto radius = unitSpeed * (turn + 1);
ReachabilityInfo unitReachability = vstd::getOrCompute( ReachabilityInfo unitReachability = vstd::getOrCompute(
@ -819,7 +819,7 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb
float ratio = blockedUnitDamage / (float)(blockedUnitDamage + activeUnitDamage + 0.01); float ratio = blockedUnitDamage / (float)(blockedUnitDamage + activeUnitDamage + 0.01);
auto unitReachability = turnBattle.getReachability(unit); auto unitReachability = turnBattle.getReachability(unit);
auto unitSpeed = unit->speed(turn); // Cached value, to avoid performance hit auto unitSpeed = unit->getMovementRange(turn); // Cached value, to avoid performance hit
for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex = hex + 1) for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex = hex + 1)
{ {

View File

@ -117,7 +117,7 @@ std::vector<SlotInfo>::iterator ArmyManager::getWeakestCreature(std::vector<Slot
if(left.creature->getLevel() != right.creature->getLevel()) if(left.creature->getLevel() != right.creature->getLevel())
return left.creature->getLevel() < right.creature->getLevel(); return left.creature->getLevel() < right.creature->getLevel();
return left.creature->speed() > right.creature->speed(); return left.creature->getMovementRange() > right.creature->getMovementRange();
}); });
return weakest; return weakest;

View File

@ -63,7 +63,7 @@ std::vector<SlotInfo>::iterator ArmyManager::getWeakestCreature(std::vector<Slot
if(left.creature->getLevel() != right.creature->getLevel()) if(left.creature->getLevel() != right.creature->getLevel())
return left.creature->getLevel() < right.creature->getLevel(); return left.creature->getLevel() < right.creature->getLevel();
return left.creature->speed() > right.creature->speed(); return left.creature->getMovementRange() > right.creature->getMovementRange();
}); });
return weakest; return weakest;

View File

@ -568,7 +568,7 @@ bool BattleActionsController::actionIsLegal(PossiblePlayerBattleAction action, B
switch (action.get()) switch (action.get())
{ {
case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK: case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK:
return (targetStack && targetStackOwned && targetStack->speed() > 0); return (targetStack && targetStackOwned && targetStack->getMovementRange() > 0);
case PossiblePlayerBattleAction::CREATURE_INFO: case PossiblePlayerBattleAction::CREATURE_INFO:
return (targetStack && targetStackOwned && targetStack->alive()); return (targetStack && targetStackOwned && targetStack->alive());

View File

@ -363,7 +363,7 @@ bool MovementAnimation::init()
Point begPosition = owner.stacksController->getStackPositionAtHex(prevHex, stack); Point begPosition = owner.stacksController->getStackPositionAtHex(prevHex, stack);
Point endPosition = owner.stacksController->getStackPositionAtHex(nextHex, stack); Point endPosition = owner.stacksController->getStackPositionAtHex(nextHex, stack);
progressPerSecond = AnimationControls::getMovementDistance(stack->unitType()); progressPerSecond = AnimationControls::getMovementRange(stack->unitType());
begX = begPosition.x; begX = begPosition.x;
begY = begPosition.y; begY = begPosition.y;

View File

@ -640,7 +640,7 @@ void BattleInterface::tacticPhaseEnd()
static bool immobile(const CStack *s) static bool immobile(const CStack *s)
{ {
return !s->speed(0, true); //should bound stacks be immobile? return s->getMovementRange() == 0; //should bound stacks be immobile?
} }
void BattleInterface::tacticNextStack(const CStack * current) void BattleInterface::tacticNextStack(const CStack * current)

View File

@ -148,7 +148,7 @@ float AnimationControls::getSpellEffectSpeed()
return static_cast<float>(getAnimationSpeedFactor() * 10); return static_cast<float>(getAnimationSpeedFactor() * 10);
} }
float AnimationControls::getMovementDistance(const CCreature * creature) float AnimationControls::getMovementRange(const CCreature * creature)
{ {
// H3 speed: 2/4/6 tiles per second // H3 speed: 2/4/6 tiles per second
return static_cast<float>( 2.0 * getAnimationSpeedFactor() / creature->animation.walkAnimationTime); return static_cast<float>( 2.0 * getAnimationSpeedFactor() / creature->animation.walkAnimationTime);

View File

@ -50,7 +50,7 @@ namespace AnimationControls
float getSpellEffectSpeed(); float getSpellEffectSpeed();
/// returns speed of movement animation across the screen, in tiles per second /// returns speed of movement animation across the screen, in tiles per second
float getMovementDistance(const CCreature * creature); float getMovementRange(const CCreature * creature);
/// returns speed of movement animation across the screen, in pixels per seconds /// returns speed of movement animation across the screen, in pixels per seconds
float getFlightDistance(const CCreature * creature); float getFlightDistance(const CCreature * creature);

View File

@ -543,7 +543,7 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
addStatLabel(EStat::DEFENCE, parent->info->creature->getDefense(battleStack->isShooter()), battleStack->getDefense(battleStack->isShooter())); addStatLabel(EStat::DEFENCE, parent->info->creature->getDefense(battleStack->isShooter()), battleStack->getDefense(battleStack->isShooter()));
addStatLabel(EStat::DAMAGE, parent->info->stackNode->getMinDamage(battleStack->isShooter()) * dmgMultiply, battleStack->getMaxDamage(battleStack->isShooter()) * dmgMultiply); addStatLabel(EStat::DAMAGE, parent->info->stackNode->getMinDamage(battleStack->isShooter()) * dmgMultiply, battleStack->getMaxDamage(battleStack->isShooter()) * dmgMultiply);
addStatLabel(EStat::HEALTH, parent->info->creature->getMaxHealth(), battleStack->getMaxHealth()); addStatLabel(EStat::HEALTH, parent->info->creature->getMaxHealth(), battleStack->getMaxHealth());
addStatLabel(EStat::SPEED, parent->info->creature->speed(), battleStack->speed()); addStatLabel(EStat::SPEED, parent->info->creature->getMovementRange(), battleStack->getMovementRange());
if(battleStack->isShooter()) if(battleStack->isShooter())
addStatLabel(EStat::SHOTS, battleStack->shots.total(), battleStack->shots.available()); addStatLabel(EStat::SHOTS, battleStack->shots.total(), battleStack->shots.available());
@ -563,7 +563,7 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
addStatLabel(EStat::DEFENCE, parent->info->creature->getDefense(shooter), parent->info->stackNode->getDefense(shooter)); addStatLabel(EStat::DEFENCE, parent->info->creature->getDefense(shooter), parent->info->stackNode->getDefense(shooter));
addStatLabel(EStat::DAMAGE, parent->info->stackNode->getMinDamage(shooter) * dmgMultiply, parent->info->stackNode->getMaxDamage(shooter) * dmgMultiply); addStatLabel(EStat::DAMAGE, parent->info->stackNode->getMinDamage(shooter) * dmgMultiply, parent->info->stackNode->getMaxDamage(shooter) * dmgMultiply);
addStatLabel(EStat::HEALTH, parent->info->creature->getMaxHealth(), parent->info->stackNode->getMaxHealth()); addStatLabel(EStat::HEALTH, parent->info->creature->getMaxHealth(), parent->info->stackNode->getMaxHealth());
addStatLabel(EStat::SPEED, parent->info->creature->speed(), parent->info->stackNode->speed()); addStatLabel(EStat::SPEED, parent->info->creature->getMovementRange(), parent->info->stackNode->getMovementRange());
if(shooter) if(shooter)
addStatLabel(EStat::SHOTS, parent->info->stackNode->valOfBonuses(BonusType::SHOTS)); addStatLabel(EStat::SHOTS, parent->info->stackNode->valOfBonuses(BonusType::SHOTS));

View File

@ -788,7 +788,7 @@ Determines how many times per combat affected creature can cast its targeted spe
- subtype - spell id, eg. spell.iceBolt - subtype - spell id, eg. spell.iceBolt
- value - chance (percent) - value - chance (percent)
- additional info - \[X, Y, Z\] - additional info - \[X, Y, Z\]
- X - spell level - X - spell mastery level (1 - Basic, 3 - Expert)
- Y = 0 - all attacks, 1 - shot only, 2 - melee only - Y = 0 - all attacks, 1 - shot only, 2 - melee only
- Z (optional) - layer for multiple SPELL_AFTER_ATTACK bonuses and multi-turn casting. Empty or value less than 0 = not participating in layering. - Z (optional) - layer for multiple SPELL_AFTER_ATTACK bonuses and multi-turn casting. Empty or value less than 0 = not participating in layering.
When enabled - spells from specific layer will not be cast until target has all spells from previous layer on him. Spell from last layer is on repeat if none of spells on lower layers expired. When enabled - spells from specific layer will not be cast until target has all spells from previous layer on him. Spell from last layer is on repeat if none of spells on lower layers expired.
@ -798,7 +798,7 @@ Determines how many times per combat affected creature can cast its targeted spe
- subtype - spell id - subtype - spell id
- value - chance % - value - chance %
- additional info - \[X, Y, Z\] - additional info - \[X, Y, Z\]
- X - spell level - X - spell mastery level (1 - Basic, 3 - Expert)
- Y = 0 - all attacks, 1 - shot only, 2 - melee only - Y = 0 - all attacks, 1 - shot only, 2 - melee only
- Z (optional) - layer for multiple SPELL_BEFORE_ATTACK bonuses and multi-turn casting. Empty or value less than 0 = not participating in layering. - Z (optional) - layer for multiple SPELL_BEFORE_ATTACK bonuses and multi-turn casting. Empty or value less than 0 = not participating in layering.
When enabled - spells from specific layer will not be cast until target has all spells from previous layer on him. Spell from last layer is on repeat if none of spells on lower layers expired. When enabled - spells from specific layer will not be cast until target has all spells from previous layer on him. Spell from last layer is on repeat if none of spells on lower layers expired.

View File

@ -23,7 +23,7 @@ class DLL_LINKAGE ACreature: public AFactionMember
{ {
public: public:
bool isLiving() const; //non-undead, non-non living or alive bool isLiving() const; //non-undead, non-non living or alive
ui32 speed(int turn = 0, bool useBind = false) const; //get speed (in moving tiles) of creature with all modificators ui32 getMovementRange(int turn = 0) const; //get speed (in moving tiles) of creature with all modificators
virtual ui32 getMaxHealth() const; //get max HP of stack with all modifiers virtual ui32 getMaxHealth() const; //get max HP of stack with all modifiers
}; };

View File

@ -168,15 +168,14 @@ ui32 ACreature::getMaxHealth() const
return std::max(1, value); //never 0 return std::max(1, value); //never 0
} }
ui32 ACreature::speed(int turn, bool useBind) const ui32 ACreature::getMovementRange(int turn) const
{ {
//war machines cannot move //war machines cannot move
if(getBonusBearer()->hasBonus(Selector::type()(BonusType::SIEGE_WEAPON).And(Selector::turns(turn)))) if(getBonusBearer()->hasBonus(Selector::type()(BonusType::SIEGE_WEAPON).And(Selector::turns(turn))))
{ {
return 0; return 0;
} }
//bind effect check - doesn't influence stack initiative if(getBonusBearer()->hasBonus(Selector::type()(BonusType::BIND_EFFECT).And(Selector::turns(turn))))
if(useBind && getBonusBearer()->hasBonus(Selector::type()(BonusType::BIND_EFFECT).And(Selector::turns(turn))))
{ {
return 0; return 0;
} }

View File

@ -267,7 +267,7 @@ std::vector<PossiblePlayerBattleAction> CBattleInfoCallback::getClientActionsFor
allowedActionList.push_back(PossiblePlayerBattleAction::ATTACK); //all active stacks can attack allowedActionList.push_back(PossiblePlayerBattleAction::ATTACK); //all active stacks can attack
allowedActionList.push_back(PossiblePlayerBattleAction::WALK_AND_ATTACK); //not all stacks can always walk, but we will check this elsewhere allowedActionList.push_back(PossiblePlayerBattleAction::WALK_AND_ATTACK); //not all stacks can always walk, but we will check this elsewhere
if(stack->canMove() && stack->speed(0, true)) //probably no reason to try move war machines or bound stacks if(stack->canMove() && stack->getMovementRange(0)) //probably no reason to try move war machines or bound stacks
allowedActionList.push_back(PossiblePlayerBattleAction::MOVE_STACK); allowedActionList.push_back(PossiblePlayerBattleAction::MOVE_STACK);
const auto * siegedTown = battleGetDefendedTown(); const auto * siegedTown = battleGetDefendedTown();
@ -570,7 +570,7 @@ std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const Reacha
if(!unit->getPosition().isValid()) //turrets if(!unit->getPosition().isValid()) //turrets
return ret; return ret;
auto unitSpeed = unit->speed(0, true); auto unitSpeed = unit->getMovementRange(0);
const bool tacticsPhase = battleTacticDist() && battleGetTacticsSide() == unit->unitSide(); const bool tacticsPhase = battleTacticDist() && battleGetTacticsSide() == unit->unitSide();
@ -741,15 +741,15 @@ DamageEstimation CBattleInfoCallback::battleEstimateDamage(const battle::Unit *
{ {
RETURN_IF_NOT_BATTLE({}); RETURN_IF_NOT_BATTLE({});
auto reachability = battleGetDistances(attacker, attacker->getPosition()); auto reachability = battleGetDistances(attacker, attacker->getPosition());
int movementDistance = reachability[attackerPosition]; int getMovementRange = reachability[attackerPosition];
return battleEstimateDamage(attacker, defender, movementDistance, retaliationDmg); return battleEstimateDamage(attacker, defender, getMovementRange, retaliationDmg);
} }
DamageEstimation CBattleInfoCallback::battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int movementDistance, DamageEstimation * retaliationDmg) const DamageEstimation CBattleInfoCallback::battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int getMovementRange, DamageEstimation * retaliationDmg) const
{ {
RETURN_IF_NOT_BATTLE({}); 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, getMovementRange, shooting);
return battleEstimateDamage(bai, retaliationDmg); return battleEstimateDamage(bai, retaliationDmg);
} }

View File

@ -98,7 +98,7 @@ public:
/// returns pair <min dmg, max dmg> /// returns pair <min dmg, max dmg>
DamageEstimation battleEstimateDamage(const BattleAttackInfo & bai, DamageEstimation * 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, BattleHex attackerPosition, DamageEstimation * retaliationDmg = nullptr) const;
DamageEstimation battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int movementDistance, DamageEstimation * retaliationDmg = nullptr) const; DamageEstimation battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int getMovementRange, DamageEstimation * retaliationDmg = nullptr) const;
bool battleHasPenaltyOnLine(BattleHex from, BattleHex dest, bool checkWall, bool checkMoat) const; bool battleHasPenaltyOnLine(BattleHex from, BattleHex dest, bool checkWall, bool checkMoat) const;
bool battleHasDistancePenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const; bool battleHasDistancePenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const;

View File

@ -659,7 +659,7 @@ int BattleActionProcessor::moveStack(const CBattleInfoCallback & battle, int sta
ret = path.second; ret = path.second;
int creSpeed = curStack->speed(0, true); int creSpeed = curStack->getMovementRange(0);
if (battle.battleGetTacticDist() > 0 && creSpeed > 0) if (battle.battleGetTacticDist() > 0 && creSpeed > 0)
creSpeed = GameConstants::BFIELD_SIZE; creSpeed = GameConstants::BFIELD_SIZE;
@ -1139,18 +1139,10 @@ void BattleActionProcessor::attackCasting(const CBattleInfoCallback & battle, bo
for(const auto & sf : *spellsByType) for(const auto & sf : *spellsByType)
{ {
int meleeRanged; int meleeRanged;
if(sf->additionalInfo.size() < 2) vstd::amax(spellLevel, sf->additionalInfo[0]);
{ meleeRanged = sf->additionalInfo[1];
// legacy format
vstd::amax(spellLevel, sf->additionalInfo[0] % 1000); if (meleeRanged == CAddInfo::NONE || meleeRanged == 0 || (meleeRanged == 1 && ranged) || (meleeRanged == 2 && !ranged))
meleeRanged = sf->additionalInfo[0] / 1000;
}
else
{
vstd::amax(spellLevel, sf->additionalInfo[0]);
meleeRanged = sf->additionalInfo[1];
}
if (meleeRanged == 0 || (meleeRanged == 1 && ranged) || (meleeRanged == 2 && !ranged))
castMe = true; castMe = true;
} }
int chance = attacker->valOfBonuses((Selector::typeSubtype(attackMode, BonusSubtypeID(spellID)))); int chance = attacker->valOfBonuses((Selector::typeSubtype(attackMode, BonusSubtypeID(spellID))));