mirror of
https://github.com/vcmi/vcmi.git
synced 2025-11-06 09:09:40 +02:00
Merge pull request #4654 from dydzio0614/any-hex-shooting
Allow targeting empty hex by shooters with multi-tile SPELL_LIKE_ABILITY
This commit is contained in:
@@ -930,7 +930,7 @@ void CPlayerInterface::battleAttack(const BattleID & battleID, const BattleAttac
|
||||
info.secondaryDefender.push_back(cb->getBattle(battleID)->battleGetStackByID(elem.stackAttacked));
|
||||
}
|
||||
}
|
||||
assert(info.defender != nullptr);
|
||||
assert(info.defender != nullptr || (info.spellEffect != SpellID::NONE && info.indirectAttack));
|
||||
assert(info.attacker != nullptr);
|
||||
|
||||
battleInt->stackAttacking(info);
|
||||
|
||||
@@ -175,6 +175,18 @@ void BattleActionsController::enterCreatureCastingMode()
|
||||
if (!owner.stacksController->getActiveStack())
|
||||
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())
|
||||
return;
|
||||
|
||||
@@ -263,6 +275,9 @@ void BattleActionsController::reorderPossibleActionsPriority(const CStack * stac
|
||||
return 2;
|
||||
break;
|
||||
case PossiblePlayerBattleAction::SHOOT:
|
||||
if(targetStack == nullptr || targetStack->unitSide() == stack->unitSide())
|
||||
return 100; //bottom priority
|
||||
|
||||
return 4;
|
||||
break;
|
||||
case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
|
||||
@@ -356,6 +371,12 @@ const CSpell * BattleActionsController::getStackSpellToCast(BattleHex hoveredHex
|
||||
|
||||
auto action = selectAction(hoveredHex);
|
||||
|
||||
if(owner.stacksController->getActiveStack()->hasBonusOfType(BonusType::SPELL_LIKE_ATTACK))
|
||||
{
|
||||
auto bonus = owner.stacksController->getActiveStack()->getBonus(Selector::type()(BonusType::SPELL_LIKE_ATTACK));
|
||||
return bonus->subtype.as<SpellID>().toSpell();
|
||||
}
|
||||
|
||||
if (action.spell() == SpellID::NONE)
|
||||
return nullptr;
|
||||
|
||||
@@ -514,6 +535,13 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle
|
||||
|
||||
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(spellLikeAttackBonus != nullptr);
|
||||
return boost::str(boost::format(CGI->generaltexth->allTexts[26]) % spellLikeAttackBonus->subtype.as<SpellID>().toSpell()->getNameTranslated());
|
||||
}
|
||||
|
||||
const auto * shooter = owner.stacksController->getActiveStack();
|
||||
|
||||
DamageEstimation retaliation;
|
||||
@@ -625,7 +653,20 @@ bool BattleActionsController::actionIsLegal(PossiblePlayerBattleAction action, B
|
||||
return false;
|
||||
|
||||
case PossiblePlayerBattleAction::SHOOT:
|
||||
return owner.getBattle()->battleCanShoot(owner.stacksController->getActiveStack(), targetHex);
|
||||
{
|
||||
auto currentStack = owner.stacksController->getActiveStack();
|
||||
if(!owner.getBattle()->battleCanShoot(currentStack, targetHex))
|
||||
return false;
|
||||
|
||||
if(targetStack == nullptr && owner.getBattle()->battleCanTargetEmptyHex(currentStack))
|
||||
{
|
||||
auto spellLikeAttackBonus = currentStack->getBonus(Selector::type()(BonusType::SPELL_LIKE_ATTACK));
|
||||
const CSpell * spellDataToCheck = spellLikeAttackBonus->subtype.as<SpellID>().toSpell();
|
||||
return isCastingPossibleHere(spellDataToCheck, nullptr, targetHex);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case PossiblePlayerBattleAction::NO_LOCATION:
|
||||
return false;
|
||||
@@ -771,7 +812,7 @@ void BattleActionsController::actionRealize(PossiblePlayerBattleAction action, B
|
||||
}
|
||||
}
|
||||
|
||||
if (!spellcastingModeActive())
|
||||
if (!heroSpellcastingModeActive())
|
||||
{
|
||||
if (action.spell().hasValue())
|
||||
{
|
||||
@@ -1018,14 +1059,9 @@ void BattleActionsController::activateStack()
|
||||
|
||||
void BattleActionsController::onHexRightClicked(BattleHex clickedHex)
|
||||
{
|
||||
auto spellcastActionPredicate = [](PossiblePlayerBattleAction & action)
|
||||
{
|
||||
return action.spellcast();
|
||||
};
|
||||
bool isCurrentStackInSpellcastMode = creatureSpellcastingModeActive();
|
||||
|
||||
bool isCurrentStackInSpellcastMode = !possibleActions.empty() && std::all_of(possibleActions.begin(), possibleActions.end(), spellcastActionPredicate);
|
||||
|
||||
if (spellcastingModeActive() || isCurrentStackInSpellcastMode)
|
||||
if (heroSpellcastingModeActive() || isCurrentStackInSpellcastMode)
|
||||
{
|
||||
endCastingSpell();
|
||||
CRClickPopup::createAndPush(CGI->generaltexth->translate("core.genrltxt.731")); // spell cancelled
|
||||
@@ -1044,11 +1080,21 @@ void BattleActionsController::onHexRightClicked(BattleHex clickedHex)
|
||||
owner.defendingHero->heroRightClicked();
|
||||
}
|
||||
|
||||
bool BattleActionsController::spellcastingModeActive() const
|
||||
bool BattleActionsController::heroSpellcastingModeActive() const
|
||||
{
|
||||
return heroSpellToCast != nullptr;
|
||||
}
|
||||
|
||||
bool BattleActionsController::creatureSpellcastingModeActive() const
|
||||
{
|
||||
auto spellcastModePredicate = [](const PossiblePlayerBattleAction & action)
|
||||
{
|
||||
return action.spellcast() || action.get() == PossiblePlayerBattleAction::SHOOT; //for hotkey-eligible SPELL_LIKE_ATTACK creature should have only SHOOT action
|
||||
};
|
||||
|
||||
return !possibleActions.empty() && std::all_of(possibleActions.begin(), possibleActions.end(), spellcastModePredicate);
|
||||
}
|
||||
|
||||
bool BattleActionsController::currentActionSpellcasting(BattleHex hoveredHex)
|
||||
{
|
||||
if (heroSpellToCast)
|
||||
|
||||
@@ -82,8 +82,10 @@ public:
|
||||
/// initialize list of potential actions for new active stack
|
||||
void activateStack();
|
||||
|
||||
/// returns true if UI is currently in target selection mode
|
||||
bool spellcastingModeActive() const;
|
||||
/// returns true if UI is currently in hero spell target selection mode
|
||||
bool heroSpellcastingModeActive() const;
|
||||
/// returns true if UI is currently in "F" hotkey creature spell target selection mode
|
||||
bool creatureSpellcastingModeActive() const;
|
||||
|
||||
/// returns true if one of the following is true:
|
||||
/// - we are casting spell by hero
|
||||
|
||||
@@ -566,7 +566,9 @@ void BattleFieldController::showHighlightedHexes(Canvas & canvas)
|
||||
calculateRangeLimitAndHighlightImages(shootingRangeDistance, shootingRangeLimitImages, shootingRangeLimitHexes, shootingRangeLimitHexesHighlights);
|
||||
}
|
||||
|
||||
bool useSpellRangeForMouse = hoveredHex != BattleHex::INVALID && owner.actionsController->currentActionSpellcasting(getHoveredHex());
|
||||
bool useSpellRangeForMouse = hoveredHex != BattleHex::INVALID
|
||||
&& (owner.actionsController->currentActionSpellcasting(getHoveredHex())
|
||||
|| owner.actionsController->creatureSpellcastingModeActive()); //at least shooting with SPELL_LIKE_ATTACK can operate in spellcasting mode without being actual spellcast
|
||||
bool useMoveRangeForMouse = !hoveredMoveHexes.empty() || !settings["battle"]["mouseShadow"].Bool();
|
||||
|
||||
const auto & hoveredMouseHexes = useSpellRangeForMouse ? hoveredSpellHexes : ( useMoveRangeForMouse ? hoveredMoveHexes : hoveredMouseHex);
|
||||
|
||||
@@ -328,7 +328,7 @@ void BattleHero::setPhase(EHeroAnimType newPhase)
|
||||
|
||||
void BattleHero::heroLeftClicked()
|
||||
{
|
||||
if(owner.actionsController->spellcastingModeActive()) //we are casting a spell
|
||||
if(owner.actionsController->heroSpellcastingModeActive()) //we are casting a spell
|
||||
return;
|
||||
|
||||
if(!hero || !owner.makingTurn())
|
||||
|
||||
@@ -862,7 +862,8 @@ std::vector<const CStack *> BattleStacksController::selectHoveredStacks()
|
||||
spell = owner.actionsController->getCurrentSpell(hoveredHex);
|
||||
caster = owner.actionsController->getCurrentSpellcaster();
|
||||
|
||||
if(caster && spell && owner.actionsController->currentActionSpellcasting(hoveredHex) ) //when casting spell
|
||||
//casting spell or in explicit spellcasting mode that also handles SPELL_LIKE_ATTACK
|
||||
if(caster && spell && (owner.actionsController->currentActionSpellcasting(hoveredHex) || owner.actionsController->creatureSpellcastingModeActive()))
|
||||
{
|
||||
spells::Target target;
|
||||
target.emplace_back(hoveredHex);
|
||||
|
||||
@@ -538,7 +538,7 @@ void BattleWindow::tacticPhaseEnded()
|
||||
|
||||
void BattleWindow::bOptionsf()
|
||||
{
|
||||
if (owner.actionsController->spellcastingModeActive())
|
||||
if (owner.actionsController->heroSpellcastingModeActive())
|
||||
return;
|
||||
|
||||
CCS->curh->set(Cursor::Map::POINTER);
|
||||
@@ -548,7 +548,7 @@ void BattleWindow::bOptionsf()
|
||||
|
||||
void BattleWindow::bSurrenderf()
|
||||
{
|
||||
if (owner.actionsController->spellcastingModeActive())
|
||||
if (owner.actionsController->heroSpellcastingModeActive())
|
||||
return;
|
||||
|
||||
int cost = owner.getBattle()->battleGetSurrenderCost();
|
||||
@@ -568,7 +568,7 @@ void BattleWindow::bSurrenderf()
|
||||
|
||||
void BattleWindow::bFleef()
|
||||
{
|
||||
if (owner.actionsController->spellcastingModeActive())
|
||||
if (owner.actionsController->heroSpellcastingModeActive())
|
||||
return;
|
||||
|
||||
if ( owner.getBattle()->battleCanFlee() )
|
||||
@@ -675,7 +675,7 @@ void BattleWindow::setAlternativeActions(const std::list<PossiblePlayerBattleAct
|
||||
|
||||
void BattleWindow::bAutofightf()
|
||||
{
|
||||
if (owner.actionsController->spellcastingModeActive())
|
||||
if (owner.actionsController->heroSpellcastingModeActive())
|
||||
return;
|
||||
|
||||
if(settings["battle"]["endWithAutocombat"].Bool() && onlyOnePlayerHuman)
|
||||
@@ -712,7 +712,7 @@ void BattleWindow::bAutofightf()
|
||||
|
||||
void BattleWindow::bSpellf()
|
||||
{
|
||||
if (owner.actionsController->spellcastingModeActive())
|
||||
if (owner.actionsController->heroSpellcastingModeActive())
|
||||
return;
|
||||
|
||||
if (!owner.makingTurn())
|
||||
@@ -785,7 +785,7 @@ void BattleWindow::bSwitchActionf()
|
||||
|
||||
void BattleWindow::bWaitf()
|
||||
{
|
||||
if (owner.actionsController->spellcastingModeActive())
|
||||
if (owner.actionsController->heroSpellcastingModeActive())
|
||||
return;
|
||||
|
||||
if (owner.stacksController->getActiveStack() != nullptr)
|
||||
@@ -794,7 +794,7 @@ void BattleWindow::bWaitf()
|
||||
|
||||
void BattleWindow::bDefencef()
|
||||
{
|
||||
if (owner.actionsController->spellcastingModeActive())
|
||||
if (owner.actionsController->heroSpellcastingModeActive())
|
||||
return;
|
||||
|
||||
if (owner.stacksController->getActiveStack() != nullptr)
|
||||
@@ -803,7 +803,7 @@ void BattleWindow::bDefencef()
|
||||
|
||||
void BattleWindow::bConsoleUpf()
|
||||
{
|
||||
if (owner.actionsController->spellcastingModeActive())
|
||||
if (owner.actionsController->heroSpellcastingModeActive())
|
||||
return;
|
||||
|
||||
console->scrollUp();
|
||||
@@ -811,7 +811,7 @@ void BattleWindow::bConsoleUpf()
|
||||
|
||||
void BattleWindow::bConsoleDownf()
|
||||
{
|
||||
if (owner.actionsController->spellcastingModeActive())
|
||||
if (owner.actionsController->heroSpellcastingModeActive())
|
||||
return;
|
||||
|
||||
console->scrollDown();
|
||||
@@ -851,8 +851,8 @@ void BattleWindow::blockUI(bool on)
|
||||
setShortcutBlocked(EShortcut::BATTLE_WAIT, on || owner.tacticsMode || !canWait);
|
||||
setShortcutBlocked(EShortcut::BATTLE_DEFEND, on || owner.tacticsMode);
|
||||
setShortcutBlocked(EShortcut::BATTLE_SELECT_ACTION, on || owner.tacticsMode);
|
||||
setShortcutBlocked(EShortcut::BATTLE_AUTOCOMBAT, (settings["battle"]["endWithAutocombat"].Bool() && onlyOnePlayerHuman) ? on || owner.tacticsMode || owner.actionsController->spellcastingModeActive() : owner.actionsController->spellcastingModeActive());
|
||||
setShortcutBlocked(EShortcut::BATTLE_END_WITH_AUTOCOMBAT, on || owner.tacticsMode || !onlyOnePlayerHuman || owner.actionsController->spellcastingModeActive());
|
||||
setShortcutBlocked(EShortcut::BATTLE_AUTOCOMBAT, (settings["battle"]["endWithAutocombat"].Bool() && onlyOnePlayerHuman) ? on || owner.tacticsMode || owner.actionsController->heroSpellcastingModeActive() : owner.actionsController->heroSpellcastingModeActive());
|
||||
setShortcutBlocked(EShortcut::BATTLE_END_WITH_AUTOCOMBAT, on || owner.tacticsMode || !onlyOnePlayerHuman || owner.actionsController->heroSpellcastingModeActive());
|
||||
setShortcutBlocked(EShortcut::BATTLE_TACTICS_END, on || !owner.tacticsMode);
|
||||
setShortcutBlocked(EShortcut::BATTLE_TACTICS_NEXT, on || !owner.tacticsMode);
|
||||
setShortcutBlocked(EShortcut::BATTLE_CONSOLE_DOWN, on && !owner.tacticsMode);
|
||||
|
||||
Reference in New Issue
Block a user