diff --git a/AI/Nullkiller/Goals/AdventureSpellCast.cpp b/AI/Nullkiller/Goals/AdventureSpellCast.cpp index 8c7c0294f..35b632058 100644 --- a/AI/Nullkiller/Goals/AdventureSpellCast.cpp +++ b/AI/Nullkiller/Goals/AdventureSpellCast.cpp @@ -10,6 +10,8 @@ #include "StdInc.h" #include "AdventureSpellCast.h" #include "../AIGateway.h" +#include "../../../lib/spells/ISpellMechanics.h" +#include "../../../lib/spells/adventure/TownPortalEffect.h" namespace NKAI { @@ -39,8 +41,9 @@ void AdventureSpellCast::accept(AIGateway * ai) if(hero->mana < hero->getSpellCost(spell)) throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->getNameTranslated()); + auto townPortalEffect = spell->getAdventureMechanics().getEffectAs(hero); - if(town && spellID == SpellID::TOWN_PORTAL) + if(town && townPortalEffect) { ai->selectedObject = town->id; @@ -61,7 +64,7 @@ void AdventureSpellCast::accept(AIGateway * ai) cb->waitTillRealize = true; cb->castSpell(hero, spellID, tile); - if(town && spellID == SpellID::TOWN_PORTAL) + if(town && townPortalEffect) { // visit town ai->moveHeroToTile(town->visitablePos(), hero); diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index 95560d259..71f6950d8 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -17,6 +17,8 @@ #include "../../../lib/pathfinder/CPathfinder.h" #include "../../../lib/pathfinder/PathfinderUtil.h" #include "../../../lib/pathfinder/PathfinderOptions.h" +#include "../../../lib/spells/ISpellMechanics.h" +#include "../../../lib/spells/adventure/TownPortalEffect.h" #include "../../../lib/IGameSettings.h" #include "../../../lib/CPlayerState.h" @@ -1069,22 +1071,17 @@ struct TownPortalFinder SpellID spellID; const CSpell * townPortal; - TownPortalFinder( - const ChainActor * actor, - const std::vector & initialNodes, - std::vector targetTowns, - AINodeStorage * nodeStorage) - :actor(actor), initialNodes(initialNodes), hero(actor->hero), - targetTowns(targetTowns), nodeStorage(nodeStorage) + TownPortalFinder(const ChainActor * actor, const std::vector & initialNodes, std::vector targetTowns, AINodeStorage * nodeStorage, SpellID spellID) + : actor(actor) + , initialNodes(initialNodes) + , hero(actor->hero) + , targetTowns(targetTowns) + , nodeStorage(nodeStorage) + , spellID(spellID) + , townPortal(spellID.toSpell()) { - spellID = SpellID::TOWN_PORTAL; - townPortal = spellID.toSpell(); - - // TODO: Copy/Paste from TownPortalMechanics - townPortalSkillLevel = MasteryLevel::Type(hero->getSpellSchoolLevel(townPortal)); - - int baseCost = hero->cb->getSettings().getInteger(EGameSettings::HEROES_MOVEMENT_COST_BASE); - movementNeeded = baseCost * (townPortalSkillLevel >= MasteryLevel::EXPERT ? 2 : 3); + auto townPortalEffect = townPortal->getAdventureMechanics().getEffectAs(hero); + movementNeeded = townPortalEffect->getMovementPointsRequired(); } bool actorCanCastTownPortal() @@ -1151,7 +1148,7 @@ struct TownPortalFinder DO_NOT_SAVE_TO_COMMITTED_TILES); node->theNodeBefore = bestNode; - node->addSpecialAction(std::make_shared(targetTown)); + node->addSpecialAction(std::make_shared(targetTown, spellID)); } return nodeOptional; @@ -1177,10 +1174,21 @@ void AINodeStorage::calculateTownPortal( return; // no towns no need to run loop further } - TownPortalFinder townPortalFinder(actor, initialNodes, towns, this); - - if(townPortalFinder.actorCanCastTownPortal()) + for (const auto & spell : LIBRARY->spellh->objects) { + if (!spell || !spell->isAdventure()) + continue; + + auto townPortalEffect = spell->getAdventureMechanics().getEffectAs(actor->hero); + + if (!townPortalEffect) + continue; + + TownPortalFinder townPortalFinder(actor, initialNodes, towns, this, spell->id); + + if(!townPortalFinder.actorCanCastTownPortal()) + continue; + for(const CGTownInstance * targetTown : towns) { if(targetTown->getVisitingHero() diff --git a/AI/Nullkiller/Pathfinding/Actions/TownPortalAction.cpp b/AI/Nullkiller/Pathfinding/Actions/TownPortalAction.cpp index f92a3a0b2..22434c547 100644 --- a/AI/Nullkiller/Pathfinding/Actions/TownPortalAction.cpp +++ b/AI/Nullkiller/Pathfinding/Actions/TownPortalAction.cpp @@ -20,7 +20,7 @@ using namespace AIPathfinding; void TownPortalAction::execute(AIGateway * ai, const CGHeroInstance * hero) const { - auto goal = Goals::AdventureSpellCast(hero, SpellID::TOWN_PORTAL); + auto goal = Goals::AdventureSpellCast(hero, usedSpell); goal.town = target; goal.tile = target->visitablePos(); diff --git a/AI/Nullkiller/Pathfinding/Actions/TownPortalAction.h b/AI/Nullkiller/Pathfinding/Actions/TownPortalAction.h index 34c6c3c10..aa11e5930 100644 --- a/AI/Nullkiller/Pathfinding/Actions/TownPortalAction.h +++ b/AI/Nullkiller/Pathfinding/Actions/TownPortalAction.h @@ -22,10 +22,12 @@ namespace AIPathfinding { private: const CGTownInstance * target; + SpellID usedSpell; public: - TownPortalAction(const CGTownInstance * target) + TownPortalAction(const CGTownInstance * target, SpellID usedSpell) :target(target) + ,usedSpell(usedSpell) { } diff --git a/AI/VCAI/Goals/AdventureSpellCast.cpp b/AI/VCAI/Goals/AdventureSpellCast.cpp index 7365e0309..147376cbb 100644 --- a/AI/VCAI/Goals/AdventureSpellCast.cpp +++ b/AI/VCAI/Goals/AdventureSpellCast.cpp @@ -13,6 +13,8 @@ #include "../FuzzyHelper.h" #include "../AIhelper.h" #include "../../../lib/mapObjects/CGTownInstance.h" +#include "../../../lib/spells/ISpellMechanics.h" +#include "../../../lib/spells/adventure/TownPortalEffect.h" using namespace Goals; @@ -39,7 +41,9 @@ TSubgoal AdventureSpellCast::whatToDoToAchieve() if(hero->mana < hero->getSpellCost(spell)) throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->getNameTranslated()); - if(spellID == SpellID::TOWN_PORTAL && town && town->getVisitingHero()) + auto townPortalEffect = spell->getAdventureMechanics().getEffectAs(hero.h); + + if(townPortalEffect && town && town->getVisitingHero()) throw cannotFulfillGoalException("The town is already occupied by " + town->getVisitingHero()->getNameTranslated()); return iAmElementar(); @@ -47,7 +51,9 @@ TSubgoal AdventureSpellCast::whatToDoToAchieve() void AdventureSpellCast::accept(VCAI * ai) { - if(town && spellID == SpellID::TOWN_PORTAL) + auto townPortalEffect = spellID.toSpell()->getAdventureMechanics().getEffectAs(hero.h); + + if(town && townPortalEffect) { ai->selectedObject = town->id; } @@ -57,7 +63,7 @@ void AdventureSpellCast::accept(VCAI * ai) cb->waitTillRealize = true; cb->castSpell(hero.h, spellID, tile); - if(town && spellID == SpellID::TOWN_PORTAL) + if(town && townPortalEffect) { // visit town ai->moveHeroToTile(town->visitablePos(), hero); diff --git a/AI/VCAI/Pathfinding/AINodeStorage.cpp b/AI/VCAI/Pathfinding/AINodeStorage.cpp index 93987cd00..ba7fe1ca1 100644 --- a/AI/VCAI/Pathfinding/AINodeStorage.cpp +++ b/AI/VCAI/Pathfinding/AINodeStorage.cpp @@ -15,6 +15,8 @@ #include "../../../lib/pathfinder/CPathfinder.h" #include "../../../lib/pathfinder/PathfinderOptions.h" #include "../../../lib/pathfinder/PathfinderUtil.h" +#include "../../../lib/spells/ISpellMechanics.h" +#include "../../../lib/spells/adventure/TownPortalEffect.h" #include "../../../lib/IGameSettings.h" #include "../../../lib/CPlayerState.h" @@ -225,12 +227,21 @@ void AINodeStorage::calculateTownPortalTeleportations( const PathNodeInfo & source, std::vector & neighbours) { - SpellID spellID = SpellID::TOWN_PORTAL; - const CSpell * townPortal = spellID.toSpell(); auto srcNode = getAINode(source.node); - if(hero->canCastThisSpell(townPortal) && hero->mana >= hero->getSpellCost(townPortal)) + for (const auto & spell : LIBRARY->spellh->objects) { + if (!spell || !spell->isAdventure()) + continue; + + auto townPortalEffect = spell->getAdventureMechanics().getEffectAs(hero); + + if (!townPortalEffect) + continue; + + if(!hero->canCastThisSpell(spell.get()) || hero->mana < hero->getSpellCost(spell.get())) + continue; + auto towns = cb->getTownsInfo(false); vstd::erase_if(towns, [&](const CGTownInstance * t) -> bool @@ -238,22 +249,15 @@ void AINodeStorage::calculateTownPortalTeleportations( return cb->getPlayerRelations(hero->tempOwner, t->tempOwner) == PlayerRelations::ENEMIES; }); - if(!towns.size()) + if(towns.empty()) + return; + + if(hero->movementPointsRemaining() < townPortalEffect->getMovementPointsRequired()) { return; } - // TODO: Copy/Paste from TownPortalMechanics - auto skillLevel = hero->getSpellSchoolLevel(townPortal); - int baseCost = hero->cb->getSettings().getInteger(EGameSettings::HEROES_MOVEMENT_COST_BASE); - auto movementCost = baseCost * (skillLevel >= 3 ? 2 : 3); - - if(hero->movementPointsRemaining() < movementCost) - { - return; - } - - if(skillLevel < MasteryLevel::ADVANCED) + if(!townPortalEffect->townSelectionAllowed()) { const CGTownInstance * nearestTown = *vstd::minElementByFun(towns, [&](const CGTownInstance * t) -> int { @@ -279,7 +283,7 @@ void AINodeStorage::calculateTownPortalTeleportations( AIPathNode * node = nodeOptional.value(); node->theNodeBefore = source.node; - node->specialAction.reset(new AIPathfinding::TownPortalAction(targetTown)); + node->specialAction.reset(new AIPathfinding::TownPortalAction(targetTown, spell->id)); node->moveRemains = source.node->moveRemains; neighbours.push_back(node); diff --git a/AI/VCAI/Pathfinding/Actions/TownPortalAction.cpp b/AI/VCAI/Pathfinding/Actions/TownPortalAction.cpp index 6063f12c5..5c58ffb3a 100644 --- a/AI/VCAI/Pathfinding/Actions/TownPortalAction.cpp +++ b/AI/VCAI/Pathfinding/Actions/TownPortalAction.cpp @@ -19,5 +19,5 @@ Goals::TSubgoal TownPortalAction::whatToDo(const HeroPtr & hero) const { const CGTownInstance * targetTown = target; // const pointer is not allowed in settown - return Goals::sptr(Goals::AdventureSpellCast(hero, SpellID::TOWN_PORTAL).settown(targetTown).settile(targetTown->visitablePos())); + return Goals::sptr(Goals::AdventureSpellCast(hero, spellToUse).settown(targetTown).settile(targetTown->visitablePos())); } diff --git a/AI/VCAI/Pathfinding/Actions/TownPortalAction.h b/AI/VCAI/Pathfinding/Actions/TownPortalAction.h index eba21d392..0fe3bfcb9 100644 --- a/AI/VCAI/Pathfinding/Actions/TownPortalAction.h +++ b/AI/VCAI/Pathfinding/Actions/TownPortalAction.h @@ -20,10 +20,12 @@ namespace AIPathfinding { private: const CGTownInstance * target; + SpellID spellToUse; public: - TownPortalAction(const CGTownInstance * target) + TownPortalAction(const CGTownInstance * target, SpellID spellToUse) :target(target) + ,spellToUse(spellToUse) { } diff --git a/lib/constants/EntityIdentifiers.h b/lib/constants/EntityIdentifiers.h index def1e1a43..6b6d5832e 100644 --- a/lib/constants/EntityIdentifiers.h +++ b/lib/constants/EntityIdentifiers.h @@ -856,16 +856,16 @@ public: NONE = -1, // Adventure map spells - SUMMON_BOAT = 0, - SCUTTLE_BOAT = 1, - VISIONS = 2, - VIEW_EARTH = 3, - DISGUISE = 4, - VIEW_AIR = 5, - FLY = 6, - WATER_WALK = 7, - DIMENSION_DOOR = 8, - TOWN_PORTAL = 9, + SUMMON_BOAT [[deprecated]] = 0, + SCUTTLE_BOAT [[deprecated]] = 1, + VISIONS [[deprecated]] = 2, + VIEW_EARTH [[deprecated]] = 3, + DISGUISE [[deprecated]] = 4, + VIEW_AIR [[deprecated]] = 5, + FLY [[deprecated]] = 6, + WATER_WALK [[deprecated]] = 7, + DIMENSION_DOOR [[deprecated]] = 8, + TOWN_PORTAL [[deprecated]] = 9, // Combat spells QUICKSAND = 10, diff --git a/lib/spells/adventure/TownPortalEffect.h b/lib/spells/adventure/TownPortalEffect.h index 54c40c3b7..ec6c3b7ec 100644 --- a/lib/spells/adventure/TownPortalEffect.h +++ b/lib/spells/adventure/TownPortalEffect.h @@ -27,6 +27,9 @@ class TownPortalEffect final : public IAdventureSpellEffect public: TownPortalEffect(const CSpell * s, const JsonNode & config); + bool getMovementPointsRequired() const { return movementPointsRequired; } + bool townSelectionAllowed() const { return allowTownSelection; } + protected: ESpellCastResult applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override; ESpellCastResult beginCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters, const AdventureSpellMechanics & mechanics) const override;