1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-28 08:48:48 +02:00

Fixed Genie spellcasting logic to account for spell immunities

This commit is contained in:
Ivan Savenko 2023-12-24 01:10:05 +02:00
parent 822677af77
commit 85de3143ff
5 changed files with 62 additions and 49 deletions

View File

@ -70,8 +70,9 @@ std::vector<BattleHex> BattleEvaluator::getBrokenWallMoatHexes() const
std::optional<PossibleSpellcast> BattleEvaluator::findBestCreatureSpell(const CStack *stack)
{
//TODO: faerie dragon type spell should be selected by server
SpellID creatureSpellToCast = cb->getBattle(battleID)->battleGetRandomStackSpell(CRandomGenerator::getDefault(), stack, CBattleInfoCallback::RANDOM_AIMED);
if(stack->hasBonusOfType(BonusType::SPELLCASTER) && stack->canCast() && creatureSpellToCast != SpellID::NONE)
SpellID creatureSpellToCast = cb->getBattle(battleID)->getRandomCastedSpell(CRandomGenerator::getDefault(), stack);
if(stack->canCast() && creatureSpellToCast != SpellID::NONE)
{
const CSpell * spell = creatureSpellToCast.toSpell();

View File

@ -616,8 +616,8 @@ bool BattleActionsController::actionIsLegal(PossiblePlayerBattleAction action, B
case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
if(targetStack && targetStackOwned && targetStack != owner.stacksController->getActiveStack() && targetStack->alive()) //only positive spells for other allied creatures
{
int spellID = owner.getBattle()->battleGetRandomStackSpell(CRandomGenerator::getDefault(), targetStack, CBattleInfoCallback::RANDOM_GENIE);
return spellID > -1;
SpellID spellID = owner.getBattle()->getRandomBeneficialSpell(CRandomGenerator::getDefault(), owner.stacksController->getActiveStack(), targetStack);
return spellID != SpellID::NONE;
}
return false;
@ -887,7 +887,7 @@ void BattleActionsController::tryActivateStackSpellcasting(const CStack *casterS
{
// faerie dragon can cast only one, randomly selected spell until their next move
//TODO: faerie dragon type spell should be selected by server
const auto * spellToCast = owner.getBattle()->battleGetRandomStackSpell(CRandomGenerator::getDefault(), casterStack, CBattleInfoCallback::RANDOM_AIMED).toSpell();
const auto * spellToCast = owner.getBattle()->getRandomCastedSpell(CRandomGenerator::getDefault(), casterStack).toSpell();
if (spellToCast)
creatureSpells.push_back(spellToCast);

View File

@ -324,22 +324,6 @@ std::set<BattleHex> CBattleInfoCallback::battleGetAttackedHexes(const battle::Un
return attackedHexes;
}
SpellID CBattleInfoCallback::battleGetRandomStackSpell(CRandomGenerator & rand, const CStack * stack, ERandomSpell mode) const
{
switch (mode)
{
case RANDOM_GENIE:
return getRandomBeneficialSpell(rand, stack); //target
break;
case RANDOM_AIMED:
return getRandomCastedSpell(rand, stack); //caster
break;
default:
logGlobal->error("Incorrect mode of battleGetRandomSpell (%d)", static_cast<int>(mode));
return SpellID::NONE;
}
}
const CStack* CBattleInfoCallback::battleGetStackByPos(BattleHex pos, bool onlyAlive) const
{
RETURN_IF_NOT_BATTLE(nullptr);
@ -1610,7 +1594,7 @@ std::set<const battle::Unit *> CBattleInfoCallback::battleAdjacentUnits(const ba
return ret;
}
SpellID CBattleInfoCallback::getRandomBeneficialSpell(CRandomGenerator & rand, const CStack * subject) const
SpellID CBattleInfoCallback::getRandomBeneficialSpell(CRandomGenerator & rand, const battle::Unit * caster, const battle::Unit * subject) const
{
RETURN_IF_NOT_BATTLE(SpellID::NONE);
//This is complete list. No spells from mods.
@ -1658,9 +1642,19 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(CRandomGenerator & rand, c
std::stringstream cachingStr;
cachingStr << "source_" << vstd::to_underlying(BonusSource::SPELL_EFFECT) << "id_" << spellID.num;
if(subject->hasBonus(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(spellID)), Selector::all, cachingStr.str())
//TODO: this ability has special limitations
|| !(spellID.toSpell()->canBeCast(this, spells::Mode::CREATURE_ACTIVE, subject)))
if(subject->hasBonus(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(spellID)), Selector::all, cachingStr.str()))
continue;
auto spellPtr = spellID.toSpell();
spells::Target target;
target.emplace_back(subject);
spells::BattleCast cast(this, caster, spells::Mode::CREATURE_ACTIVE, spellPtr);
auto m = spellPtr->battleMechanics(&cast);
spells::detail::ProblemImpl problem;
if (!m->canBeCastAt(target, problem))
continue;
switch (spellID.toEnum())
@ -1703,7 +1697,7 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(CRandomGenerator & rand, c
case SpellID::CURE: //only damaged units
{
//do not cast on affected by debuffs
if(!subject->canBeHealed())
if(subject->getFirstHPleft() == subject->getMaxHealth())
continue;
}
break;

View File

@ -52,11 +52,6 @@ struct DLL_LINKAGE BattleClientInterfaceData
class DLL_LINKAGE CBattleInfoCallback : public virtual CBattleInfoEssentials
{
public:
enum ERandomSpell
{
RANDOM_GENIE, RANDOM_AIMED
};
std::optional<int> battleIsFinished() const override; //return none if battle is ongoing; otherwise the victorious side (0/1) or 2 if it is a draw
std::vector<std::shared_ptr<const CObstacleInstance>> battleGetAllObstaclesOnPos(BattleHex tile, bool onlyBlocking = true) const override;
@ -121,8 +116,7 @@ public:
int32_t battleGetSpellCost(const spells::Spell * sp, const CGHeroInstance * caster) const; //returns cost of given spell
ESpellCastProblem battleCanCastSpell(const spells::Caster * caster, spells::Mode mode) const; //returns true if there are no general issues preventing from casting a spell
SpellID battleGetRandomStackSpell(CRandomGenerator & rand, const CStack * stack, ERandomSpell mode) const;
SpellID getRandomBeneficialSpell(CRandomGenerator & rand, const CStack * subject) const;
SpellID getRandomBeneficialSpell(CRandomGenerator & rand, const battle::Unit * caster, const battle::Unit * target) const;
SpellID getRandomCastedSpell(CRandomGenerator & rand, const CStack * caster) const; //called at the beginning of turn for Faerie Dragon
std::vector<PossiblePlayerBattleAction> getClientActionsForStack(const CStack * stack, const BattleClientInterfaceData & data);

View File

@ -411,24 +411,48 @@ bool BattleActionProcessor::doUnitSpellAction(const CBattleInfoCallback & battle
std::shared_ptr<const Bonus> randSpellcaster = stack->getBonus(Selector::type()(BonusType::RANDOM_SPELLCASTER));
std::shared_ptr<const Bonus> spellcaster = stack->getBonus(Selector::typeSubtype(BonusType::SPELLCASTER, BonusSubtypeID(spellID)));
//TODO special bonus for genies ability
if (randSpellcaster && battle.battleGetRandomStackSpell(gameHandler->getRandomGenerator(), stack, CBattleInfoCallback::RANDOM_AIMED) == SpellID::NONE)
spellID = battle.battleGetRandomStackSpell(gameHandler->getRandomGenerator(), stack, CBattleInfoCallback::RANDOM_GENIE);
if (spellID == SpellID::NONE)
gameHandler->complain("That stack can't cast spells!");
else
if (!spellcaster && !randSpellcaster)
{
const CSpell * spell = SpellID(spellID).toSpell();
spells::BattleCast parameters(&battle, stack, spells::Mode::CREATURE_ACTIVE, spell);
int32_t spellLvl = 0;
if(spellcaster)
vstd::amax(spellLvl, spellcaster->val);
if(randSpellcaster)
vstd::amax(spellLvl, randSpellcaster->val);
parameters.setSpellLevel(spellLvl);
parameters.cast(gameHandler->spellEnv, target);
gameHandler->complain("That stack can't cast spells!");
return false;
}
if (randSpellcaster)
{
if (target.size() != 1)
{
gameHandler->complain("Invalid target for random spellcaster!");
return false;
}
const battle::Unit * subject = target[0].unitValue;
if (target[0].unitValue == nullptr)
subject = battle.battleGetStackByPos(target[0].hexValue, true);
if (subject == nullptr)
{
gameHandler->complain("Invalid target for random spellcaster!");
return false;
}
spellID = battle.getRandomBeneficialSpell(gameHandler->getRandomGenerator(), stack, subject);
if (spellID == SpellID::NONE)
{
gameHandler->complain("That stack can't cast spells!");
return false;
}
}
const CSpell * spell = SpellID(spellID).toSpell();
spells::BattleCast parameters(&battle, stack, spells::Mode::CREATURE_ACTIVE, spell);
int32_t spellLvl = 0;
if(spellcaster)
vstd::amax(spellLvl, spellcaster->val);
if(randSpellcaster)
vstd::amax(spellLvl, randSpellcaster->val);
parameters.setSpellLevel(spellLvl);
parameters.cast(gameHandler->spellEnv, target);
return true;
}