From b0c511149de8f17ae2fa15e730a45a64829ada1d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 11 Jul 2025 17:11:01 +0300 Subject: [PATCH] Removed hardcoded checks for Summon Boat spell --- .../Pathfinding/Actions/BoatActions.cpp | 6 ++--- .../Pathfinding/Actions/BoatActions.h | 6 +++++ .../Rules/AILayerTransitionRule.cpp | 22 ++++++++++++++----- AI/VCAI/Pathfinding/Actions/BoatActions.cpp | 6 ++--- AI/VCAI/Pathfinding/Actions/BoatActions.h | 4 +++- .../Rules/AILayerTransitionRule.cpp | 22 ++++++++++++++----- lib/callback/CDynLibHandler.cpp | 4 ++++ lib/spells/adventure/AdventureSpellEffect.h | 4 ++-- lib/spells/adventure/SummonBoatEffect.cpp | 15 ++++++++++--- lib/spells/adventure/SummonBoatEffect.h | 5 ++++- 10 files changed, 68 insertions(+), 26 deletions(-) diff --git a/AI/Nullkiller/Pathfinding/Actions/BoatActions.cpp b/AI/Nullkiller/Pathfinding/Actions/BoatActions.cpp index 548792412..50bb18f51 100644 --- a/AI/Nullkiller/Pathfinding/Actions/BoatActions.cpp +++ b/AI/Nullkiller/Pathfinding/Actions/BoatActions.cpp @@ -92,7 +92,7 @@ namespace AIPathfinding void SummonBoatAction::execute(AIGateway * ai, const CGHeroInstance * hero) const { - Goals::AdventureSpellCast(hero, SpellID::SUMMON_BOAT).accept(ai); + Goals::AdventureSpellCast(hero, usedSpell).accept(ai); } const ChainActor * SummonBoatAction::getActor(const ChainActor * sourceActor) const @@ -139,10 +139,8 @@ namespace AIPathfinding int32_t SummonBoatAction::getManaCost(const CGHeroInstance * hero) const { - SpellID summonBoat = SpellID::SUMMON_BOAT; - // FIXME: this should be hero->getSpellCost, however currently queries to bonus system are too slow - return summonBoat.toSpell()->getCost(0); + return usedSpell.toSpell()->getCost(0); } } diff --git a/AI/Nullkiller/Pathfinding/Actions/BoatActions.h b/AI/Nullkiller/Pathfinding/Actions/BoatActions.h index 76857c3ca..b9034f910 100644 --- a/AI/Nullkiller/Pathfinding/Actions/BoatActions.h +++ b/AI/Nullkiller/Pathfinding/Actions/BoatActions.h @@ -24,7 +24,13 @@ namespace AIPathfinding class SummonBoatAction : public VirtualBoatAction { + SpellID usedSpell; public: + SummonBoatAction(SpellID usedSpell) + : usedSpell(usedSpell) + { + } + void execute(AIGateway * ai, const CGHeroInstance * hero) const override; virtual void applyOnDestination( diff --git a/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp index 8d95d6224..ffb780170 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp @@ -12,6 +12,8 @@ #include "../../Engine/Nullkiller.h" #include "../../../../lib/pathfinder/CPathfinder.h" #include "../../../../lib/pathfinder/TurnInfo.h" +#include "../../../../lib/spells/ISpellMechanics.h" +#include "../../../../lib/spells/adventure/SummonBoatEffect.h" namespace NKAI { @@ -159,13 +161,21 @@ namespace AIPathfinding for(const CGHeroInstance * hero : nodeStorage->getAllHeroes()) { - auto summonBoatSpell = SpellID(SpellID::SUMMON_BOAT).toSpell(); - - if(hero->canCastThisSpell(summonBoatSpell) - && hero->getSpellSchoolLevel(summonBoatSpell) >= MasteryLevel::ADVANCED) + for (const auto & spell : LIBRARY->spellh->objects) { - // TODO: For lower school level we might need to check the existence of some boat - summonableVirtualBoats[hero] = std::make_shared(); + if (!spell || !spell->isAdventure()) + continue; + + auto effect = spell->getAdventureMechanics().getEffectAs(hero); + + if (!effect || !hero->canCastThisSpell(spell.get())) + continue; + + if (effect->canCreateNewBoat() && effect->getSuccessChance(hero) == 100) + { + // TODO: For lower school level we might need to check the existence of some boat + summonableVirtualBoats[hero] = std::make_shared(spell->id); + } } } } diff --git a/AI/VCAI/Pathfinding/Actions/BoatActions.cpp b/AI/VCAI/Pathfinding/Actions/BoatActions.cpp index 50df68395..22f6b39cd 100644 --- a/AI/VCAI/Pathfinding/Actions/BoatActions.cpp +++ b/AI/VCAI/Pathfinding/Actions/BoatActions.cpp @@ -23,7 +23,7 @@ namespace AIPathfinding Goals::TSubgoal SummonBoatAction::whatToDo(const HeroPtr & hero) const { - return Goals::sptr(Goals::AdventureSpellCast(hero, SpellID::SUMMON_BOAT)); + return Goals::sptr(Goals::AdventureSpellCast(hero, usedSpell)); } void SummonBoatAction::applyOnDestination( @@ -53,8 +53,6 @@ namespace AIPathfinding uint32_t SummonBoatAction::getManaCost(const CGHeroInstance * hero) const { - SpellID summonBoat = SpellID::SUMMON_BOAT; - - return hero->getSpellCost(summonBoat.toSpell()); + return hero->getSpellCost(usedSpell.toSpell()); } } diff --git a/AI/VCAI/Pathfinding/Actions/BoatActions.h b/AI/VCAI/Pathfinding/Actions/BoatActions.h index 793a74486..f358d60f8 100644 --- a/AI/VCAI/Pathfinding/Actions/BoatActions.h +++ b/AI/VCAI/Pathfinding/Actions/BoatActions.h @@ -34,9 +34,11 @@ namespace AIPathfinding class SummonBoatAction : public VirtualBoatAction { + SpellID usedSpell; public: - SummonBoatAction() + SummonBoatAction(SpellID usedSpell) :VirtualBoatAction(AINodeStorage::CAST_CHAIN) + ,usedSpell(usedSpell) { } diff --git a/AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.cpp b/AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.cpp index d7b044ffd..34d12f2ff 100644 --- a/AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.cpp +++ b/AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.cpp @@ -10,6 +10,9 @@ #include "StdInc.h" #include "AILayerTransitionRule.h" +#include "../../../../lib/spells/ISpellMechanics.h" +#include "../../../../lib/spells/adventure/SummonBoatEffect.h" + namespace AIPathfinding { AILayerTransitionRule::AILayerTransitionRule(CPlayerSpecificInfoCallback * cb, VCAI * ai, std::shared_ptr nodeStorage) @@ -74,13 +77,22 @@ namespace AIPathfinding } auto hero = nodeStorage->getHero(); - auto summonBoatSpell = SpellID(SpellID::SUMMON_BOAT).toSpell(); - if(hero->canCastThisSpell(summonBoatSpell) - && hero->getSpellSchoolLevel(summonBoatSpell) >= MasteryLevel::ADVANCED) + for (const auto & spell : LIBRARY->spellh->objects) { - // TODO: For lower school level we might need to check the existence of some boat - summonableVirtualBoat.reset(new SummonBoatAction()); + if (!spell || !spell->isAdventure()) + continue; + + auto effect = spell->getAdventureMechanics().getEffectAs(hero); + + if (!effect || !hero->canCastThisSpell(spell.get())) + continue; + + if (effect->canCreateNewBoat() && effect->getSuccessChance(hero) == 100) + { + // TODO: For lower school level we might need to check the existence of some boat + summonableVirtualBoat.reset(new SummonBoatAction(spell->id)); + } } } diff --git a/lib/callback/CDynLibHandler.cpp b/lib/callback/CDynLibHandler.cpp index 50e669fa6..6892a791c 100644 --- a/lib/callback/CDynLibHandler.cpp +++ b/lib/callback/CDynLibHandler.cpp @@ -67,6 +67,10 @@ VCMI_LIB_NAMESPACE_BEGIN getName = reinterpret_cast(dlsym(dll, "GetAiName")); getAI = reinterpret_cast(dlsym(dll, methodName.c_str())); } + else + { + logGlobal->error("Cannot open dynamic library '%s'. Reason: %s", libpath.string(), dlerror()); + } #endif // VCMI_WINDOWS if (!dll) diff --git a/lib/spells/adventure/AdventureSpellEffect.h b/lib/spells/adventure/AdventureSpellEffect.h index f1ffcf1b4..c71c0e07b 100644 --- a/lib/spells/adventure/AdventureSpellEffect.h +++ b/lib/spells/adventure/AdventureSpellEffect.h @@ -43,7 +43,7 @@ public: AdventureSpellEffect() = default; }; -class AdventureSpellRangedEffect : public IAdventureSpellEffect +class DLL_LINKAGE AdventureSpellRangedEffect : public IAdventureSpellEffect { int rangeX; int rangeY; @@ -52,7 +52,7 @@ class AdventureSpellRangedEffect : public IAdventureSpellEffect public: AdventureSpellRangedEffect(const JsonNode & config); - DLL_LINKAGE bool isTargetInRange(const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const; + bool isTargetInRange(const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const; std::string getCursorForTarget(const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const override = 0; //must be implemented in derived classes bool canBeCastAtImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const override = 0; //must be implemented in derived classes }; diff --git a/lib/spells/adventure/SummonBoatEffect.cpp b/lib/spells/adventure/SummonBoatEffect.cpp index 857920c62..4f705fb0b 100644 --- a/lib/spells/adventure/SummonBoatEffect.cpp +++ b/lib/spells/adventure/SummonBoatEffect.cpp @@ -28,6 +28,17 @@ SummonBoatEffect::SummonBoatEffect(const CSpell * s, const JsonNode & config) { } +bool SummonBoatEffect::canCreateNewBoat() const +{ + return createNewBoat; +} + +int SummonBoatEffect::getSuccessChance(const spells::Caster * caster) const +{ + const auto schoolLevel = caster->getSpellSchoolLevel(owner); + return owner->getLevelPower(schoolLevel); +} + bool SummonBoatEffect::canBeCastImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const { if(!caster->getHeroCaster()) @@ -56,10 +67,8 @@ bool SummonBoatEffect::canBeCastImpl(spells::Problem & problem, const IGameInfoC ESpellCastResult SummonBoatEffect::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const { - const auto schoolLevel = parameters.caster->getSpellSchoolLevel(owner); - //check if spell works at all - if(env->getRNG()->nextInt(0, 99) >= owner->getLevelPower(schoolLevel)) //power is % chance of success + if(env->getRNG()->nextInt(0, 99) >= getSuccessChance(parameters.caster)) //power is % chance of success { InfoWindow iw; iw.player = parameters.caster->getCasterOwner(); diff --git a/lib/spells/adventure/SummonBoatEffect.h b/lib/spells/adventure/SummonBoatEffect.h index 8994c189e..64b69881e 100644 --- a/lib/spells/adventure/SummonBoatEffect.h +++ b/lib/spells/adventure/SummonBoatEffect.h @@ -14,7 +14,7 @@ VCMI_LIB_NAMESPACE_BEGIN -class SummonBoatEffect final : public IAdventureSpellEffect +class DLL_LINKAGE SummonBoatEffect final : public IAdventureSpellEffect { const CSpell * owner; bool useExistingBoat; @@ -23,6 +23,9 @@ class SummonBoatEffect final : public IAdventureSpellEffect public: SummonBoatEffect(const CSpell * s, const JsonNode & config); + bool canCreateNewBoat() const; + int getSuccessChance(const spells::Caster * caster) const; + protected: bool canBeCastImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const final;