1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-06 09:09:40 +02:00

Remove hardcoded checks for town portal from AI code

This commit is contained in:
Ivan Savenko
2025-07-11 18:03:18 +03:00
parent f51c7c5c28
commit b1aff17e82
10 changed files with 82 additions and 54 deletions

View File

@@ -10,6 +10,8 @@
#include "StdInc.h" #include "StdInc.h"
#include "AdventureSpellCast.h" #include "AdventureSpellCast.h"
#include "../AIGateway.h" #include "../AIGateway.h"
#include "../../../lib/spells/ISpellMechanics.h"
#include "../../../lib/spells/adventure/TownPortalEffect.h"
namespace NKAI namespace NKAI
{ {
@@ -39,8 +41,9 @@ void AdventureSpellCast::accept(AIGateway * ai)
if(hero->mana < hero->getSpellCost(spell)) if(hero->mana < hero->getSpellCost(spell))
throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->getNameTranslated()); throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->getNameTranslated());
auto townPortalEffect = spell->getAdventureMechanics().getEffectAs<TownPortalEffect>(hero);
if(town && spellID == SpellID::TOWN_PORTAL) if(town && townPortalEffect)
{ {
ai->selectedObject = town->id; ai->selectedObject = town->id;
@@ -61,7 +64,7 @@ void AdventureSpellCast::accept(AIGateway * ai)
cb->waitTillRealize = true; cb->waitTillRealize = true;
cb->castSpell(hero, spellID, tile); cb->castSpell(hero, spellID, tile);
if(town && spellID == SpellID::TOWN_PORTAL) if(town && townPortalEffect)
{ {
// visit town // visit town
ai->moveHeroToTile(town->visitablePos(), hero); ai->moveHeroToTile(town->visitablePos(), hero);

View File

@@ -17,6 +17,8 @@
#include "../../../lib/pathfinder/CPathfinder.h" #include "../../../lib/pathfinder/CPathfinder.h"
#include "../../../lib/pathfinder/PathfinderUtil.h" #include "../../../lib/pathfinder/PathfinderUtil.h"
#include "../../../lib/pathfinder/PathfinderOptions.h" #include "../../../lib/pathfinder/PathfinderOptions.h"
#include "../../../lib/spells/ISpellMechanics.h"
#include "../../../lib/spells/adventure/TownPortalEffect.h"
#include "../../../lib/IGameSettings.h" #include "../../../lib/IGameSettings.h"
#include "../../../lib/CPlayerState.h" #include "../../../lib/CPlayerState.h"
@@ -1069,22 +1071,17 @@ struct TownPortalFinder
SpellID spellID; SpellID spellID;
const CSpell * townPortal; const CSpell * townPortal;
TownPortalFinder( TownPortalFinder(const ChainActor * actor, const std::vector<CGPathNode *> & initialNodes, std::vector<const CGTownInstance *> targetTowns, AINodeStorage * nodeStorage, SpellID spellID)
const ChainActor * actor, : actor(actor)
const std::vector<CGPathNode *> & initialNodes, , initialNodes(initialNodes)
std::vector<const CGTownInstance *> targetTowns, , hero(actor->hero)
AINodeStorage * nodeStorage) , targetTowns(targetTowns)
:actor(actor), initialNodes(initialNodes), hero(actor->hero), , nodeStorage(nodeStorage)
targetTowns(targetTowns), nodeStorage(nodeStorage) , spellID(spellID)
, townPortal(spellID.toSpell())
{ {
spellID = SpellID::TOWN_PORTAL; auto townPortalEffect = townPortal->getAdventureMechanics().getEffectAs<TownPortalEffect>(hero);
townPortal = spellID.toSpell(); movementNeeded = townPortalEffect->getMovementPointsRequired();
// 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);
} }
bool actorCanCastTownPortal() bool actorCanCastTownPortal()
@@ -1151,7 +1148,7 @@ struct TownPortalFinder
DO_NOT_SAVE_TO_COMMITTED_TILES); DO_NOT_SAVE_TO_COMMITTED_TILES);
node->theNodeBefore = bestNode; node->theNodeBefore = bestNode;
node->addSpecialAction(std::make_shared<AIPathfinding::TownPortalAction>(targetTown)); node->addSpecialAction(std::make_shared<AIPathfinding::TownPortalAction>(targetTown, spellID));
} }
return nodeOptional; return nodeOptional;
@@ -1177,10 +1174,21 @@ void AINodeStorage::calculateTownPortal(
return; // no towns no need to run loop further return; // no towns no need to run loop further
} }
TownPortalFinder townPortalFinder(actor, initialNodes, towns, this); for (const auto & spell : LIBRARY->spellh->objects)
if(townPortalFinder.actorCanCastTownPortal())
{ {
if (!spell || !spell->isAdventure())
continue;
auto townPortalEffect = spell->getAdventureMechanics().getEffectAs<TownPortalEffect>(actor->hero);
if (!townPortalEffect)
continue;
TownPortalFinder townPortalFinder(actor, initialNodes, towns, this, spell->id);
if(!townPortalFinder.actorCanCastTownPortal())
continue;
for(const CGTownInstance * targetTown : towns) for(const CGTownInstance * targetTown : towns)
{ {
if(targetTown->getVisitingHero() if(targetTown->getVisitingHero()

View File

@@ -20,7 +20,7 @@ using namespace AIPathfinding;
void TownPortalAction::execute(AIGateway * ai, const CGHeroInstance * hero) const 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.town = target;
goal.tile = target->visitablePos(); goal.tile = target->visitablePos();

View File

@@ -22,10 +22,12 @@ namespace AIPathfinding
{ {
private: private:
const CGTownInstance * target; const CGTownInstance * target;
SpellID usedSpell;
public: public:
TownPortalAction(const CGTownInstance * target) TownPortalAction(const CGTownInstance * target, SpellID usedSpell)
:target(target) :target(target)
,usedSpell(usedSpell)
{ {
} }

View File

@@ -13,6 +13,8 @@
#include "../FuzzyHelper.h" #include "../FuzzyHelper.h"
#include "../AIhelper.h" #include "../AIhelper.h"
#include "../../../lib/mapObjects/CGTownInstance.h" #include "../../../lib/mapObjects/CGTownInstance.h"
#include "../../../lib/spells/ISpellMechanics.h"
#include "../../../lib/spells/adventure/TownPortalEffect.h"
using namespace Goals; using namespace Goals;
@@ -39,7 +41,9 @@ TSubgoal AdventureSpellCast::whatToDoToAchieve()
if(hero->mana < hero->getSpellCost(spell)) if(hero->mana < hero->getSpellCost(spell))
throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->getNameTranslated()); throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->getNameTranslated());
if(spellID == SpellID::TOWN_PORTAL && town && town->getVisitingHero()) auto townPortalEffect = spell->getAdventureMechanics().getEffectAs<TownPortalEffect>(hero.h);
if(townPortalEffect && town && town->getVisitingHero())
throw cannotFulfillGoalException("The town is already occupied by " + town->getVisitingHero()->getNameTranslated()); throw cannotFulfillGoalException("The town is already occupied by " + town->getVisitingHero()->getNameTranslated());
return iAmElementar(); return iAmElementar();
@@ -47,7 +51,9 @@ TSubgoal AdventureSpellCast::whatToDoToAchieve()
void AdventureSpellCast::accept(VCAI * ai) void AdventureSpellCast::accept(VCAI * ai)
{ {
if(town && spellID == SpellID::TOWN_PORTAL) auto townPortalEffect = spellID.toSpell()->getAdventureMechanics().getEffectAs<TownPortalEffect>(hero.h);
if(town && townPortalEffect)
{ {
ai->selectedObject = town->id; ai->selectedObject = town->id;
} }
@@ -57,7 +63,7 @@ void AdventureSpellCast::accept(VCAI * ai)
cb->waitTillRealize = true; cb->waitTillRealize = true;
cb->castSpell(hero.h, spellID, tile); cb->castSpell(hero.h, spellID, tile);
if(town && spellID == SpellID::TOWN_PORTAL) if(town && townPortalEffect)
{ {
// visit town // visit town
ai->moveHeroToTile(town->visitablePos(), hero); ai->moveHeroToTile(town->visitablePos(), hero);

View File

@@ -15,6 +15,8 @@
#include "../../../lib/pathfinder/CPathfinder.h" #include "../../../lib/pathfinder/CPathfinder.h"
#include "../../../lib/pathfinder/PathfinderOptions.h" #include "../../../lib/pathfinder/PathfinderOptions.h"
#include "../../../lib/pathfinder/PathfinderUtil.h" #include "../../../lib/pathfinder/PathfinderUtil.h"
#include "../../../lib/spells/ISpellMechanics.h"
#include "../../../lib/spells/adventure/TownPortalEffect.h"
#include "../../../lib/IGameSettings.h" #include "../../../lib/IGameSettings.h"
#include "../../../lib/CPlayerState.h" #include "../../../lib/CPlayerState.h"
@@ -225,12 +227,21 @@ void AINodeStorage::calculateTownPortalTeleportations(
const PathNodeInfo & source, const PathNodeInfo & source,
std::vector<CGPathNode *> & neighbours) std::vector<CGPathNode *> & neighbours)
{ {
SpellID spellID = SpellID::TOWN_PORTAL;
const CSpell * townPortal = spellID.toSpell();
auto srcNode = getAINode(source.node); 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<TownPortalEffect>(hero);
if (!townPortalEffect)
continue;
if(!hero->canCastThisSpell(spell.get()) || hero->mana < hero->getSpellCost(spell.get()))
continue;
auto towns = cb->getTownsInfo(false); auto towns = cb->getTownsInfo(false);
vstd::erase_if(towns, [&](const CGTownInstance * t) -> bool vstd::erase_if(towns, [&](const CGTownInstance * t) -> bool
@@ -238,22 +249,15 @@ void AINodeStorage::calculateTownPortalTeleportations(
return cb->getPlayerRelations(hero->tempOwner, t->tempOwner) == PlayerRelations::ENEMIES; return cb->getPlayerRelations(hero->tempOwner, t->tempOwner) == PlayerRelations::ENEMIES;
}); });
if(!towns.size()) if(towns.empty())
return;
if(hero->movementPointsRemaining() < townPortalEffect->getMovementPointsRequired())
{ {
return; return;
} }
// TODO: Copy/Paste from TownPortalMechanics if(!townPortalEffect->townSelectionAllowed())
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)
{ {
const CGTownInstance * nearestTown = *vstd::minElementByFun(towns, [&](const CGTownInstance * t) -> int const CGTownInstance * nearestTown = *vstd::minElementByFun(towns, [&](const CGTownInstance * t) -> int
{ {
@@ -279,7 +283,7 @@ void AINodeStorage::calculateTownPortalTeleportations(
AIPathNode * node = nodeOptional.value(); AIPathNode * node = nodeOptional.value();
node->theNodeBefore = source.node; 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; node->moveRemains = source.node->moveRemains;
neighbours.push_back(node); neighbours.push_back(node);

View File

@@ -19,5 +19,5 @@ Goals::TSubgoal TownPortalAction::whatToDo(const HeroPtr & hero) const
{ {
const CGTownInstance * targetTown = target; // const pointer is not allowed in settown 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()));
} }

View File

@@ -20,10 +20,12 @@ namespace AIPathfinding
{ {
private: private:
const CGTownInstance * target; const CGTownInstance * target;
SpellID spellToUse;
public: public:
TownPortalAction(const CGTownInstance * target) TownPortalAction(const CGTownInstance * target, SpellID spellToUse)
:target(target) :target(target)
,spellToUse(spellToUse)
{ {
} }

View File

@@ -856,16 +856,16 @@ public:
NONE = -1, NONE = -1,
// Adventure map spells // Adventure map spells
SUMMON_BOAT = 0, SUMMON_BOAT [[deprecated]] = 0,
SCUTTLE_BOAT = 1, SCUTTLE_BOAT [[deprecated]] = 1,
VISIONS = 2, VISIONS [[deprecated]] = 2,
VIEW_EARTH = 3, VIEW_EARTH [[deprecated]] = 3,
DISGUISE = 4, DISGUISE [[deprecated]] = 4,
VIEW_AIR = 5, VIEW_AIR [[deprecated]] = 5,
FLY = 6, FLY [[deprecated]] = 6,
WATER_WALK = 7, WATER_WALK [[deprecated]] = 7,
DIMENSION_DOOR = 8, DIMENSION_DOOR [[deprecated]] = 8,
TOWN_PORTAL = 9, TOWN_PORTAL [[deprecated]] = 9,
// Combat spells // Combat spells
QUICKSAND = 10, QUICKSAND = 10,

View File

@@ -27,6 +27,9 @@ class TownPortalEffect final : public IAdventureSpellEffect
public: public:
TownPortalEffect(const CSpell * s, const JsonNode & config); TownPortalEffect(const CSpell * s, const JsonNode & config);
bool getMovementPointsRequired() const { return movementPointsRequired; }
bool townSelectionAllowed() const { return allowTownSelection; }
protected: protected:
ESpellCastResult applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override; ESpellCastResult applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
ESpellCastResult beginCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters, const AdventureSpellMechanics & mechanics) const override; ESpellCastResult beginCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters, const AdventureSpellMechanics & mechanics) const override;