From f51c7c5c2877c8b15a06abd2907c945e5062d14d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 11 Jul 2025 17:38:03 +0300 Subject: [PATCH] Removed hardcoded AI logic for Water Walk and Fly spell --- AI/Nullkiller/Analyzers/HeroManager.cpp | 19 ++++++++-------- .../AdventureSpellCastMovementActions.cpp | 8 +++---- .../AdventureSpellCastMovementActions.h | 6 +++-- .../Rules/AILayerTransitionRule.cpp | 21 ++++++++++-------- lib/pathfinder/CPathfinder.cpp | 22 ++++++++++++++----- lib/spells/ISpellMechanics.h | 2 ++ .../adventure/AdventureSpellMechanics.cpp | 9 ++++++++ .../adventure/AdventureSpellMechanics.h | 1 + 8 files changed, 59 insertions(+), 29 deletions(-) diff --git a/AI/Nullkiller/Analyzers/HeroManager.cpp b/AI/Nullkiller/Analyzers/HeroManager.cpp index efaf14a4e..69aee6b37 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.cpp +++ b/AI/Nullkiller/Analyzers/HeroManager.cpp @@ -12,6 +12,8 @@ #include "../Engine/Nullkiller.h" #include "../../../lib/mapObjects/MapObjects.h" #include "../../../lib/IGameSettings.h" +#include "../../../lib/spells/ISpellMechanics.h" +#include "../../../lib/spells/adventure/TownPortalEffect.h" namespace NKAI { @@ -210,32 +212,31 @@ float HeroManager::getFightingStrengthCached(const CGHeroInstance * hero) const float HeroManager::getMagicStrength(const CGHeroInstance * hero) const { - auto hasFly = hero->spellbookContainsSpell(SpellID::FLY); - auto hasTownPortal = hero->spellbookContainsSpell(SpellID::TOWN_PORTAL); auto manaLimit = hero->manaLimit(); auto spellPower = hero->getPrimSkillLevel(PrimarySkill::SPELL_POWER); - auto hasEarth = hero->getSpellSchoolLevel(SpellID(SpellID::TOWN_PORTAL).toSpell()) > 0; auto score = 0.0f; + // FIXME: this will not cover spells give by scrolls / tomes. Intended? for(auto spellId : hero->getSpellsInSpellbook()) { auto spell = spellId.toSpell(); auto schoolLevel = hero->getSpellSchoolLevel(spell); + auto townPortalEffect = spell->getAdventureMechanics().getEffectAs(hero); score += (spell->getLevel() + 1) * (schoolLevel + 1) * 0.05f; + + if (spell->getAdventureMechanics().givesBonus(hero, BonusType::FLYING_MOVEMENT)) + score += 0.3; + + if(townPortalEffect != nullptr && schoolLevel != 0) + score += 0.6f; } vstd::amin(score, 1); score *= std::min(1.0f, spellPower / 10.0f); - if(hasFly) - score += 0.3f; - - if(hasTownPortal && hasEarth) - score += 0.6f; - vstd::amin(score, 1); score *= std::min(1.0f, manaLimit / 100.0f); diff --git a/AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.cpp b/AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.cpp index cb0cfa102..4c9ce19b8 100644 --- a/AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.cpp +++ b/AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.cpp @@ -28,12 +28,12 @@ namespace AIPathfinding manaCost = hero->getSpellCost(spellToCast.toSpell()); } - WaterWalkingAction::WaterWalkingAction(const CGHeroInstance * hero) - :AdventureCastAction(SpellID::WATER_WALK, hero, DayFlags::WATER_WALK_CAST) + WaterWalkingAction::WaterWalkingAction(const CGHeroInstance * hero, SpellID spellToCast) + :AdventureCastAction(spellToCast, hero, DayFlags::WATER_WALK_CAST) { } - AirWalkingAction::AirWalkingAction(const CGHeroInstance * hero) - : AdventureCastAction(SpellID::FLY, hero, DayFlags::FLY_CAST) + AirWalkingAction::AirWalkingAction(const CGHeroInstance * hero, SpellID spellToCast) + : AdventureCastAction(spellToCast, hero, DayFlags::FLY_CAST) { } diff --git a/AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.h b/AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.h index acbaf28fe..a88324e3e 100644 --- a/AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.h +++ b/AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.h @@ -45,14 +45,16 @@ namespace AIPathfinding class WaterWalkingAction : public AdventureCastAction { + SpellID spellToCast; public: - WaterWalkingAction(const CGHeroInstance * hero); + WaterWalkingAction(const CGHeroInstance * hero, SpellID spellToCast); }; class AirWalkingAction : public AdventureCastAction { + SpellID spellToCast; public: - AirWalkingAction(const CGHeroInstance * hero); + AirWalkingAction(const CGHeroInstance * hero, SpellID spellToCast); }; } diff --git a/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp index ffb780170..e4f37bb0d 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp @@ -111,19 +111,22 @@ namespace AIPathfinding void AILayerTransitionRule::setup() { - SpellID waterWalk = SpellID::WATER_WALK; - SpellID airWalk = SpellID::FLY; - for(const CGHeroInstance * hero : nodeStorage->getAllHeroes()) { - if(hero->canCastThisSpell(waterWalk.toSpell()) && hero->mana >= hero->getSpellCost(waterWalk.toSpell())) + for (const auto & spell : LIBRARY->spellh->objects) { - waterWalkingActions[hero] = std::make_shared(hero); - } + if (!spell || !spell->isAdventure()) + continue; - if(hero->canCastThisSpell(airWalk.toSpell()) && hero->mana >= hero->getSpellCost(airWalk.toSpell())) - { - airWalkingActions[hero] = std::make_shared(hero); + if(spell->getAdventureMechanics().givesBonus(hero, BonusType::WATER_WALKING) && hero->canCastThisSpell(spell.get()) && hero->mana >= hero->getSpellCost(spell.get())) + { + waterWalkingActions[hero] = std::make_shared(hero, spell->id); + } + + if(spell->getAdventureMechanics().givesBonus(hero, BonusType::FLYING_MOVEMENT) && hero->canCastThisSpell(spell.get()) && hero->mana >= hero->getSpellCost(spell.get())) + { + airWalkingActions[hero] = std::make_shared(hero, spell->id); + } } } diff --git a/lib/pathfinder/CPathfinder.cpp b/lib/pathfinder/CPathfinder.cpp index a3c04f2fb..154a6fcec 100644 --- a/lib/pathfinder/CPathfinder.cpp +++ b/lib/pathfinder/CPathfinder.cpp @@ -25,6 +25,7 @@ #include "../mapObjects/MiscObjects.h" #include "../mapping/CMap.h" #include "../spells/CSpellHandler.h" +#include "spells/ISpellMechanics.h" VCMI_LIB_NAMESPACE_BEGIN @@ -502,7 +503,9 @@ CPathfinderHelper::CPathfinderHelper(const IGameInfoCallback & gameInfo, const C turn(-1), owner(Hero->tempOwner), hero(Hero), - options(Options) + options(Options), + canCastFly(false), + canCastWaterWalk(false) { turnsInfo.reserve(16); updateTurnInfo(); @@ -510,11 +513,20 @@ CPathfinderHelper::CPathfinderHelper(const IGameInfoCallback & gameInfo, const C whirlpoolProtection = Hero->hasBonusOfType(BonusType::WHIRLPOOL_PROTECTION); - SpellID flySpell = SpellID::FLY; - canCastFly = Hero->canCastThisSpell(flySpell.toSpell()); + if (options.canUseCast) + { + for (const auto & spell : LIBRARY->spellh->objects) + { + if (!spell || !spell->isAdventure()) + continue; - SpellID waterWalk = SpellID::WATER_WALK; - canCastWaterWalk = Hero->canCastThisSpell(waterWalk.toSpell()); + if(spell->getAdventureMechanics().givesBonus(hero, BonusType::WATER_WALKING) && hero->canCastThisSpell(spell.get()) && hero->mana >= hero->getSpellCost(spell.get())) + canCastWaterWalk = true; + + if(spell->getAdventureMechanics().givesBonus(hero, BonusType::FLYING_MOVEMENT) && hero->canCastThisSpell(spell.get()) && hero->mana >= hero->getSpellCost(spell.get())) + canCastFly = true; + } + } } CPathfinderHelper::~CPathfinderHelper() = default; diff --git a/lib/spells/ISpellMechanics.h b/lib/spells/ISpellMechanics.h index 55ed17b08..3439d6c67 100644 --- a/lib/spells/ISpellMechanics.h +++ b/lib/spells/ISpellMechanics.h @@ -359,6 +359,8 @@ public: static std::unique_ptr createMechanics(const CSpell * s); + virtual bool givesBonus(const spells::Caster * caster, BonusType which) const = 0; + template const EffectType * getEffectAs(const spells::Caster * caster) const { diff --git a/lib/spells/adventure/AdventureSpellMechanics.cpp b/lib/spells/adventure/AdventureSpellMechanics.cpp index ada8dd9ed..e0aca591a 100644 --- a/lib/spells/adventure/AdventureSpellMechanics.cpp +++ b/lib/spells/adventure/AdventureSpellMechanics.cpp @@ -84,6 +84,15 @@ const IAdventureSpellEffect * AdventureSpellMechanics::getEffect(const spells::C return getLevel(caster).effect.get(); } +bool AdventureSpellMechanics::givesBonus(const spells::Caster * caster, BonusType which) const +{ + for (const auto & bonus : getLevel(caster).bonuses) + if (bonus->type == which) + return true; + + return false; +} + bool AdventureSpellMechanics::canBeCast(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const { if(!owner->isAdventure()) diff --git a/lib/spells/adventure/AdventureSpellMechanics.h b/lib/spells/adventure/AdventureSpellMechanics.h index cad20504f..b6ea56515 100644 --- a/lib/spells/adventure/AdventureSpellMechanics.h +++ b/lib/spells/adventure/AdventureSpellMechanics.h @@ -39,6 +39,7 @@ public: bool canBeCastAt(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const final; bool adventureCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const final; const IAdventureSpellEffect * getEffect(const spells::Caster * caster) const final; + bool givesBonus(const spells::Caster * caster, BonusType which) const final; void performCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const; };