瀏覽代碼

Remove hardcoded checks for town portal from AI code

Ivan Savenko 3 月之前
父節點
當前提交
b1aff17e82

+ 5 - 2
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<TownPortalEffect>(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);

+ 27 - 19
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<CGPathNode *> & initialNodes,
-		std::vector<const CGTownInstance *> targetTowns,
-		AINodeStorage * nodeStorage)
-		:actor(actor), initialNodes(initialNodes), hero(actor->hero),
-		targetTowns(targetTowns), nodeStorage(nodeStorage)
+	TownPortalFinder(const ChainActor * actor, const std::vector<CGPathNode *> & initialNodes, std::vector<const CGTownInstance *> 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<TownPortalEffect>(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<AIPathfinding::TownPortalAction>(targetTown));
+			node->addSpecialAction(std::make_shared<AIPathfinding::TownPortalAction>(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<TownPortalEffect>(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()

+ 1 - 1
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();

+ 3 - 1
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)
 		{
 		}
 

+ 9 - 3
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<TownPortalEffect>(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<TownPortalEffect>(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);

+ 18 - 14
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<CGPathNode *> & 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<TownPortalEffect>(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;
-		}
-
-		// 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)
+		if(hero->movementPointsRemaining() < townPortalEffect->getMovementPointsRequired())
 		{
 			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);

+ 1 - 1
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()));
 }

+ 3 - 1
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)
 		{
 		}
 

+ 10 - 10
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,

+ 3 - 0
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;