1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-09-16 09:26:28 +02:00

Merge pull request #1534 from kambala-decapitator/pathfinder-fly

fix movement cost with Fly
This commit is contained in:
Ivan Savenko
2023-03-21 14:32:56 +02:00
committed by GitHub
9 changed files with 69 additions and 70 deletions

View File

@@ -285,7 +285,7 @@ std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
for(auto & neighbour : accessibleNeighbourTiles) for(auto & neighbour : accessibleNeighbourTiles)
{ {
for(EPathfindingLayer i = EPathfindingLayer::LAND; i <= EPathfindingLayer::AIR; i.advance(1)) for(EPathfindingLayer i = EPathfindingLayer::LAND; i < EPathfindingLayer::NUM_LAYERS; i.advance(1))
{ {
auto nextNode = getOrCreateNode(neighbour, i, srcNode->actor); auto nextNode = getOrCreateNode(neighbour, i, srcNode->actor);

View File

@@ -167,7 +167,7 @@ std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
for(auto & neighbour : accessibleNeighbourTiles) for(auto & neighbour : accessibleNeighbourTiles)
{ {
for(EPathfindingLayer i = EPathfindingLayer::LAND; i <= EPathfindingLayer::AIR; i.advance(1)) for(EPathfindingLayer i = EPathfindingLayer::LAND; i < EPathfindingLayer::NUM_LAYERS; i.advance(1))
{ {
auto nextNode = getOrCreateNode(neighbour, i, srcNode->chainMask); auto nextNode = getOrCreateNode(neighbour, i, srcNode->chainMask);

View File

@@ -331,7 +331,7 @@ TerrainId CCreature::getNativeTerrain() const
static const auto selectorNoTerrainPenalty = Selector::type()(Bonus::NO_TERRAIN_PENALTY); static const auto selectorNoTerrainPenalty = Selector::type()(Bonus::NO_TERRAIN_PENALTY);
//this code is used in the CreatureTerrainLimiter::limit to setup battle bonuses //this code is used in the CreatureTerrainLimiter::limit to setup battle bonuses
//and in the CGHeroInstance::getNativeTerrain() to setup mevement bonuses or/and penalties. //and in the CGHeroInstance::getNativeTerrain() to setup movement bonuses or/and penalties.
return hasBonus(selectorNoTerrainPenalty, selectorNoTerrainPenalty) return hasBonus(selectorNoTerrainPenalty, selectorNoTerrainPenalty)
? TerrainId(ETerrainId::ANY_TERRAIN) ? TerrainId(ETerrainId::ANY_TERRAIN)
: (*VLC->townh)[faction]->nativeTerrain; : (*VLC->townh)[faction]->nativeTerrain;

View File

@@ -161,9 +161,6 @@ public:
bool isItNativeTerrain(TerrainId terrain) const; bool isItNativeTerrain(TerrainId terrain) const;
/** /**
Returns creature native terrain considering some terrain bonuses. Returns creature native terrain considering some terrain bonuses.
@param considerBonus is used to avoid Dead Lock when this method is called inside getAllBonuses
considerBonus = true is called from Pathfinder and fills actual nativeTerrain considering bonus(es).
considerBonus = false is called on Battle init and returns already prepared nativeTerrain without Bonus system calling.
*/ */
TerrainId getNativeTerrain() const; TerrainId getNativeTerrain() const;
int32_t getIndex() const override; int32_t getIndex() const override;

View File

@@ -78,7 +78,7 @@ std::vector<CGPathNode *> NodeStorage::calculateNeighbours(
for(auto & neighbour : accessibleNeighbourTiles) for(auto & neighbour : accessibleNeighbourTiles)
{ {
for(EPathfindingLayer i = EPathfindingLayer::LAND; i <= EPathfindingLayer::AIR; i.advance(1)) for(EPathfindingLayer i = EPathfindingLayer::LAND; i < EPathfindingLayer::NUM_LAYERS; i.advance(1))
{ {
auto * node = getNode(neighbour, i); auto * node = getNode(neighbour, i);
@@ -117,7 +117,7 @@ std::vector<CGPathNode *> NodeStorage::calculateTeleportations(
std::vector<int3> CPathfinderHelper::getNeighbourTiles(const PathNodeInfo & source) const std::vector<int3> CPathfinderHelper::getNeighbourTiles(const PathNodeInfo & source) const
{ {
std::vector<int3> neighbourTiles; std::vector<int3> neighbourTiles;
neighbourTiles.reserve(16); neighbourTiles.reserve(8);
getNeighbours( getNeighbours(
*source.tile, *source.tile,
@@ -721,7 +721,7 @@ PathfinderBlockingRule::BlockingReason MovementAfterDestinationRule::getBlocking
switch(destination.action) switch(destination.action)
{ {
/// TODO: Investigate what kind of limitation is possible to apply on movement from visitable tiles /// TODO: Investigate what kind of limitation is possible to apply on movement from visitable tiles
/// Likely in many cases we don't need to add visitable tile to queue when hero don't fly /// Likely in many cases we don't need to add visitable tile to queue when hero doesn't fly
case CGPathNode::VISIT: case CGPathNode::VISIT:
{ {
/// For now we only add visitable tile into queue when it's teleporter that allow transit /// For now we only add visitable tile into queue when it's teleporter that allow transit
@@ -730,7 +730,7 @@ PathfinderBlockingRule::BlockingReason MovementAfterDestinationRule::getBlocking
if(pathfinderHelper->isAllowedTeleportEntrance(objTeleport)) if(pathfinderHelper->isAllowedTeleportEntrance(objTeleport))
{ {
/// For now we'll always allow transit over teleporters /// For now we'll always allow transit over teleporters
/// Transit over whirlpools only allowed when hero protected /// Transit over whirlpools only allowed when hero is protected
return BlockingReason::NONE; return BlockingReason::NONE;
} }
else if(destination.nodeObject->ID == Obj::GARRISON else if(destination.nodeObject->ID == Obj::GARRISON
@@ -1163,8 +1163,8 @@ int CPathfinderHelper::getMaxMovePoints(const EPathfindingLayer & layer) const
} }
void CPathfinderHelper::getNeighbours( void CPathfinderHelper::getNeighbours(
const TerrainTile & srct, const TerrainTile & srcTile,
const int3 & tile, const int3 & srcCoord,
std::vector<int3> & vec, std::vector<int3> & vec,
const boost::logic::tribool & onLand, const boost::logic::tribool & onLand,
const bool limitCoastSailing) const const bool limitCoastSailing) const
@@ -1179,35 +1179,32 @@ void CPathfinderHelper::getNeighbours(
for(const auto & dir : dirs) for(const auto & dir : dirs)
{ {
const int3 hlp = tile + dir; const int3 destCoord = srcCoord + dir;
if(!map->isInTheMap(hlp)) if(!map->isInTheMap(destCoord))
continue; continue;
const TerrainTile & hlpt = map->getTile(hlp); const TerrainTile & destTile = map->getTile(destCoord);
if(!hlpt.terType->isPassable()) if(!destTile.terType->isPassable())
continue; continue;
// //we cannot visit things from blocked tiles // //we cannot visit things from blocked tiles
// if(srct.blocked && !srct.visitable && hlpt.visitable && srct.blockingObjects.front()->ID != HEROI_TYPE) // if(srcTile.blocked && !srcTile.visitable && destTile.visitable && srcTile.blockingObjects.front()->ID != HEROI_TYPE)
// { // {
// continue; // continue;
// } // }
/// Following condition let us avoid diagonal movement over coast when sailing /// Following condition let us avoid diagonal movement over coast when sailing
if(srct.terType->isWater() && limitCoastSailing && hlpt.terType->isWater() && dir.x && dir.y) //diagonal move through water if(srcTile.terType->isWater() && limitCoastSailing && destTile.terType->isWater() && dir.x && dir.y) //diagonal move through water
{ {
int3 hlp1 = tile; const int3 horizontalNeighbour = srcCoord + int3{dir.x, 0, 0};
int3 hlp2 = tile; const int3 verticalNeighbour = srcCoord + int3{0, dir.y, 0};
hlp1.x += dir.x; if(map->getTile(horizontalNeighbour).terType->isLand() || map->getTile(verticalNeighbour).terType->isLand())
hlp2.y += dir.y;
if(map->getTile(hlp1).terType->isLand() || map->getTile(hlp2).terType->isLand())
continue; continue;
} }
if(indeterminate(onLand) || onLand == hlpt.terType->isLand()) if(indeterminate(onLand) || onLand == destTile.terType->isLand())
{ {
vec.push_back(hlp); vec.push_back(destCoord);
} }
} }
} }
@@ -1218,7 +1215,9 @@ int CPathfinderHelper::getMovementCost(
const TerrainTile * ct, const TerrainTile * ct,
const TerrainTile * dt, const TerrainTile * dt,
const int remainingMovePoints, const int remainingMovePoints,
const bool checkLast) const const bool checkLast,
boost::logic::tribool isDstSailLayer,
boost::logic::tribool isDstWaterLayer) const
{ {
if(src == dst) //same tile if(src == dst) //same tile
return 0; return 0;
@@ -1231,48 +1230,49 @@ int CPathfinderHelper::getMovementCost(
dt = hero->cb->getTile(dst); dt = hero->cb->getTile(dst);
} }
/// TODO: by the original game rules hero shouldn't be affected by terrain penalty while flying. bool isSailLayer;
/// Also flying movement only has penalty when player moving over blocked tiles. if(indeterminate(isDstSailLayer))
/// So if you only have base flying with 40% penalty you can still ignore terrain penalty while having zero flying penalty. isSailLayer = hero->boat != nullptr && dt->terType->isWater();
int ret = hero->getTileCost(*dt, *ct, ti); else
/// Unfortunately this can't be implemented yet as server don't know when player flying and when he's not. isSailLayer = static_cast<bool>(isDstSailLayer);
/// Difference in cost calculation on client and server is much worse than incorrect cost.
/// So this one is waiting till server going to use pathfinder rules for path validation.
if(dt->blocked && ti->hasBonusOfType(Bonus::FLYING_MOVEMENT)) bool isWaterLayer;
if(indeterminate(isDstWaterLayer))
isWaterLayer = dt->terType->isWater();
else
isWaterLayer = static_cast<bool>(isDstWaterLayer);
int ret = hero->getTileCost(*dt, *ct, ti);
if(isSailLayer)
{ {
ret = static_cast<int>(ret * (100.0 + ti->valOfBonuses(Bonus::FLYING_MOVEMENT)) / 100.0); if(ct->hasFavorableWinds())
} ret = static_cast<int>(ret * 2.0 / 3);
else if(dt->terType->isWater())
{
if(hero->boat && ct->hasFavorableWinds() && dt->hasFavorableWinds())
ret = static_cast<int>(ret * 0.666);
else if(!hero->boat && ti->hasBonusOfType(Bonus::WATER_WALKING))
{
ret = static_cast<int>(ret * (100.0 + ti->valOfBonuses(Bonus::WATER_WALKING)) / 100.0);
}
} }
else if(ti->hasBonusOfType(Bonus::FLYING_MOVEMENT))
vstd::amin(ret, GameConstants::BASE_MOVEMENT_COST + ti->valOfBonuses(Bonus::FLYING_MOVEMENT));
else if(isWaterLayer && ti->hasBonusOfType(Bonus::WATER_WALKING))
ret = static_cast<int>(ret * (100.0 + ti->valOfBonuses(Bonus::WATER_WALKING)) / 100.0);
if(src.x != dst.x && src.y != dst.y) //it's diagonal move if(src.x != dst.x && src.y != dst.y) //it's diagonal move
{ {
int old = ret; int old = ret;
ret = static_cast<int>(ret * M_SQRT2); ret = static_cast<int>(ret * M_SQRT2);
//diagonal move costs too much but normal move is possible - allow diagonal move for remaining move points //diagonal move costs too much but normal move is possible - allow diagonal move for remaining move points
// https://heroes.thelazy.net/index.php/Movement#Diagonal_move_exception
if(ret > remainingMovePoints && remainingMovePoints >= old) if(ret > remainingMovePoints && remainingMovePoints >= old)
{ {
return remainingMovePoints; return remainingMovePoints;
} }
} }
/// TODO: This part need rework in order to work properly with flying and water walking const int left = remainingMovePoints - ret;
/// Currently it's only work properly for normal movement or sailing constexpr auto maxCostOfOneStep = static_cast<int>(175 * M_SQRT2); // diagonal move on Swamp - 247 MP
int left = remainingMovePoints-ret; if(checkLast && left > 0 && left <= maxCostOfOneStep) //it might be the last tile - if no further move possible we take all move points
if(checkLast && left > 0 && remainingMovePoints-ret < 250) //it might be the last tile - if no further move possible we take all move points
{ {
std::vector<int3> vec; std::vector<int3> vec;
vec.reserve(8); //optimization vec.reserve(8); //optimization
getNeighbours(*dt, dst, vec, ct->terType->isLand(), true); getNeighbours(*dt, dst, vec, ct->terType->isLand(), true);
for(auto & elem : vec) for(const auto & elem : vec)
{ {
int fcost = getMovementCost(dst, elem, nullptr, nullptr, left, false); int fcost = getMovementCost(dst, elem, nullptr, nullptr, left, false);
if(fcost <= left) if(fcost <= left)

View File

@@ -65,7 +65,7 @@ struct DLL_LINKAGE CGPathNode
VISITABLE, //tile can be entered as the last tile in path VISITABLE, //tile can be entered as the last tile in path
BLOCKVIS, //visitable from neighboring tile but not passable BLOCKVIS, //visitable from neighboring tile but not passable
FLYABLE, //can only be accessed in air layer FLYABLE, //can only be accessed in air layer
BLOCKED //tile can't be entered nor visited BLOCKED //tile can be neither entered nor visited
}; };
CGPathNode * theNodeBefore; CGPathNode * theNodeBefore;
@@ -580,8 +580,8 @@ public:
std::vector<int3> getTeleportExits(const PathNodeInfo & source) const; std::vector<int3> getTeleportExits(const PathNodeInfo & source) const;
void getNeighbours( void getNeighbours(
const TerrainTile & srct, const TerrainTile & srcTile,
const int3 & tile, const int3 & srcCoord,
std::vector<int3> & vec, std::vector<int3> & vec,
const boost::logic::tribool & onLand, const boost::logic::tribool & onLand,
const bool limitCoastSailing) const; const bool limitCoastSailing) const;
@@ -591,8 +591,10 @@ public:
const int3 & dst, const int3 & dst,
const TerrainTile * ct, const TerrainTile * ct,
const TerrainTile * dt, const TerrainTile * dt,
const int remainingMovePoints =- 1, const int remainingMovePoints = -1,
const bool checkLast = true) const; const bool checkLast = true,
boost::logic::tribool isDstSailLayer = boost::logic::indeterminate,
boost::logic::tribool isDstWaterLayer = boost::logic::indeterminate) const;
int getMovementCost( int getMovementCost(
const PathNodeInfo & src, const PathNodeInfo & src,
@@ -606,7 +608,9 @@ public:
src.tile, src.tile,
dst.tile, dst.tile,
remainingMovePoints, remainingMovePoints,
checkLast checkLast,
dst.node->layer == EPathfindingLayer::SAIL,
dst.node->layer == EPathfindingLayer::WATER
); );
} }

View File

@@ -66,10 +66,10 @@ ui32 CGHeroInstance::getTileCost(const TerrainTile & dest, const TerrainTile & f
{ {
int64_t ret = GameConstants::BASE_MOVEMENT_COST; int64_t ret = GameConstants::BASE_MOVEMENT_COST;
//if there is road both on dest and src tiles - use road movement cost //if there is road both on dest and src tiles - use src road movement cost
if(dest.roadType->getId() != Road::NO_ROAD && from.roadType->getId() != Road::NO_ROAD) if(dest.roadType->getId() != Road::NO_ROAD && from.roadType->getId() != Road::NO_ROAD)
{ {
ret = std::max(dest.roadType->movementCost, from.roadType->movementCost); ret = from.roadType->movementCost;
} }
else if(ti->nativeTerrain != from.terType->getId() &&//the terrain is not native else if(ti->nativeTerrain != from.terType->getId() &&//the terrain is not native
ti->nativeTerrain != ETerrainId::ANY_TERRAIN && //no special creature bonus ti->nativeTerrain != ETerrainId::ANY_TERRAIN && //no special creature bonus
@@ -1106,22 +1106,19 @@ CBonusSystemNode & CGHeroInstance::whereShouldBeAttached(CGameState * gs)
int CGHeroInstance::movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark, const TurnInfo * ti) const int CGHeroInstance::movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark, const TurnInfo * ti) const
{ {
int ret = 0; //take all MPs by default std::unique_ptr<TurnInfo> turnInfoLocal;
bool localTi = false;
if(!ti) if(!ti)
{ {
localTi = true; turnInfoLocal = std::make_unique<TurnInfo>(this);
ti = new TurnInfo(this); ti = turnInfoLocal.get();
} }
if(!ti->hasBonusOfType(Bonus::FREE_SHIP_BOARDING))
return 0; // take all MPs by default
int mp1 = ti->getMaxMovePoints(disembark ? EPathfindingLayer::LAND : EPathfindingLayer::SAIL); int mp1 = ti->getMaxMovePoints(disembark ? EPathfindingLayer::LAND : EPathfindingLayer::SAIL);
int mp2 = ti->getMaxMovePoints(disembark ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND); int mp2 = ti->getMaxMovePoints(disembark ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND);
if(ti->hasBonusOfType(Bonus::FREE_SHIP_BOARDING)) int ret = static_cast<int>((MPsBefore - basicCost) * static_cast<double>(mp1) / mp2);
ret = static_cast<int>((MPsBefore - basicCost) * static_cast<double>(mp1) / mp2);
if(localTi)
delete ti;
return ret; return ret;
} }

View File

@@ -59,7 +59,10 @@ PlayerParams::PlayerParams(MapController & ctrl, int playerId, QWidget *parent)
{ {
auto * ctown = town->town; auto * ctown = town->town;
if(!ctown) if(!ctown)
{
ctown = VLC->townh->randomTown; ctown = VLC->townh->randomTown;
town->town = ctown;
}
if(ctown && town->getOwner().getNum() == playerColor) if(ctown && town->getOwner().getNum() == playerColor)
{ {
if(playerInfo.hasMainTown && playerInfo.posOfMainTown == town->pos) if(playerInfo.hasMainTown && playerInfo.posOfMainTown == town->pos)

View File

@@ -2342,8 +2342,6 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
//check if destination tile is available //check if destination tile is available
auto pathfinderHelper = std::make_unique<CPathfinderHelper>(gs, h, PathfinderOptions()); auto pathfinderHelper = std::make_unique<CPathfinderHelper>(gs, h, PathfinderOptions());
pathfinderHelper->updateTurnInfo(0);
auto ti = pathfinderHelper->getTurnInfo(); auto ti = pathfinderHelper->getTurnInfo();
const bool canFly = pathfinderHelper->hasBonusOfType(Bonus::FLYING_MOVEMENT); const bool canFly = pathfinderHelper->hasBonusOfType(Bonus::FLYING_MOVEMENT);