mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Merge remote-tracking branch 'upstream/develop' into develop
This commit is contained in:
		| @@ -1311,6 +1311,11 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h) | ||||
|  | ||||
| 		auto doTeleportMovement = [&](ObjectInstanceID exitId, int3 exitPos) | ||||
| 		{ | ||||
| 			if(cb->getObj(exitId) && cb->getObj(exitId)->ID == Obj::WHIRLPOOL) | ||||
| 			{ | ||||
| 				nullkiller->armyFormation->rearrangeArmyForWhirlpool(*h); | ||||
| 			} | ||||
|  | ||||
| 			destinationTeleport = exitId; | ||||
| 			if(exitPos.valid()) | ||||
| 				destinationTeleportPos = exitPos; | ||||
| @@ -1332,6 +1337,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h) | ||||
| 				status.setChannelProbing(true); | ||||
| 				for(auto exit : teleportChannelProbingList) | ||||
| 					doTeleportMovement(exit, int3(-1)); | ||||
|  | ||||
| 				teleportChannelProbingList.clear(); | ||||
| 				status.setChannelProbing(false); | ||||
|  | ||||
|   | ||||
| @@ -48,9 +48,12 @@ Goals::TGoalVec ExplorationBehavior::decompose(const Nullkiller * ai) const | ||||
| 		case Obj::MONOLITH_ONE_WAY_ENTRANCE: | ||||
| 		case Obj::MONOLITH_TWO_WAY: | ||||
| 		case Obj::SUBTERRANEAN_GATE: | ||||
| 		case Obj::WHIRLPOOL: | ||||
| 			{ | ||||
| 				auto tObj = dynamic_cast<const CGTeleport *>(obj); | ||||
| 				for (auto exit : cb->getTeleportChannelExits(tObj->channel)) | ||||
| 				if(TeleportChannel::IMPASSABLE == ai->memory->knownTeleportChannels[tObj->channel]->passability) | ||||
| 					break; | ||||
| 				for(auto exit : ai->memory->knownTeleportChannels[tObj->channel]->exits) | ||||
| 				{ | ||||
| 					if (exit != tObj->id) | ||||
| 					{ | ||||
|   | ||||
| @@ -8,6 +8,7 @@ set(Nullkiller_SRCS | ||||
| 		Pathfinding/Actions/QuestAction.cpp | ||||
| 		Pathfinding/Actions/BuyArmyAction.cpp | ||||
| 		Pathfinding/Actions/BoatActions.cpp | ||||
| 		Pathfinding/Actions/WhirlpoolAction.cpp | ||||
| 		Pathfinding/Actions/TownPortalAction.cpp | ||||
| 		Pathfinding/Actions/AdventureSpellCastMovementActions.cpp | ||||
| 		Pathfinding/Rules/AILayerTransitionRule.cpp | ||||
| @@ -79,6 +80,7 @@ set(Nullkiller_HEADERS | ||||
| 		Pathfinding/Actions/QuestAction.h | ||||
| 		Pathfinding/Actions/BuyArmyAction.h | ||||
| 		Pathfinding/Actions/BoatActions.h | ||||
| 		Pathfinding/Actions/WhirlpoolAction.h | ||||
| 		Pathfinding/Actions/TownPortalAction.h | ||||
| 		Pathfinding/Actions/AdventureSpellCastMovementActions.h | ||||
| 		Pathfinding/Rules/AILayerTransitionRule.h | ||||
|   | ||||
| @@ -196,6 +196,26 @@ void ExecuteHeroChain::accept(AIGateway * ai) | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				auto findWhirlpool = [&ai](const int3 & pos) -> ObjectInstanceID | ||||
| 				{ | ||||
| 					auto objs = ai->myCb->getVisitableObjs(pos); | ||||
| 					auto whirlpool = std::find_if(objs.begin(), objs.end(), [](const CGObjectInstance * o)->bool | ||||
| 						{ | ||||
| 							return o->ID == Obj::WHIRLPOOL; | ||||
| 						}); | ||||
|  | ||||
| 					return whirlpool != objs.end() ? dynamic_cast<const CGWhirlpool *>(*whirlpool)->id : ObjectInstanceID(-1); | ||||
| 				}; | ||||
|  | ||||
| 				auto sourceWhirlpool = findWhirlpool(hero->visitablePos()); | ||||
| 				auto targetWhirlpool = findWhirlpool(node->coord); | ||||
| 				 | ||||
| 				if(i != chainPath.nodes.size() - 1 && sourceWhirlpool.hasValue() && sourceWhirlpool == targetWhirlpool) | ||||
| 				{ | ||||
| 					logAi->trace("AI exited whirlpool at %s but expected at %s", hero->visitablePos().toString(), node->coord.toString()); | ||||
| 					continue; | ||||
| 				} | ||||
|  | ||||
| 				if(hero->movementPointsRemaining()) | ||||
| 				{ | ||||
| 					try | ||||
|   | ||||
| @@ -14,27 +14,37 @@ | ||||
| namespace NKAI | ||||
| { | ||||
|  | ||||
| void ArmyFormation::rearrangeArmyForSiege(const CGTownInstance * town, const CGHeroInstance * attacker) | ||||
| void ArmyFormation::rearrangeArmyForWhirlpool(const CGHeroInstance * hero) | ||||
| { | ||||
| 	auto freeSlots = attacker->getFreeSlotsQueue(); | ||||
| 	addSingleCreatureStacks(hero); | ||||
| } | ||||
|  | ||||
| void ArmyFormation::addSingleCreatureStacks(const CGHeroInstance * hero) | ||||
| { | ||||
| 	auto freeSlots = hero->getFreeSlotsQueue(); | ||||
|  | ||||
| 	while(!freeSlots.empty()) | ||||
| 	{ | ||||
| 		auto weakestCreature = vstd::minElementByFun(attacker->Slots(), [](const std::pair<SlotID, CStackInstance *> & slot) -> int | ||||
| 		auto weakestCreature = vstd::minElementByFun(hero->Slots(), [](const std::pair<SlotID, CStackInstance *> & slot) -> int | ||||
| 			{ | ||||
| 				return slot.second->getCount() == 1 | ||||
| 					? std::numeric_limits<int>::max() | ||||
| 					: slot.second->getCreatureID().toCreature()->getAIValue(); | ||||
| 			}); | ||||
|  | ||||
| 		if(weakestCreature == attacker->Slots().end() || weakestCreature->second->getCount() == 1) | ||||
| 		if(weakestCreature == hero->Slots().end() || weakestCreature->second->getCount() == 1) | ||||
| 		{ | ||||
| 			break; | ||||
| 		} | ||||
|  | ||||
| 		cb->splitStack(attacker, attacker, weakestCreature->first, freeSlots.front(), 1); | ||||
| 		cb->splitStack(hero, hero, weakestCreature->first, freeSlots.front(), 1); | ||||
| 		freeSlots.pop(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void ArmyFormation::rearrangeArmyForSiege(const CGTownInstance * town, const CGHeroInstance * attacker) | ||||
| { | ||||
| 	addSingleCreatureStacks(attacker); | ||||
|  | ||||
| 	if(town->fortLevel() > CGTownInstance::FORT) | ||||
| 	{ | ||||
|   | ||||
| @@ -31,6 +31,10 @@ public: | ||||
| 	ArmyFormation(std::shared_ptr<CCallback> CB, const Nullkiller * ai): cb(CB) {} | ||||
|  | ||||
| 	void rearrangeArmyForSiege(const CGTownInstance * town, const CGHeroInstance * attacker); | ||||
|  | ||||
| 	void rearrangeArmyForWhirlpool(const CGHeroInstance * hero); | ||||
|  | ||||
| 	void addSingleCreatureStacks(const CGHeroInstance * hero); | ||||
| }; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -10,6 +10,7 @@ | ||||
| #include "StdInc.h" | ||||
| #include "AINodeStorage.h" | ||||
| #include "Actions/TownPortalAction.h" | ||||
| #include "Actions/WhirlpoolAction.h" | ||||
| #include "../Goals/Goals.h" | ||||
| #include "../AIGateway.h" | ||||
| #include "../Engine/Nullkiller.h" | ||||
| @@ -255,10 +256,45 @@ void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInf | ||||
| 	{ | ||||
| 		commit(dstNode, srcNode, destination.action, destination.turn, destination.movementLeft, destination.cost); | ||||
|  | ||||
| 		if(srcNode->specialAction || srcNode->chainOther) | ||||
| 		// regular pathfinder can not go directly through whirlpool | ||||
| 		bool isWhirlpoolTeleport = destination.nodeObject | ||||
| 			&& destination.nodeObject->ID == Obj::WHIRLPOOL; | ||||
|  | ||||
| 		if(srcNode->specialAction | ||||
| 			|| srcNode->chainOther | ||||
| 			|| isWhirlpoolTeleport) | ||||
| 		{ | ||||
| 			// there is some action on source tile which should be performed before we can bypass it | ||||
| 			destination.node->theNodeBefore = source.node; | ||||
| 			dstNode->theNodeBefore = source.node; | ||||
|  | ||||
| 			if(isWhirlpoolTeleport) | ||||
| 			{ | ||||
| 				if(dstNode->actor->creatureSet->Slots().size() == 1 | ||||
| 					&& dstNode->actor->creatureSet->Slots().begin()->second->getCount() == 1) | ||||
| 				{ | ||||
| 					return; | ||||
| 				} | ||||
|  | ||||
| 				auto weakest = vstd::minElementByFun(dstNode->actor->creatureSet->Slots(), [](std::pair<SlotID, const CStackInstance *> pair) -> int | ||||
| 					{ | ||||
| 						return pair.second->getCount() * pair.second->getCreatureID().toCreature()->getAIValue(); | ||||
| 					}); | ||||
|  | ||||
| 				if(weakest == dstNode->actor->creatureSet->Slots().end()) | ||||
| 				{ | ||||
| 					logAi->debug("Empty army entering whirlpool detected at tile %s", dstNode->coord.toString()); | ||||
| 					destination.blocked = true; | ||||
|  | ||||
| 					return; | ||||
| 				} | ||||
|  | ||||
| 				if(dstNode->actor->creatureSet->getFreeSlots().size()) | ||||
| 					dstNode->armyLoss += weakest->second->getCreatureID().toCreature()->getAIValue(); | ||||
| 				else | ||||
| 					dstNode->armyLoss += (weakest->second->getCount() + 1) / 2 * weakest->second->getCreatureID().toCreature()->getAIValue(); | ||||
|  | ||||
| 				dstNode->specialAction = AIPathfinding::WhirlpoolAction::instance; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if(dstNode->specialAction && dstNode->actor) | ||||
| @@ -1014,7 +1050,7 @@ std::vector<CGPathNode *> AINodeStorage::calculateTeleportations( | ||||
|  | ||||
| 		for(auto & neighbour : accessibleExits) | ||||
| 		{ | ||||
| 			auto node = getOrCreateNode(neighbour, source.node->layer, srcNode->actor); | ||||
| 			std::optional<AIPathNode *> node = getOrCreateNode(neighbour, source.node->layer, srcNode->actor); | ||||
| 			 | ||||
| 			if(!node) | ||||
| 				continue; | ||||
|   | ||||
| @@ -48,6 +48,8 @@ namespace AIPathfinding | ||||
| 	{ | ||||
| 		options.canUseCast = true; | ||||
| 		options.allowLayerTransitioningAfterBattle = true; | ||||
| 		options.useTeleportWhirlpool = true; | ||||
| 		options.forceUseTeleportWhirlpool = true; | ||||
| 	} | ||||
|  | ||||
| 	AIPathfinderConfig::~AIPathfinderConfig() = default; | ||||
|   | ||||
							
								
								
									
										55
									
								
								AI/Nullkiller/Pathfinding/Actions/WhirlpoolAction.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								AI/Nullkiller/Pathfinding/Actions/WhirlpoolAction.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| /* | ||||
| * WhirlpoolAction.cpp, part of VCMI engine | ||||
| * | ||||
| * Authors: listed in file AUTHORS in main folder | ||||
| * | ||||
| * License: GNU General Public License v2.0 or later | ||||
| * Full text of license available in license.txt file, in main folder | ||||
| * | ||||
| */ | ||||
|  | ||||
| #include "StdInc.h" | ||||
| #include "../../Goals/AdventureSpellCast.h" | ||||
| #include "../../../../lib/mapObjects/MapObjects.h" | ||||
| #include "WhirlpoolAction.h" | ||||
| #include "../../AIGateway.h" | ||||
|  | ||||
| namespace NKAI | ||||
| { | ||||
|  | ||||
| using namespace AIPathfinding; | ||||
|  | ||||
| std::shared_ptr<WhirlpoolAction> WhirlpoolAction::instance = std::make_shared<WhirlpoolAction>(); | ||||
|  | ||||
| void WhirlpoolAction::execute(AIGateway * ai, const CGHeroInstance * hero) const | ||||
| { | ||||
| 	ai->nullkiller->armyFormation->rearrangeArmyForWhirlpool(hero); | ||||
| } | ||||
|  | ||||
| std::string WhirlpoolAction::toString() const | ||||
| { | ||||
| 	return "Prepare for whirlpool"; | ||||
| } | ||||
| /* | ||||
| bool TownPortalAction::canAct(const CGHeroInstance * hero, const AIPathNode * source) const | ||||
| { | ||||
| #ifdef VCMI_TRACE_PATHFINDER | ||||
| 	logAi->trace( | ||||
| 		"Hero %s has %d mana and needed %d and already spent %d", | ||||
| 		hero->name, | ||||
| 		hero->mana, | ||||
| 		getManaCost(hero), | ||||
| 		source->manaCost); | ||||
| #endif | ||||
|  | ||||
| 	return hero->mana >= source->manaCost + getManaCost(hero); | ||||
| } | ||||
|  | ||||
| uint32_t TownPortalAction::getManaCost(const CGHeroInstance * hero) const | ||||
| { | ||||
| 	SpellID summonBoat = SpellID::TOWN_PORTAL; | ||||
|  | ||||
| 	return hero->getSpellCost(summonBoat.toSpell()); | ||||
| }*/ | ||||
|  | ||||
| } | ||||
							
								
								
									
										35
									
								
								AI/Nullkiller/Pathfinding/Actions/WhirlpoolAction.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								AI/Nullkiller/Pathfinding/Actions/WhirlpoolAction.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| /* | ||||
| * WhirlpoolAction.h, part of VCMI engine | ||||
| * | ||||
| * Authors: listed in file AUTHORS in main folder | ||||
| * | ||||
| * License: GNU General Public License v2.0 or later | ||||
| * Full text of license available in license.txt file, in main folder | ||||
| * | ||||
| */ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "SpecialAction.h" | ||||
| #include "../../../../lib/mapObjects/MapObjects.h" | ||||
| #include "../../Goals/AdventureSpellCast.h" | ||||
|  | ||||
| namespace NKAI | ||||
| { | ||||
| namespace AIPathfinding | ||||
| { | ||||
| 	class WhirlpoolAction : public SpecialAction | ||||
| 	{ | ||||
| 	public: | ||||
| 		WhirlpoolAction() | ||||
| 		{ | ||||
| 		} | ||||
|  | ||||
| 		static std::shared_ptr<WhirlpoolAction> instance; | ||||
|  | ||||
| 		void execute(AIGateway * ai, const CGHeroInstance * hero) const override; | ||||
|  | ||||
| 		std::string toString() const override; | ||||
| 	}; | ||||
| } | ||||
| } | ||||
| @@ -11,9 +11,11 @@ | ||||
| #include "AIMovementAfterDestinationRule.h" | ||||
| #include "../Actions/BattleAction.h" | ||||
| #include "../Actions/QuestAction.h" | ||||
| #include "../Actions/WhirlpoolAction.h" | ||||
| #include "../../Goals/Invalid.h" | ||||
| #include "AIPreviousNodeRule.h" | ||||
| #include "../../../../lib/pathfinder/PathfinderOptions.h" | ||||
| #include "../../../../lib/pathfinder/CPathfinder.h" | ||||
|  | ||||
| namespace NKAI | ||||
| { | ||||
|   | ||||
| @@ -466,7 +466,7 @@ bool CPathfinderHelper::addTeleportOneWayRandom(const CGTeleport * obj) const | ||||
|  | ||||
| bool CPathfinderHelper::addTeleportWhirlpool(const CGWhirlpool * obj) const | ||||
| { | ||||
| 	return options.useTeleportWhirlpool && hasBonusOfType(BonusType::WHIRLPOOL_PROTECTION) && obj; | ||||
| 	return options.useTeleportWhirlpool && (whirlpoolProtection || options.forceUseTeleportWhirlpool) && obj; | ||||
| } | ||||
|  | ||||
| int CPathfinderHelper::movementPointsAfterEmbark(int movement, int basicCost, bool disembark) const | ||||
| @@ -506,6 +506,8 @@ CPathfinderHelper::CPathfinderHelper(CGameState * gs, const CGHeroInstance * Her | ||||
| 	updateTurnInfo(); | ||||
| 	initializePatrol(); | ||||
|  | ||||
| 	whirlpoolProtection = Hero->hasBonusOfType(BonusType::WHIRLPOOL_PROTECTION); | ||||
|  | ||||
| 	SpellID flySpell = SpellID::FLY; | ||||
| 	canCastFly = Hero->canCastThisSpell(flySpell.toSpell()); | ||||
|  | ||||
|   | ||||
| @@ -85,6 +85,7 @@ public: | ||||
| 	const PathfinderOptions & options; | ||||
| 	bool canCastFly; | ||||
| 	bool canCastWaterWalk; | ||||
| 	bool whirlpoolProtection; | ||||
|  | ||||
| 	CPathfinderHelper(CGameState * gs, const CGHeroInstance * Hero, const PathfinderOptions & Options); | ||||
| 	virtual ~CPathfinderHelper(); | ||||
|   | ||||
| @@ -34,6 +34,7 @@ PathfinderOptions::PathfinderOptions() | ||||
| 	, turnLimit(std::numeric_limits<uint8_t>::max()) | ||||
| 	, canUseCast(false) | ||||
| 	, allowLayerTransitioningAfterBattle(false) | ||||
| 	, forceUseTeleportWhirlpool(false) | ||||
| { | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -30,6 +30,7 @@ struct DLL_LINKAGE PathfinderOptions | ||||
| 	bool useTeleportOneWay; // One-way monoliths with one known exit only | ||||
| 	bool useTeleportOneWayRandom; // One-way monoliths with more than one known exit | ||||
| 	bool useTeleportWhirlpool; // Force enabled if hero protected or unaffected (have one stack of one creature) | ||||
| 	bool forceUseTeleportWhirlpool; // Force enabled if hero protected or unaffected (have one stack of one creature) | ||||
|  | ||||
| 							   /// TODO: Find out with client and server code, merge with normal teleporters. | ||||
| 							   /// Likely proper implementation would require some refactoring of CGTeleport. | ||||
|   | ||||
		Reference in New Issue
	
	Block a user