1
0
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:
Xilmi 2024-07-24 14:59:24 +02:00
commit b83a214763
15 changed files with 193 additions and 13 deletions

View File

@ -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);

View File

@ -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)
{ {

View File

@ -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

View File

@ -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

View File

@ -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)
{ {

View File

@ -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);
}; };
} }

View File

@ -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;

View File

@ -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;

View 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());
}*/
}

View 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;
};
}
}

View File

@ -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
{ {

View File

@ -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());

View File

@ -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();

View File

@ -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)
{ {
} }

View File

@ -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.