mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-14 10:12:59 +02:00
Merge remote-tracking branch 'upstream/develop' into develop
This commit is contained in:
commit
b83a214763
@ -1311,6 +1311,11 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
|
|||||||
|
|
||||||
auto doTeleportMovement = [&](ObjectInstanceID exitId, int3 exitPos)
|
auto doTeleportMovement = [&](ObjectInstanceID exitId, int3 exitPos)
|
||||||
{
|
{
|
||||||
|
if(cb->getObj(exitId) && cb->getObj(exitId)->ID == Obj::WHIRLPOOL)
|
||||||
|
{
|
||||||
|
nullkiller->armyFormation->rearrangeArmyForWhirlpool(*h);
|
||||||
|
}
|
||||||
|
|
||||||
destinationTeleport = exitId;
|
destinationTeleport = exitId;
|
||||||
if(exitPos.valid())
|
if(exitPos.valid())
|
||||||
destinationTeleportPos = exitPos;
|
destinationTeleportPos = exitPos;
|
||||||
@ -1332,6 +1337,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
|
|||||||
status.setChannelProbing(true);
|
status.setChannelProbing(true);
|
||||||
for(auto exit : teleportChannelProbingList)
|
for(auto exit : teleportChannelProbingList)
|
||||||
doTeleportMovement(exit, int3(-1));
|
doTeleportMovement(exit, int3(-1));
|
||||||
|
|
||||||
teleportChannelProbingList.clear();
|
teleportChannelProbingList.clear();
|
||||||
status.setChannelProbing(false);
|
status.setChannelProbing(false);
|
||||||
|
|
||||||
|
@ -48,9 +48,12 @@ Goals::TGoalVec ExplorationBehavior::decompose(const Nullkiller * ai) const
|
|||||||
case Obj::MONOLITH_ONE_WAY_ENTRANCE:
|
case Obj::MONOLITH_ONE_WAY_ENTRANCE:
|
||||||
case Obj::MONOLITH_TWO_WAY:
|
case Obj::MONOLITH_TWO_WAY:
|
||||||
case Obj::SUBTERRANEAN_GATE:
|
case Obj::SUBTERRANEAN_GATE:
|
||||||
|
case Obj::WHIRLPOOL:
|
||||||
{
|
{
|
||||||
auto tObj = dynamic_cast<const CGTeleport*>(obj);
|
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)
|
if (exit != tObj->id)
|
||||||
{
|
{
|
||||||
|
@ -8,6 +8,7 @@ set(Nullkiller_SRCS
|
|||||||
Pathfinding/Actions/QuestAction.cpp
|
Pathfinding/Actions/QuestAction.cpp
|
||||||
Pathfinding/Actions/BuyArmyAction.cpp
|
Pathfinding/Actions/BuyArmyAction.cpp
|
||||||
Pathfinding/Actions/BoatActions.cpp
|
Pathfinding/Actions/BoatActions.cpp
|
||||||
|
Pathfinding/Actions/WhirlpoolAction.cpp
|
||||||
Pathfinding/Actions/TownPortalAction.cpp
|
Pathfinding/Actions/TownPortalAction.cpp
|
||||||
Pathfinding/Actions/AdventureSpellCastMovementActions.cpp
|
Pathfinding/Actions/AdventureSpellCastMovementActions.cpp
|
||||||
Pathfinding/Rules/AILayerTransitionRule.cpp
|
Pathfinding/Rules/AILayerTransitionRule.cpp
|
||||||
@ -79,6 +80,7 @@ set(Nullkiller_HEADERS
|
|||||||
Pathfinding/Actions/QuestAction.h
|
Pathfinding/Actions/QuestAction.h
|
||||||
Pathfinding/Actions/BuyArmyAction.h
|
Pathfinding/Actions/BuyArmyAction.h
|
||||||
Pathfinding/Actions/BoatActions.h
|
Pathfinding/Actions/BoatActions.h
|
||||||
|
Pathfinding/Actions/WhirlpoolAction.h
|
||||||
Pathfinding/Actions/TownPortalAction.h
|
Pathfinding/Actions/TownPortalAction.h
|
||||||
Pathfinding/Actions/AdventureSpellCastMovementActions.h
|
Pathfinding/Actions/AdventureSpellCastMovementActions.h
|
||||||
Pathfinding/Rules/AILayerTransitionRule.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())
|
if(hero->movementPointsRemaining())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -14,27 +14,37 @@
|
|||||||
namespace NKAI
|
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())
|
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
|
return slot.second->getCount() == 1
|
||||||
? std::numeric_limits<int>::max()
|
? std::numeric_limits<int>::max()
|
||||||
: slot.second->getCreatureID().toCreature()->getAIValue();
|
: slot.second->getCreatureID().toCreature()->getAIValue();
|
||||||
});
|
});
|
||||||
|
|
||||||
if(weakestCreature == attacker->Slots().end() || weakestCreature->second->getCount() == 1)
|
if(weakestCreature == hero->Slots().end() || weakestCreature->second->getCount() == 1)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
cb->splitStack(attacker, attacker, weakestCreature->first, freeSlots.front(), 1);
|
cb->splitStack(hero, hero, weakestCreature->first, freeSlots.front(), 1);
|
||||||
freeSlots.pop();
|
freeSlots.pop();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArmyFormation::rearrangeArmyForSiege(const CGTownInstance * town, const CGHeroInstance * attacker)
|
||||||
|
{
|
||||||
|
addSingleCreatureStacks(attacker);
|
||||||
|
|
||||||
if(town->fortLevel() > CGTownInstance::FORT)
|
if(town->fortLevel() > CGTownInstance::FORT)
|
||||||
{
|
{
|
||||||
|
@ -31,6 +31,10 @@ public:
|
|||||||
ArmyFormation(std::shared_ptr<CCallback> CB, const Nullkiller * ai): cb(CB) {}
|
ArmyFormation(std::shared_ptr<CCallback> CB, const Nullkiller * ai): cb(CB) {}
|
||||||
|
|
||||||
void rearrangeArmyForSiege(const CGTownInstance * town, const CGHeroInstance * attacker);
|
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 "StdInc.h"
|
||||||
#include "AINodeStorage.h"
|
#include "AINodeStorage.h"
|
||||||
#include "Actions/TownPortalAction.h"
|
#include "Actions/TownPortalAction.h"
|
||||||
|
#include "Actions/WhirlpoolAction.h"
|
||||||
#include "../Goals/Goals.h"
|
#include "../Goals/Goals.h"
|
||||||
#include "../AIGateway.h"
|
#include "../AIGateway.h"
|
||||||
#include "../Engine/Nullkiller.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);
|
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
|
// 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)
|
if(dstNode->specialAction && dstNode->actor)
|
||||||
@ -1014,7 +1050,7 @@ std::vector<CGPathNode *> AINodeStorage::calculateTeleportations(
|
|||||||
|
|
||||||
for(auto & neighbour : accessibleExits)
|
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)
|
if(!node)
|
||||||
continue;
|
continue;
|
||||||
|
@ -48,6 +48,8 @@ namespace AIPathfinding
|
|||||||
{
|
{
|
||||||
options.canUseCast = true;
|
options.canUseCast = true;
|
||||||
options.allowLayerTransitioningAfterBattle = true;
|
options.allowLayerTransitioningAfterBattle = true;
|
||||||
|
options.useTeleportWhirlpool = true;
|
||||||
|
options.forceUseTeleportWhirlpool = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
AIPathfinderConfig::~AIPathfinderConfig() = default;
|
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 "AIMovementAfterDestinationRule.h"
|
||||||
#include "../Actions/BattleAction.h"
|
#include "../Actions/BattleAction.h"
|
||||||
#include "../Actions/QuestAction.h"
|
#include "../Actions/QuestAction.h"
|
||||||
|
#include "../Actions/WhirlpoolAction.h"
|
||||||
#include "../../Goals/Invalid.h"
|
#include "../../Goals/Invalid.h"
|
||||||
#include "AIPreviousNodeRule.h"
|
#include "AIPreviousNodeRule.h"
|
||||||
#include "../../../../lib/pathfinder/PathfinderOptions.h"
|
#include "../../../../lib/pathfinder/PathfinderOptions.h"
|
||||||
|
#include "../../../../lib/pathfinder/CPathfinder.h"
|
||||||
|
|
||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
@ -466,7 +466,7 @@ bool CPathfinderHelper::addTeleportOneWayRandom(const CGTeleport * obj) const
|
|||||||
|
|
||||||
bool CPathfinderHelper::addTeleportWhirlpool(const CGWhirlpool * 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
|
int CPathfinderHelper::movementPointsAfterEmbark(int movement, int basicCost, bool disembark) const
|
||||||
@ -506,6 +506,8 @@ CPathfinderHelper::CPathfinderHelper(CGameState * gs, const CGHeroInstance * Her
|
|||||||
updateTurnInfo();
|
updateTurnInfo();
|
||||||
initializePatrol();
|
initializePatrol();
|
||||||
|
|
||||||
|
whirlpoolProtection = Hero->hasBonusOfType(BonusType::WHIRLPOOL_PROTECTION);
|
||||||
|
|
||||||
SpellID flySpell = SpellID::FLY;
|
SpellID flySpell = SpellID::FLY;
|
||||||
canCastFly = Hero->canCastThisSpell(flySpell.toSpell());
|
canCastFly = Hero->canCastThisSpell(flySpell.toSpell());
|
||||||
|
|
||||||
|
@ -85,6 +85,7 @@ public:
|
|||||||
const PathfinderOptions & options;
|
const PathfinderOptions & options;
|
||||||
bool canCastFly;
|
bool canCastFly;
|
||||||
bool canCastWaterWalk;
|
bool canCastWaterWalk;
|
||||||
|
bool whirlpoolProtection;
|
||||||
|
|
||||||
CPathfinderHelper(CGameState * gs, const CGHeroInstance * Hero, const PathfinderOptions & Options);
|
CPathfinderHelper(CGameState * gs, const CGHeroInstance * Hero, const PathfinderOptions & Options);
|
||||||
virtual ~CPathfinderHelper();
|
virtual ~CPathfinderHelper();
|
||||||
|
@ -34,6 +34,7 @@ PathfinderOptions::PathfinderOptions()
|
|||||||
, turnLimit(std::numeric_limits<uint8_t>::max())
|
, turnLimit(std::numeric_limits<uint8_t>::max())
|
||||||
, canUseCast(false)
|
, canUseCast(false)
|
||||||
, allowLayerTransitioningAfterBattle(false)
|
, allowLayerTransitioningAfterBattle(false)
|
||||||
|
, forceUseTeleportWhirlpool(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ struct DLL_LINKAGE PathfinderOptions
|
|||||||
bool useTeleportOneWay; // One-way monoliths with one known exit only
|
bool useTeleportOneWay; // One-way monoliths with one known exit only
|
||||||
bool useTeleportOneWayRandom; // One-way monoliths with more than one known exit
|
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 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.
|
/// TODO: Find out with client and server code, merge with normal teleporters.
|
||||||
/// Likely proper implementation would require some refactoring of CGTeleport.
|
/// Likely proper implementation would require some refactoring of CGTeleport.
|
||||||
|
Loading…
Reference in New Issue
Block a user