mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-22 22:13:35 +02:00
NKAI: whirlpool
This commit is contained in:
parent
3c611ffa5b
commit
683c363946
@ -1310,6 +1310,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;
|
||||
@ -1331,6 +1336,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
|
||||
status.setChannelProbing(true);
|
||||
for(auto exit : teleportChannelProbingList)
|
||||
doTeleportMovement(exit, int3(-1));
|
||||
|
||||
teleportChannelProbingList.clear();
|
||||
status.setChannelProbing(false);
|
||||
|
||||
|
@ -46,6 +46,7 @@ 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);
|
||||
if(TeleportChannel::IMPASSABLE != ai->memory->knownTeleportChannels[tObj->channel]->passability)
|
||||
{
|
||||
@ -60,6 +61,7 @@ Goals::TGoalVec ExplorationBehavior::decompose(const Nullkiller * ai) const
|
||||
{
|
||||
case Obj::MONOLITH_TWO_WAY:
|
||||
case Obj::SUBTERRANEAN_GATE:
|
||||
case Obj::WHIRLPOOL:
|
||||
auto tObj = dynamic_cast<const CGTeleport *>(obj);
|
||||
if(TeleportChannel::IMPASSABLE == ai->memory->knownTeleportChannels[tObj->channel]->passability)
|
||||
break;
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -33,6 +33,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,8 +1050,8 @@ 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.
|
||||
|
Loading…
Reference in New Issue
Block a user