1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-14 10:12:59 +02:00
vcmi/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp
Alexander Wilms 02e429e973 Fix typos using https://github.com/crate-ci/typos
Changes were reviewed manually
2024-06-24 03:47:19 +02:00

248 lines
6.9 KiB
C++

/*
* AILayerTransitionRule.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 "AILayerTransitionRule.h"
#include "../../Engine/Nullkiller.h"
#include "../../../../lib/pathfinder/CPathfinder.h"
#include "../../../../lib/pathfinder/TurnInfo.h"
namespace NKAI
{
namespace AIPathfinding
{
AILayerTransitionRule::AILayerTransitionRule(
CPlayerSpecificInfoCallback * cb,
Nullkiller * ai,
std::shared_ptr<AINodeStorage> nodeStorage)
:cb(cb), ai(ai), nodeStorage(nodeStorage)
{
setup();
}
void AILayerTransitionRule::process(
const PathNodeInfo & source,
CDestinationNodeInfo & destination,
const PathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) const
{
LayerTransitionRule::process(source, destination, pathfinderConfig, pathfinderHelper);
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
logAi->trace("Layer transitioning %s -> %s, action: %d, blocked: %s",
source.coord.toString(),
destination.coord.toString(),
static_cast<int32_t>(destination.action),
destination.blocked ? "true" : "false");
#endif
if(!destination.blocked)
{
if(source.node->layer == EPathfindingLayer::LAND
&& (destination.node->layer == EPathfindingLayer::AIR || destination.node->layer == EPathfindingLayer::WATER))
{
if(pathfinderHelper->getTurnInfo()->isLayerAvailable(destination.node->layer))
return;
else
destination.blocked = true;
}
else
{
return;
}
}
if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::SAIL)
{
std::shared_ptr<const VirtualBoatAction> virtualBoat = findVirtualBoat(destination, source);
if(virtualBoat && tryUseSpecialAction(destination, source, virtualBoat, EPathNodeAction::EMBARK))
{
#if NKAI_PATHFINDER_TRACE_LEVEL >= 1
logAi->trace("Embarking to virtual boat while moving %s -> %s!", source.coord.toString(), destination.coord.toString());
#endif
}
}
if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::WATER)
{
if(nodeStorage->getAINode(source.node)->dayFlags & DayFlags::WATER_WALK_CAST)
{
destination.blocked = false;
return;
}
auto action = waterWalkingActions.find(nodeStorage->getHero(source.node));
if(action != waterWalkingActions.end() && tryUseSpecialAction(destination, source, action->second, EPathNodeAction::NORMAL))
{
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
logAi->trace("Casting water walk while moving %s -> %s!", source.coord.toString(), destination.coord.toString());
#endif
}
}
if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::AIR)
{
if(nodeStorage->getAINode(source.node)->dayFlags & DayFlags::FLY_CAST)
{
destination.blocked = false;
return;
}
auto action = airWalkingActions.find(nodeStorage->getHero(source.node));
if(action != airWalkingActions.end() && tryUseSpecialAction(destination, source, action->second, EPathNodeAction::NORMAL))
{
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
logAi->trace("Casting fly while moving %s -> %s!", source.coord.toString(), destination.coord.toString());
#endif
}
}
}
void AILayerTransitionRule::setup()
{
SpellID waterWalk = SpellID::WATER_WALK;
SpellID airWalk = SpellID::FLY;
for(const CGHeroInstance * hero : nodeStorage->getAllHeroes())
{
if(hero->canCastThisSpell(waterWalk.toSpell()) && hero->mana >= hero->getSpellCost(waterWalk.toSpell()))
{
waterWalkingActions[hero] = std::make_shared<WaterWalkingAction>(hero);
}
if(hero->canCastThisSpell(airWalk.toSpell()) && hero->mana >= hero->getSpellCost(airWalk.toSpell()))
{
airWalkingActions[hero] = std::make_shared<AirWalkingAction>(hero);
}
}
collectVirtualBoats();
}
void AILayerTransitionRule::collectVirtualBoats()
{
std::vector<const IShipyard *> shipyards;
for(const CGTownInstance * t : cb->getTownsInfo())
{
if(t->hasBuilt(BuildingID::SHIPYARD))
shipyards.push_back(t);
}
for(const CGObjectInstance * obj : ai->memory->visitableObjs)
{
if(obj->ID != Obj::TOWN) //towns were handled in the previous loop
{
if(const auto * shipyard = dynamic_cast<const IShipyard *>(obj))
shipyards.push_back(shipyard);
}
}
for(const IShipyard * shipyard : shipyards)
{
if(shipyard->shipyardStatus() == IShipyard::GOOD)
{
int3 boatLocation = shipyard->bestLocation();
virtualBoats[boatLocation] = std::make_shared<BuildBoatAction>(cb, shipyard);
logAi->debug("Virtual boat added at %s", boatLocation.toString());
}
}
for(const CGHeroInstance * hero : nodeStorage->getAllHeroes())
{
auto summonBoatSpell = SpellID(SpellID::SUMMON_BOAT).toSpell();
if(hero->canCastThisSpell(summonBoatSpell)
&& hero->getSpellSchoolLevel(summonBoatSpell) >= MasteryLevel::ADVANCED)
{
// TODO: For lower school level we might need to check the existence of some boat
summonableVirtualBoats[hero] = std::make_shared<SummonBoatAction>();
}
}
}
std::shared_ptr<const VirtualBoatAction> AILayerTransitionRule::findVirtualBoat(
CDestinationNodeInfo & destination,
const PathNodeInfo & source) const
{
std::shared_ptr<const VirtualBoatAction> virtualBoat;
if(vstd::contains(virtualBoats, destination.coord))
{
virtualBoat = virtualBoats.at(destination.coord);
}
else
{
const CGHeroInstance * hero = nodeStorage->getHero(source.node);
if(vstd::contains(summonableVirtualBoats, hero)
&& summonableVirtualBoats.at(hero)->canAct(ai, nodeStorage->getAINode(source.node)))
{
virtualBoat = summonableVirtualBoats.at(hero);
}
}
return virtualBoat;
}
bool AILayerTransitionRule::tryUseSpecialAction(
CDestinationNodeInfo & destination,
const PathNodeInfo & source,
std::shared_ptr<const SpecialAction> specialAction,
EPathNodeAction targetAction) const
{
bool result = false;
nodeStorage->updateAINode(destination.node, [&](AIPathNode * node)
{
auto castNodeOptional = nodeStorage->getOrCreateNode(
node->coord,
node->layer,
specialAction->getActor(node->actor));
if(castNodeOptional)
{
AIPathNode * castNode = castNodeOptional.value();
if(castNode->action == EPathNodeAction::UNKNOWN)
{
castNode->addSpecialAction(specialAction);
destination.blocked = false;
destination.action = targetAction;
destination.node = castNode;
result = true;
}
else
{
#if NKAI_PATHFINDER_TRACE_LEVEL >= 1
logAi->trace(
"Special transition node already allocated. Blocked moving %s -> %s",
source.coord.toString(),
destination.coord.toString());
#endif
}
}
else
{
logAi->debug(
"Can not allocate special transition node while moving %s -> %s",
source.coord.toString(),
destination.coord.toString());
}
});
return result;
}
}
}