mirror of
https://github.com/vcmi/vcmi.git
synced 2025-04-07 07:10:04 +02:00
Initial unconditionally working version
This commit is contained in:
parent
894c88defc
commit
1a2d349267
@ -175,6 +175,18 @@ void BattleActionsController::enterCreatureCastingMode()
|
|||||||
if (!owner.stacksController->getActiveStack())
|
if (!owner.stacksController->getActiveStack())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if(owner.getBattle()->battleCanTargetEmptyHex(owner.stacksController->getActiveStack()))
|
||||||
|
{
|
||||||
|
auto actionFilterPredicate = [](const PossiblePlayerBattleAction x)
|
||||||
|
{
|
||||||
|
return x.get() != PossiblePlayerBattleAction::SHOOT;
|
||||||
|
};
|
||||||
|
|
||||||
|
vstd::erase_if(possibleActions, actionFilterPredicate);
|
||||||
|
GH.fakeMouseMove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!isActiveStackSpellcaster())
|
if (!isActiveStackSpellcaster())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -263,6 +275,9 @@ void BattleActionsController::reorderPossibleActionsPriority(const CStack * stac
|
|||||||
return 2;
|
return 2;
|
||||||
break;
|
break;
|
||||||
case PossiblePlayerBattleAction::SHOOT:
|
case PossiblePlayerBattleAction::SHOOT:
|
||||||
|
if(targetStack == nullptr || targetStack->unitSide() == stack->unitSide())
|
||||||
|
return 100; //bottom priority
|
||||||
|
|
||||||
return 4;
|
return 4;
|
||||||
break;
|
break;
|
||||||
case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
|
case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
|
||||||
@ -514,6 +529,13 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle
|
|||||||
|
|
||||||
case PossiblePlayerBattleAction::SHOOT:
|
case PossiblePlayerBattleAction::SHOOT:
|
||||||
{
|
{
|
||||||
|
if(targetStack == nullptr) //should be true only for spell-like attack
|
||||||
|
{
|
||||||
|
auto spellLikeAttackBonus = owner.stacksController->getActiveStack()->getBonus(Selector::type()(BonusType::SPELL_LIKE_ATTACK));
|
||||||
|
assert(bonus != nullptr);
|
||||||
|
return boost::str(boost::format(CGI->generaltexth->allTexts[26]) % spellLikeAttackBonus->subtype.as<SpellID>().toSpell()->getNameTranslated());
|
||||||
|
}
|
||||||
|
|
||||||
const auto * shooter = owner.stacksController->getActiveStack();
|
const auto * shooter = owner.stacksController->getActiveStack();
|
||||||
|
|
||||||
DamageEstimation retaliation;
|
DamageEstimation retaliation;
|
||||||
@ -1020,7 +1042,7 @@ void BattleActionsController::onHexRightClicked(BattleHex clickedHex)
|
|||||||
{
|
{
|
||||||
auto spellcastActionPredicate = [](PossiblePlayerBattleAction & action)
|
auto spellcastActionPredicate = [](PossiblePlayerBattleAction & action)
|
||||||
{
|
{
|
||||||
return action.spellcast();
|
return action.spellcast() || action.get() == PossiblePlayerBattleAction::SHOOT;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool isCurrentStackInSpellcastMode = !possibleActions.empty() && std::all_of(possibleActions.begin(), possibleActions.end(), spellcastActionPredicate);
|
bool isCurrentStackInSpellcastMode = !possibleActions.empty() && std::all_of(possibleActions.begin(), possibleActions.end(), spellcastActionPredicate);
|
||||||
|
@ -725,18 +725,48 @@ bool CBattleInfoCallback::battleCanShoot(const battle::Unit * attacker) const
|
|||||||
|| attacker->hasBonusOfType(BonusType::FREE_SHOOTING));
|
|| attacker->hasBonusOfType(BonusType::FREE_SHOOTING));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CBattleInfoCallback::battleCanTargetEmptyHex(const battle::Unit * attacker) const
|
||||||
|
{
|
||||||
|
RETURN_IF_NOT_BATTLE(false);
|
||||||
|
|
||||||
|
if(attacker->hasBonusOfType(BonusType::SPELL_LIKE_ATTACK))
|
||||||
|
{
|
||||||
|
auto bonus = attacker->getBonus(Selector::type()(BonusType::SPELL_LIKE_ATTACK));
|
||||||
|
const CSpell * spell = bonus->subtype.as<SpellID>().toSpell();
|
||||||
|
if(spell->isDamage())
|
||||||
|
{
|
||||||
|
spells::BattleCast cast(this, attacker, spells::Mode::SPELL_LIKE_ATTACK, spell);
|
||||||
|
|
||||||
|
if(vstd::find(spell->battleMechanics(&cast)->getTargetTypes(), spells::AimType::LOCATION) != spell->battleMechanics(&cast)->getTargetTypes().end())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool CBattleInfoCallback::battleCanShoot(const battle::Unit * attacker, BattleHex dest) const
|
bool CBattleInfoCallback::battleCanShoot(const battle::Unit * attacker, BattleHex dest) const
|
||||||
{
|
{
|
||||||
RETURN_IF_NOT_BATTLE(false);
|
RETURN_IF_NOT_BATTLE(false);
|
||||||
|
|
||||||
const battle::Unit * defender = battleGetUnitByPos(dest);
|
const battle::Unit * defender = battleGetUnitByPos(dest);
|
||||||
if(!attacker || !defender)
|
if(!attacker)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if(defender->hasBonusOfType(BonusType::INVINCIBLE))
|
bool emptyHexAreaAttack = battleCanTargetEmptyHex(attacker);
|
||||||
return false;
|
|
||||||
|
|
||||||
if(battleMatchOwner(attacker, defender) && defender->alive())
|
if(!emptyHexAreaAttack)
|
||||||
|
{
|
||||||
|
if(!defender)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(defender->hasBonusOfType(BonusType::INVINCIBLE))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(emptyHexAreaAttack || (battleMatchOwner(attacker, defender) && defender->alive()))
|
||||||
{
|
{
|
||||||
if(battleCanShoot(attacker))
|
if(battleCanShoot(attacker))
|
||||||
{
|
{
|
||||||
@ -747,7 +777,11 @@ bool CBattleInfoCallback::battleCanShoot(const battle::Unit * attacker, BattleHe
|
|||||||
}
|
}
|
||||||
|
|
||||||
int shootingRange = limitedRangeBonus->val;
|
int shootingRange = limitedRangeBonus->val;
|
||||||
return isEnemyUnitWithinSpecifiedRange(attacker->getPosition(), defender, shootingRange);
|
|
||||||
|
if(defender)
|
||||||
|
return isEnemyUnitWithinSpecifiedRange(attacker->getPosition(), defender, shootingRange);
|
||||||
|
else
|
||||||
|
return isHexWithinSpecifiedRange(attacker->getPosition(), dest, shootingRange);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1593,6 +1627,14 @@ bool CBattleInfoCallback::isEnemyUnitWithinSpecifiedRange(BattleHex attackerPosi
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CBattleInfoCallback::isHexWithinSpecifiedRange(BattleHex attackerPosition, BattleHex targetPosition, unsigned int range) const
|
||||||
|
{
|
||||||
|
if(BattleHex::getDistance(attackerPosition, targetPosition) <= range)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
BattleHex CBattleInfoCallback::wallPartToBattleHex(EWallPart part) const
|
BattleHex CBattleInfoCallback::wallPartToBattleHex(EWallPart part) const
|
||||||
{
|
{
|
||||||
RETURN_IF_NOT_BATTLE(BattleHex::INVALID);
|
RETURN_IF_NOT_BATTLE(BattleHex::INVALID);
|
||||||
|
@ -86,9 +86,11 @@ public:
|
|||||||
ReachabilityInfo::TDistances battleGetDistances(const battle::Unit * unit, BattleHex assumedPosition) const;
|
ReachabilityInfo::TDistances battleGetDistances(const battle::Unit * unit, BattleHex assumedPosition) const;
|
||||||
std::set<BattleHex> battleGetAttackedHexes(const battle::Unit * attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID) const;
|
std::set<BattleHex> battleGetAttackedHexes(const battle::Unit * attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID) const;
|
||||||
bool isEnemyUnitWithinSpecifiedRange(BattleHex attackerPosition, const battle::Unit * defenderUnit, unsigned int range) const;
|
bool isEnemyUnitWithinSpecifiedRange(BattleHex attackerPosition, const battle::Unit * defenderUnit, unsigned int range) const;
|
||||||
|
bool isHexWithinSpecifiedRange(BattleHex attackerPosition, BattleHex targetPosition, unsigned int range) const;
|
||||||
|
|
||||||
std::pair< std::vector<BattleHex>, int > getPath(BattleHex start, BattleHex dest, const battle::Unit * stack) const;
|
std::pair< std::vector<BattleHex>, int > getPath(BattleHex start, BattleHex dest, const battle::Unit * stack) const;
|
||||||
|
|
||||||
|
bool battleCanTargetEmptyHex(const battle::Unit * attacker) const; //determines of stack with given ID can target empty hex to attack - currently used only for SPELL_LIKE_ATTACK shooting
|
||||||
bool battleCanAttack(const battle::Unit * stack, const battle::Unit * target, BattleHex dest) const; //determines if stack with given ID can attack target at the selected destination
|
bool battleCanAttack(const battle::Unit * stack, const battle::Unit * target, BattleHex dest) const; //determines if stack with given ID can attack target at the selected destination
|
||||||
bool battleCanShoot(const battle::Unit * attacker, BattleHex dest) const; //determines if stack with given ID shoot at the selected destination
|
bool battleCanShoot(const battle::Unit * attacker, BattleHex dest) const; //determines if stack with given ID shoot at the selected destination
|
||||||
bool battleCanShoot(const battle::Unit * attacker) const; //determines if stack with given ID shoot in principle
|
bool battleCanShoot(const battle::Unit * attacker) const; //determines if stack with given ID shoot in principle
|
||||||
|
@ -166,8 +166,6 @@ EffectTarget UnitEffect::transformTargetByRange(const Mechanics * m, const Targe
|
|||||||
{
|
{
|
||||||
if(!aimPoint.empty() && aimPoint.front().unitValue)
|
if(!aimPoint.empty() && aimPoint.front().unitValue)
|
||||||
targets.insert(aimPoint.front().unitValue);
|
targets.insert(aimPoint.front().unitValue);
|
||||||
else
|
|
||||||
logGlobal->error("Spell-like attack with no primary target.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EffectTarget effectTarget;
|
EffectTarget effectTarget;
|
||||||
|
@ -347,20 +347,27 @@ bool BattleActionProcessor::doShootAction(const CBattleInfoCallback & battle, co
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!destinationStack)
|
const bool emptyTileAreaAttack = battle.battleCanTargetEmptyHex(stack);
|
||||||
|
|
||||||
|
if (!destinationStack && !emptyTileAreaAttack)
|
||||||
{
|
{
|
||||||
gameHandler->complain("No target to shoot!");
|
gameHandler->complain("No target to shoot!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const auto firstStrikeSelector = Selector::typeSubtype(BonusType::FIRST_STRIKE, BonusCustomSubtype::damageTypeAll).Or(Selector::typeSubtype(BonusType::FIRST_STRIKE, BonusCustomSubtype::damageTypeRanged));
|
bool firstStrike = false;
|
||||||
const bool firstStrike = destinationStack->hasBonus(firstStrikeSelector);
|
if(!emptyTileAreaAttack)
|
||||||
|
{
|
||||||
|
static const auto firstStrikeSelector = Selector::typeSubtype(BonusType::FIRST_STRIKE, BonusCustomSubtype::damageTypeAll).Or(Selector::typeSubtype(BonusType::FIRST_STRIKE, BonusCustomSubtype::damageTypeRanged));
|
||||||
|
firstStrike = destinationStack->hasBonus(firstStrikeSelector);
|
||||||
|
}
|
||||||
|
|
||||||
if (!firstStrike)
|
if (!firstStrike)
|
||||||
makeAttack(battle, stack, destinationStack, 0, destination, true, true, false);
|
makeAttack(battle, stack, destinationStack, 0, destination, true, true, false);
|
||||||
|
|
||||||
//ranged counterattack
|
//ranged counterattack
|
||||||
if (destinationStack->hasBonusOfType(BonusType::RANGED_RETALIATION)
|
if (!emptyTileAreaAttack
|
||||||
|
&& destinationStack->hasBonusOfType(BonusType::RANGED_RETALIATION)
|
||||||
&& !stack->hasBonusOfType(BonusType::BLOCKS_RANGED_RETALIATION)
|
&& !stack->hasBonusOfType(BonusType::BLOCKS_RANGED_RETALIATION)
|
||||||
&& destinationStack->ableToRetaliate()
|
&& destinationStack->ableToRetaliate()
|
||||||
&& battle.battleCanShoot(destinationStack, stack->getPosition())
|
&& battle.battleCanShoot(destinationStack, stack->getPosition())
|
||||||
@ -381,11 +388,9 @@ bool BattleActionProcessor::doShootAction(const CBattleInfoCallback & battle, co
|
|||||||
|
|
||||||
for(int i = firstStrike ? 0:1; i < totalRangedAttacks; ++i)
|
for(int i = firstStrike ? 0:1; i < totalRangedAttacks; ++i)
|
||||||
{
|
{
|
||||||
if(
|
if(stack->alive()
|
||||||
stack->alive()
|
&& (emptyTileAreaAttack || destinationStack->alive())
|
||||||
&& destinationStack->alive()
|
&& stack->shots.canUse())
|
||||||
&& stack->shots.canUse()
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
makeAttack(battle, stack, destinationStack, 0, destination, false, true, false);
|
makeAttack(battle, stack, destinationStack, 0, destination, false, true, false);
|
||||||
}
|
}
|
||||||
@ -907,7 +912,7 @@ int BattleActionProcessor::moveStack(const CBattleInfoCallback & battle, int sta
|
|||||||
|
|
||||||
void BattleActionProcessor::makeAttack(const CBattleInfoCallback & battle, const CStack * attacker, const CStack * defender, int distance, BattleHex targetHex, bool first, bool ranged, bool counter)
|
void BattleActionProcessor::makeAttack(const CBattleInfoCallback & battle, const CStack * attacker, const CStack * defender, int distance, BattleHex targetHex, bool first, bool ranged, bool counter)
|
||||||
{
|
{
|
||||||
if(first && !counter)
|
if(defender && first && !counter)
|
||||||
handleAttackBeforeCasting(battle, ranged, attacker, defender);
|
handleAttackBeforeCasting(battle, ranged, attacker, defender);
|
||||||
|
|
||||||
FireShieldInfo fireShield;
|
FireShieldInfo fireShield;
|
||||||
@ -962,7 +967,7 @@ void BattleActionProcessor::makeAttack(const CBattleInfoCallback & battle, const
|
|||||||
battle::HealInfo healInfo;
|
battle::HealInfo healInfo;
|
||||||
|
|
||||||
// only primary target
|
// only primary target
|
||||||
if(defender->alive())
|
if(defender && defender->alive())
|
||||||
applyBattleEffects(battle, bat, attackerState, fireShield, defender, healInfo, distance, false);
|
applyBattleEffects(battle, bat, attackerState, fireShield, defender, healInfo, distance, false);
|
||||||
|
|
||||||
//multiple-hex normal attack
|
//multiple-hex normal attack
|
||||||
@ -1044,7 +1049,8 @@ void BattleActionProcessor::makeAttack(const CBattleInfoCallback & battle, const
|
|||||||
|
|
||||||
addGenericDamageLog(blm, attackerState, totalDamage);
|
addGenericDamageLog(blm, attackerState, totalDamage);
|
||||||
|
|
||||||
addGenericKilledLog(blm, defender, totalKills, multipleTargets);
|
if(defender)
|
||||||
|
addGenericKilledLog(blm, defender, totalKills, multipleTargets);
|
||||||
}
|
}
|
||||||
|
|
||||||
// drain life effect (as well as log entry) must be applied after the attack
|
// drain life effect (as well as log entry) must be applied after the attack
|
||||||
@ -1110,7 +1116,8 @@ void BattleActionProcessor::makeAttack(const CBattleInfoCallback & battle, const
|
|||||||
|
|
||||||
gameHandler->sendAndApply(&blm);
|
gameHandler->sendAndApply(&blm);
|
||||||
|
|
||||||
handleAfterAttackCasting(battle, ranged, attacker, defender);
|
if(defender)
|
||||||
|
handleAfterAttackCasting(battle, ranged, attacker, defender);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BattleActionProcessor::attackCasting(const CBattleInfoCallback & battle, bool ranged, BonusType attackMode, const battle::Unit * attacker, const CStack * defender)
|
void BattleActionProcessor::attackCasting(const CBattleInfoCallback & battle, bool ranged, BonusType attackMode, const battle::Unit * attacker, const CStack * defender)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user