mirror of
https://github.com/vcmi/vcmi.git
synced 2025-02-05 13:04:54 +02:00
Merge pull request #2247 from IvanSavenko/pathfinding_fixes
Fix accumulated issues with pathfinding
This commit is contained in:
commit
5c3cacd290
@ -802,8 +802,8 @@ void AIGateway::makeTurn()
|
||||
//for debug purpose
|
||||
for (auto h : cb->getHeroesInfo())
|
||||
{
|
||||
if (h->movement)
|
||||
logAi->warn("Hero %s has %d MP left", h->getNameTranslated(), h->movement);
|
||||
if (h->movementPointsRemaining())
|
||||
logAi->warn("Hero %s has %d MP left", h->getNameTranslated(), h->movementPointsRemaining());
|
||||
}
|
||||
#if NKAI_TRACE_LEVEL == 0
|
||||
}
|
||||
|
@ -266,7 +266,7 @@ bool isBlockVisitObj(const int3 & pos)
|
||||
{
|
||||
if(auto obj = cb->getTopObj(pos))
|
||||
{
|
||||
if(obj->blockVisit) //we can't stand on that object
|
||||
if(obj->isBlockedVisitable()) //we can't stand on that object
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -187,7 +187,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
if(ai->nullkiller->isHeroLocked(existingHero)
|
||||
|| existingHero->getArmyStrength() > hero->getArmyStrength()
|
||||
|| ai->nullkiller->heroManager->getHeroRole(existingHero) == HeroRole::MAIN
|
||||
|| existingHero->movement
|
||||
|| existingHero->movementPointsRemaining()
|
||||
|| existingHero->artifactsWorn.size() > (existingHero->hasSpellbook() ? 2 : 1))
|
||||
continue;
|
||||
|
||||
|
@ -206,7 +206,7 @@ Goals::TGoalVec StartupBehavior::decompose() const
|
||||
for(const CGTownInstance * town : towns)
|
||||
{
|
||||
if(town->garrisonHero
|
||||
&& town->garrisonHero->movement
|
||||
&& town->garrisonHero->movementPointsRemaining()
|
||||
&& !town->visitingHero
|
||||
&& ai->nullkiller->getHeroLockedReason(town->garrisonHero) != HeroLockedReason::DEFENCE)
|
||||
{
|
||||
|
@ -790,7 +790,7 @@ public:
|
||||
if(garrisonHero && swapCommand.getLockingReason() == HeroLockedReason::DEFENCE)
|
||||
{
|
||||
auto defenderRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(garrisonHero);
|
||||
auto mpLeft = garrisonHero->movement / (float)garrisonHero->maxMovePoints(true);
|
||||
auto mpLeft = garrisonHero->movementPointsRemaining() / (float)garrisonHero->movementPointsLimit(true);
|
||||
|
||||
evaluationContext.movementCost += mpLeft;
|
||||
evaluationContext.movementCostByRole[defenderRole] += mpLeft;
|
||||
|
@ -78,7 +78,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
|
||||
|
||||
try
|
||||
{
|
||||
if(hero->movement)
|
||||
if(hero->movementPointsRemaining() > 0)
|
||||
{
|
||||
ai->nullkiller->setActive(hero, node.coord);
|
||||
|
||||
@ -117,7 +117,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
|
||||
}
|
||||
}
|
||||
|
||||
if(hero->movement)
|
||||
if(hero->movementPointsRemaining())
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -135,14 +135,14 @@ void ExecuteHeroChain::accept(AIGateway * ai)
|
||||
return;
|
||||
}
|
||||
|
||||
if(hero->movement > 0)
|
||||
if(hero->movementPointsRemaining() > 0)
|
||||
{
|
||||
CGPath path;
|
||||
bool isOk = cb->getPathsInfo(hero)->getPath(path, node.coord);
|
||||
|
||||
if(isOk && path.nodes.back().turns > 0)
|
||||
{
|
||||
logAi->warn("Hero %s has %d mp which is not enough to continue his way towards %s.", hero->getNameTranslated(), hero->movement, node.coord.toString());
|
||||
logAi->warn("Hero %s has %d mp which is not enough to continue his way towards %s.", hero->getNameTranslated(), hero->movementPointsRemaining(), node.coord.toString());
|
||||
|
||||
ai->nullkiller->lockHero(hero, HeroLockedReason::HERO_CHAIN);
|
||||
return;
|
||||
|
@ -888,7 +888,7 @@ void AINodeStorage::setHeroes(std::map<const CGHeroInstance *, HeroRole> heroes)
|
||||
if(actor->hero->tempOwner != ai->playerID)
|
||||
{
|
||||
bool onLand = !actor->hero->boat || actor->hero->boat->layer != EPathfindingLayer::SAIL;
|
||||
actor->initialMovement = actor->hero->maxMovePoints(onLand);
|
||||
actor->initialMovement = actor->hero->movementPointsLimit(onLand);
|
||||
}
|
||||
|
||||
playerID = actor->hero->tempOwner;
|
||||
@ -1053,7 +1053,7 @@ struct TowmPortalFinder
|
||||
return std::nullopt;
|
||||
|
||||
AIPathNode * node = nodeOptional.value();
|
||||
float movementCost = (float)movementNeeded / (float)hero->maxMovePoints(EPathfindingLayer::LAND);
|
||||
float movementCost = (float)movementNeeded / (float)hero->movementPointsLimit(EPathfindingLayer::LAND);
|
||||
|
||||
movementCost += bestNode->getCost();
|
||||
|
||||
|
@ -43,7 +43,7 @@ ChainActor::ChainActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t
|
||||
{
|
||||
initialPosition = hero->visitablePos();
|
||||
layer = hero->boat ? hero->boat->layer : EPathfindingLayer::LAND;
|
||||
initialMovement = hero->movement;
|
||||
initialMovement = hero->movementPointsRemaining();
|
||||
initialTurn = 0;
|
||||
armyValue = hero->getArmyStrength();
|
||||
heroFightingStrength = hero->getFightingStrength();
|
||||
@ -75,7 +75,7 @@ int ChainActor::maxMovePoints(CGPathNode::ELayer layer)
|
||||
throw std::logic_error("Asking movement points for static actor");
|
||||
#endif
|
||||
|
||||
return hero->maxMovePointsCached(layer, tiCache.get());
|
||||
return hero->movementPointsLimitCached(layer, tiCache.get());
|
||||
}
|
||||
|
||||
std::string ChainActor::toString() const
|
||||
|
@ -216,7 +216,7 @@ bool isBlockVisitObj(const int3 & pos)
|
||||
{
|
||||
if(auto obj = cb->getTopObj(pos))
|
||||
{
|
||||
if(obj->blockVisit) //we can't stand on that object
|
||||
if(obj->isBlockedVisitable()) //we can't stand on that object
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -155,7 +155,7 @@ namespace Goals
|
||||
|
||||
// picking up resources does not yield any exploration at all.
|
||||
// if it blocks the way to some explorable tile AIPathfinder will take care of it
|
||||
if(obj && obj->blockVisit)
|
||||
if(obj && obj->isBlockedVisitable())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -267,7 +267,7 @@ TGoalVec Explore::getAllPossibleSubgoals()
|
||||
if(!ai->isAbleToExplore(h))
|
||||
return true;
|
||||
|
||||
return !h->movement; //saves time, immobile heroes are useless anyway
|
||||
return !h->movementPointsRemaining(); //saves time, immobile heroes are useless anyway
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -113,7 +113,7 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
|
||||
auto initialNode = getOrCreateNode(hpos, hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND, NORMAL_CHAIN).value();
|
||||
|
||||
initialNode->turns = 0;
|
||||
initialNode->moveRemains = hero->movement;
|
||||
initialNode->moveRemains = hero->movementPointsRemaining();
|
||||
initialNode->danger = 0;
|
||||
initialNode->setCost(0.0);
|
||||
|
||||
@ -245,7 +245,7 @@ void AINodeStorage::calculateTownPortalTeleportations(
|
||||
auto skillLevel = hero->getSpellSchoolLevel(townPortal);
|
||||
auto movementCost = GameConstants::BASE_MOVEMENT_COST * (skillLevel >= 3 ? 2 : 3);
|
||||
|
||||
if(hero->movement < movementCost)
|
||||
if(hero->movementPointsRemaining() < movementCost)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -41,6 +41,11 @@ namespace AIPathfinding
|
||||
std::shared_ptr<AINodeStorage> nodeStorage)
|
||||
:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage)), hero(nodeStorage->getHero())
|
||||
{
|
||||
options.useEmbarkAndDisembark = true;
|
||||
options.useTeleportTwoWay = true;
|
||||
options.useTeleportOneWay = true;
|
||||
options.useTeleportOneWayRandom = true;
|
||||
options.useTeleportWhirlpool = true;
|
||||
}
|
||||
|
||||
AIPathfinderConfig::~AIPathfinderConfig() = default;
|
||||
|
@ -813,8 +813,8 @@ void VCAI::makeTurn()
|
||||
//for debug purpose
|
||||
for (auto h : cb->getHeroesInfo())
|
||||
{
|
||||
if (h->movement)
|
||||
logAi->warn("Hero %s has %d MP left", h->getNameTranslated(), h->movement);
|
||||
if (h->movementPointsRemaining())
|
||||
logAi->warn("Hero %s has %d MP left", h->getNameTranslated(), h->movementPointsRemaining());
|
||||
}
|
||||
}
|
||||
catch (boost::thread_interrupted & e)
|
||||
@ -949,7 +949,7 @@ void VCAI::mainLoop()
|
||||
if (bestGoal->hero) //lock this hero to fulfill goal
|
||||
{
|
||||
setGoal(bestGoal->hero, bestGoal);
|
||||
if (!bestGoal->hero->movement || vstd::contains(invalidPathHeroes, bestGoal->hero))
|
||||
if (!bestGoal->hero->movementPointsRemaining() || vstd::contains(invalidPathHeroes, bestGoal->hero))
|
||||
{
|
||||
if (!vstd::erase_if_present(possibleGoals, bestGoal))
|
||||
{
|
||||
@ -1354,7 +1354,7 @@ void VCAI::wander(HeroPtr h)
|
||||
|
||||
TimeCheck tc("looking for wander destination");
|
||||
|
||||
while(h->movement)
|
||||
while(h->movementPointsRemaining())
|
||||
{
|
||||
validateVisitableObjs();
|
||||
ah->updatePaths(getMyHeroes());
|
||||
@ -2031,7 +2031,7 @@ void VCAI::tryRealize(Goals::RecruitHero & g)
|
||||
|
||||
void VCAI::tryRealize(Goals::VisitTile & g)
|
||||
{
|
||||
if(!g.hero->movement)
|
||||
if(!g.hero->movementPointsRemaining())
|
||||
throw cannotFulfillGoalException("Cannot visit tile: hero is out of MPs!");
|
||||
if(g.tile == g.hero->visitablePos() && cb->getVisitableObjs(g.hero->visitablePos()).size() < 2)
|
||||
{
|
||||
@ -2047,7 +2047,7 @@ void VCAI::tryRealize(Goals::VisitTile & g)
|
||||
void VCAI::tryRealize(Goals::VisitObj & g)
|
||||
{
|
||||
auto position = g.tile;
|
||||
if(!g.hero->movement)
|
||||
if(!g.hero->movementPointsRemaining())
|
||||
throw cannotFulfillGoalException("Cannot visit object: hero is out of MPs!");
|
||||
if(position == g.hero->visitablePos() && cb->getVisitableObjs(g.hero->visitablePos()).size() < 2)
|
||||
{
|
||||
@ -2062,7 +2062,7 @@ void VCAI::tryRealize(Goals::VisitObj & g)
|
||||
|
||||
void VCAI::tryRealize(Goals::VisitHero & g)
|
||||
{
|
||||
if(!g.hero->movement)
|
||||
if(!g.hero->movementPointsRemaining())
|
||||
throw cannotFulfillGoalException("Cannot visit target hero: hero is out of MPs!");
|
||||
|
||||
const CGObjectInstance * obj = cb->getObj(ObjectInstanceID(g.objid));
|
||||
@ -2263,7 +2263,7 @@ bool VCAI::canAct(HeroPtr h) const
|
||||
return false;
|
||||
}
|
||||
|
||||
return h->movement;
|
||||
return h->movementPointsRemaining();
|
||||
}
|
||||
|
||||
HeroPtr VCAI::primaryHero() const
|
||||
@ -2412,7 +2412,7 @@ void VCAI::performTypicalActions()
|
||||
if(!h) //hero might be lost. getUnblockedHeroes() called once on start of turn
|
||||
continue;
|
||||
|
||||
logAi->debug("Hero %s started wandering, MP=%d", h->getNameTranslated(), h->movement);
|
||||
logAi->debug("Hero %s started wandering, MP=%d", h->getNameTranslated(), h->movementPointsRemaining());
|
||||
makePossibleUpgrades(*h);
|
||||
pickBestArtifacts(*h);
|
||||
try
|
||||
|
@ -1922,10 +1922,27 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
|
||||
for (auto & elem : path.nodes)
|
||||
elem.coord = h->convertFromVisitablePos(elem.coord);
|
||||
|
||||
TerrainId currentTerrain = ETerrainId::NONE;
|
||||
TerrainId newTerrain;
|
||||
bool wasOnRoad = true;
|
||||
int sh = -1;
|
||||
int soundChannel = -1;
|
||||
std::string soundName;
|
||||
|
||||
auto getMovementSoundFor = [&](const CGHeroInstance * hero, int3 posPrev, int3 posNext) -> std::string
|
||||
{
|
||||
// flying movement sound
|
||||
if (hero->hasBonusOfType(BonusType::FLYING_MOVEMENT))
|
||||
return "HORSE10.wav";
|
||||
|
||||
auto prevTile = cb->getTile(h->convertToVisitablePos(posPrev));
|
||||
auto nextTile = cb->getTile(h->convertToVisitablePos(posNext));
|
||||
|
||||
auto prevRoad = prevTile->roadType;
|
||||
auto nextRoad = nextTile->roadType;
|
||||
bool movingOnRoad = prevRoad->getId() != Road::NO_ROAD && nextRoad->getId() != Road::NO_ROAD;
|
||||
|
||||
if (movingOnRoad)
|
||||
return nextTile->terType->horseSound;
|
||||
else
|
||||
return nextTile->terType->horseSoundPenalty;
|
||||
};
|
||||
|
||||
auto canStop = [&](CGPathNode * node) -> bool
|
||||
{
|
||||
@ -1943,18 +1960,13 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
|
||||
int3 prevCoord = path.nodes[i].coord;
|
||||
int3 nextCoord = path.nodes[i-1].coord;
|
||||
|
||||
auto prevRoad = cb->getTile(h->convertToVisitablePos(prevCoord))->roadType;
|
||||
auto nextRoad = cb->getTile(h->convertToVisitablePos(nextCoord))->roadType;
|
||||
|
||||
bool movingOnRoad = prevRoad->getId() != Road::NO_ROAD && nextRoad->getId() != Road::NO_ROAD;
|
||||
|
||||
auto prevObject = getObj(prevCoord, prevCoord == h->pos);
|
||||
auto nextObjectTop = getObj(nextCoord, false);
|
||||
auto nextObject = getObj(nextCoord, true);
|
||||
auto destTeleportObj = getDestTeleportObj(prevObject, nextObjectTop, nextObject);
|
||||
if (isTeleportAction(path.nodes[i-1].action) && destTeleportObj != nullptr)
|
||||
{
|
||||
CCS->soundh->stopSound(sh);
|
||||
CCS->soundh->stopSound(soundChannel);
|
||||
destinationTeleport = destTeleportObj->id;
|
||||
destinationTeleportPos = nextCoord;
|
||||
doMovement(h->pos, false);
|
||||
@ -1966,10 +1978,8 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
|
||||
}
|
||||
if(i != path.nodes.size() - 1)
|
||||
{
|
||||
if (movingOnRoad)
|
||||
sh = CCS->soundh->playSound(VLC->terrainTypeHandler->getById(currentTerrain)->horseSound, -1);
|
||||
else
|
||||
sh = CCS->soundh->playSound(VLC->terrainTypeHandler->getById(currentTerrain)->horseSoundPenalty, -1);
|
||||
soundName = getMovementSoundFor(h, prevCoord, nextCoord);
|
||||
soundChannel = CCS->soundh->playSound(soundName, -1);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@ -1980,23 +1990,16 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
|
||||
break;
|
||||
}
|
||||
|
||||
// Start a new sound for the hero movement or let the existing one carry on.
|
||||
#if 0
|
||||
// TODO
|
||||
if (hero is flying && sh == -1)
|
||||
sh = CCS->soundh->playSound(soundBase::horseFlying, -1);
|
||||
#endif
|
||||
{
|
||||
newTerrain = cb->getTile(h->convertToVisitablePos(prevCoord))->terType->getId();
|
||||
if(newTerrain != currentTerrain || wasOnRoad != movingOnRoad)
|
||||
// Start a new sound for the hero movement or let the existing one carry on.
|
||||
std::string newSoundName = getMovementSoundFor(h, prevCoord, nextCoord);
|
||||
|
||||
if(newSoundName != soundName)
|
||||
{
|
||||
CCS->soundh->stopSound(sh);
|
||||
if (movingOnRoad)
|
||||
sh = CCS->soundh->playSound(VLC->terrainTypeHandler->getById(newTerrain)->horseSound, -1);
|
||||
else
|
||||
sh = CCS->soundh->playSound(VLC->terrainTypeHandler->getById(newTerrain)->horseSoundPenalty, -1);
|
||||
currentTerrain = newTerrain;
|
||||
wasOnRoad = movingOnRoad;
|
||||
soundName = newSoundName;
|
||||
|
||||
CCS->soundh->stopSound(soundChannel);
|
||||
soundChannel = CCS->soundh->playSound(soundName, -1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2022,7 +2025,7 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
|
||||
break;
|
||||
}
|
||||
|
||||
CCS->soundh->stopSound(sh);
|
||||
CCS->soundh->stopSound(soundChannel);
|
||||
}
|
||||
|
||||
//Update cursor so icon can change if needed when it reappears; doesn;'t apply if a dialog box pops up at the end of the movement
|
||||
|
@ -388,7 +388,7 @@ void ClientCommandManager::handleTellCommand(std::istringstream& singleWordBuffe
|
||||
void ClientCommandManager::handleMpCommand()
|
||||
{
|
||||
if(const CGHeroInstance* h = LOCPLINT->localState->getCurrentHero())
|
||||
printCommandMessage(std::to_string(h->movement) + "; max: " + std::to_string(h->maxMovePoints(true)) + "/" + std::to_string(h->maxMovePoints(false)) + "\n");
|
||||
printCommandMessage(std::to_string(h->movementPointsRemaining()) + "; max: " + std::to_string(h->movementPointsLimit(true)) + "/" + std::to_string(h->movementPointsLimit(false)) + "\n");
|
||||
}
|
||||
|
||||
void ClientCommandManager::handleSetCommand(std::istringstream& singleWordBuffer)
|
||||
|
@ -277,6 +277,8 @@ void ApplyClientNetPackVisitor::visitMoveArtifact(MoveArtifact & pack)
|
||||
moveArtifact(pack.src.owningPlayer());
|
||||
if(pack.src.owningPlayer() != pack.dst.owningPlayer())
|
||||
moveArtifact(pack.dst.owningPlayer());
|
||||
|
||||
cl.invalidatePaths(); // hero might have equipped/unequipped Angel Wings
|
||||
}
|
||||
|
||||
void ApplyClientNetPackVisitor::visitBulkMoveArtifacts(BulkMoveArtifacts & pack)
|
||||
@ -303,11 +305,15 @@ void ApplyClientNetPackVisitor::visitBulkMoveArtifacts(BulkMoveArtifacts & pack)
|
||||
void ApplyClientNetPackVisitor::visitAssembledArtifact(AssembledArtifact & pack)
|
||||
{
|
||||
callInterfaceIfPresent(cl, pack.al.owningPlayer(), &IGameEventsReceiver::artifactAssembled, pack.al);
|
||||
|
||||
cl.invalidatePaths(); // hero might have equipped/unequipped Angel Wings
|
||||
}
|
||||
|
||||
void ApplyClientNetPackVisitor::visitDisassembledArtifact(DisassembledArtifact & pack)
|
||||
{
|
||||
callInterfaceIfPresent(cl, pack.al.owningPlayer(), &IGameEventsReceiver::artifactDisassembled, pack.al);
|
||||
|
||||
cl.invalidatePaths(); // hero might have equipped/unequipped Angel Wings
|
||||
}
|
||||
|
||||
void ApplyClientNetPackVisitor::visitHeroVisit(HeroVisit & pack)
|
||||
|
@ -124,7 +124,7 @@ const CGHeroInstance * PlayerLocalState::getNextWanderingHero(const CGHeroInstan
|
||||
if (isHeroSleeping(hero))
|
||||
continue;
|
||||
|
||||
if (hero->movement == 0)
|
||||
if (hero->movementPointsRemaining() == 0)
|
||||
continue;
|
||||
|
||||
if (!firstSuitable)
|
||||
|
@ -125,6 +125,12 @@ void AdventureMapInterface::activate()
|
||||
}
|
||||
|
||||
GH.fakeMouseMove(); //to restore the cursor
|
||||
|
||||
// workaround for an edge case:
|
||||
// if player unequips Angel Wings / Boots of Levitation of currently active hero
|
||||
// game will correctly invalidate paths but current route will not be updated since verifyPath() is not called for current hero
|
||||
if (LOCPLINT->makingTurn && LOCPLINT->localState->getCurrentHero())
|
||||
LOCPLINT->localState->verifyPath(LOCPLINT->localState->getCurrentHero());
|
||||
}
|
||||
|
||||
void AdventureMapInterface::deactivate()
|
||||
@ -676,7 +682,7 @@ void AdventureMapInterface::onTileHovered(const int3 &mapPos)
|
||||
|
||||
void AdventureMapInterface::showMoveDetailsInStatusbar(const CGHeroInstance & hero, const CGPathNode & pathNode)
|
||||
{
|
||||
const int maxMovementPointsAtStartOfLastTurn = pathNode.turns > 0 ? hero.maxMovePoints(pathNode.layer == EPathfindingLayer::LAND) : hero.movement;
|
||||
const int maxMovementPointsAtStartOfLastTurn = pathNode.turns > 0 ? hero.movementPointsLimit(pathNode.layer == EPathfindingLayer::LAND) : hero.movementPointsRemaining();
|
||||
const int movementPointsLastTurnCost = maxMovementPointsAtStartOfLastTurn - pathNode.moveRemains;
|
||||
const int remainingPointsAfterMove = pathNode.turns == 0 ? pathNode.moveRemains : 0;
|
||||
|
||||
|
@ -224,7 +224,7 @@ void AdventureMapShortcuts::endTurn()
|
||||
{
|
||||
for(auto hero : LOCPLINT->localState->getWanderingHeroes())
|
||||
{
|
||||
if(!LOCPLINT->localState->isHeroSleeping(hero) && hero->movement > 0)
|
||||
if(!LOCPLINT->localState->isHeroSleeping(hero) && hero->movementPointsRemaining() > 0)
|
||||
{
|
||||
// Only show hero reminder if conditions met:
|
||||
// - There still movement points
|
||||
@ -418,7 +418,7 @@ bool AdventureMapShortcuts::optionHeroSelected()
|
||||
bool AdventureMapShortcuts::optionHeroCanMove()
|
||||
{
|
||||
const auto * hero = LOCPLINT->localState->getCurrentHero();
|
||||
return optionInMapView() && hero && hero->movement != 0 && LOCPLINT->localState->hasPath(hero);
|
||||
return optionInMapView() && hero && hero->movementPointsRemaining() != 0 && LOCPLINT->localState->hasPath(hero);
|
||||
}
|
||||
|
||||
bool AdventureMapShortcuts::optionHasNextHero()
|
||||
|
@ -234,7 +234,7 @@ CHeroList::CHeroItem::CHeroItem(CHeroList *parent, const CGHeroInstance * Hero)
|
||||
|
||||
void CHeroList::CHeroItem::update()
|
||||
{
|
||||
movement->setFrame(std::min<size_t>(movement->size()-1, hero->movement / 100));
|
||||
movement->setFrame(std::min<size_t>(movement->size()-1, hero->movementPointsRemaining() / 100));
|
||||
mana->setFrame(std::min<size_t>(mana->size()-1, hero->mana / 5));
|
||||
redraw();
|
||||
}
|
||||
|
@ -329,6 +329,20 @@
|
||||
"commanders": false
|
||||
},
|
||||
|
||||
"pathfinder" :
|
||||
{
|
||||
// if enabled, pathfinder will take use of any available boats
|
||||
"useBoat" : true,
|
||||
// if enabled, pathfinder will take use of any bidirectional monoliths
|
||||
"useMonolithTwoWay" : true,
|
||||
// if enabled, pathfinder will take use of one-way monolith that only have one known exit
|
||||
"useMonolithOneWayUnique" : false,
|
||||
// if enabled, pathfinder will take use of one-way monoliths with multiple exits.
|
||||
"useMonolithOneWayRandom" : false,
|
||||
// if enabled and hero has whirlpool protection effect, pathfinder will take use of whirpools
|
||||
"useWhirlpool" : true
|
||||
},
|
||||
|
||||
"bonuses" :
|
||||
{
|
||||
"global" :
|
||||
|
@ -640,6 +640,8 @@
|
||||
"shipwreck" : {
|
||||
"index" : 0,
|
||||
"resetDuration" : 0,
|
||||
"blockedVisitable" : true,
|
||||
"coastVisitable" : true,
|
||||
"name" : "Shipwreck",
|
||||
"aiValue" : 2000,
|
||||
"rmg" : {
|
||||
@ -732,6 +734,7 @@
|
||||
"derelictShip" : {
|
||||
"index" : 0,
|
||||
"resetDuration" : 0,
|
||||
"blockedVisitable" : true,
|
||||
"name" : "Derelict Ship",
|
||||
"aiValue" : 4000,
|
||||
"rmg" : {
|
||||
|
@ -3,7 +3,7 @@
|
||||
{
|
||||
"type" : "object",
|
||||
"$schema" : "http://json-schema.org/draft-04/schema",
|
||||
"required" : [ "general", "video", "adventure", "pathfinder", "battle", "server", "logging", "launcher", "gameTweaks" ],
|
||||
"required" : [ "general", "video", "adventure", "battle", "server", "logging", "launcher", "gameTweaks" ],
|
||||
"definitions" : {
|
||||
"logLevelEnum" : {
|
||||
"type" : "string",
|
||||
@ -221,74 +221,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"pathfinder" : {
|
||||
"type" : "object",
|
||||
"additionalProperties" : false,
|
||||
"default" : {},
|
||||
"required" : [ "teleports", "layers", "oneTurnSpecialLayersLimit", "originalMovementRules", "lightweightFlyingMode" ],
|
||||
"properties" : {
|
||||
"layers" : {
|
||||
"type" : "object",
|
||||
"additionalProperties" : false,
|
||||
"default" : {},
|
||||
"required" : [ "sailing", "waterWalking", "flying" ],
|
||||
"properties" : {
|
||||
"sailing" : {
|
||||
"type" : "boolean",
|
||||
"default" : true
|
||||
},
|
||||
"waterWalking" : {
|
||||
"type" : "boolean",
|
||||
"default" : true
|
||||
},
|
||||
"flying" : {
|
||||
"type" : "boolean",
|
||||
"default" : true
|
||||
}
|
||||
}
|
||||
},
|
||||
"teleports" : {
|
||||
"type" : "object",
|
||||
"additionalProperties" : false,
|
||||
"default" : {},
|
||||
"required" : [ "twoWay", "oneWay", "oneWayRandom", "whirlpool", "castleGate" ],
|
||||
"properties" : {
|
||||
"twoWay" : {
|
||||
"type" : "boolean",
|
||||
"default" : true
|
||||
},
|
||||
"oneWay" : {
|
||||
"type" : "boolean",
|
||||
"default" : true
|
||||
},
|
||||
"oneWayRandom" : {
|
||||
"type" : "boolean",
|
||||
"default" : false
|
||||
},
|
||||
"whirlpool" : {
|
||||
"type" : "boolean",
|
||||
"default" : true
|
||||
},
|
||||
"castleGate" : {
|
||||
"type" : "boolean",
|
||||
"default" : false
|
||||
}
|
||||
}
|
||||
},
|
||||
"oneTurnSpecialLayersLimit" : {
|
||||
"type" : "boolean",
|
||||
"default" : true
|
||||
},
|
||||
"originalMovementRules" : {
|
||||
"type" : "boolean",
|
||||
"default" : false
|
||||
},
|
||||
"lightweightFlyingMode" : {
|
||||
"type" : "boolean",
|
||||
"default" : false
|
||||
}
|
||||
}
|
||||
},
|
||||
"battle" : {
|
||||
"type" : "object",
|
||||
"additionalProperties" : false,
|
||||
|
@ -1900,7 +1900,7 @@ std::vector<CGObjectInstance*> CGameState::guardingCreatures (int3 pos) const
|
||||
{
|
||||
for (CGObjectInstance* obj : posTile.visitableObjects)
|
||||
{
|
||||
if(obj->blockVisit)
|
||||
if(obj->isBlockedVisitable())
|
||||
{
|
||||
if (obj->ID == Obj::MONSTER) // Monster
|
||||
guards.push_back(obj);
|
||||
|
@ -33,6 +33,8 @@ std::vector<int> IGameSettings::getVector(EGameSettings option) const
|
||||
return getValue(option).convertTo<std::vector<int>>();
|
||||
}
|
||||
|
||||
GameSettings::~GameSettings() = default;
|
||||
|
||||
GameSettings::GameSettings()
|
||||
: gameSettings(static_cast<size_t>(EGameSettings::OPTIONS_COUNT))
|
||||
{
|
||||
@ -91,6 +93,11 @@ void GameSettings::load(const JsonNode & input)
|
||||
{EGameSettings::TEXTS_ROAD, "textData", "road" },
|
||||
{EGameSettings::TEXTS_SPELL, "textData", "spell" },
|
||||
{EGameSettings::TEXTS_TERRAIN, "textData", "terrain" },
|
||||
{EGameSettings::PATHFINDER_USE_BOAT, "pathfinder", "useBoat" },
|
||||
{EGameSettings::PATHFINDER_USE_MONOLITH_TWO_WAY, "pathfinder", "useMonolithTwoWay" },
|
||||
{EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE, "pathfinder", "useMonolithOneWayUnique" },
|
||||
{EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_RANDOM, "pathfinder", "useMonolithOneWayRandom" },
|
||||
{EGameSettings::PATHFINDER_USE_WHIRLPOOL, "pathfinder", "useWhirlpool" },
|
||||
{EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP, "towns", "buildingsPerTurnCap" },
|
||||
{EGameSettings::TOWNS_STARTING_DWELLING_CHANCES, "towns", "startingDwellingChances" },
|
||||
};
|
||||
|
@ -57,6 +57,11 @@ enum class EGameSettings
|
||||
MAP_FORMAT_HORN_OF_THE_ABYSS,
|
||||
MAP_FORMAT_JSON_VCMI,
|
||||
MAP_FORMAT_IN_THE_WAKE_OF_GODS,
|
||||
PATHFINDER_USE_BOAT,
|
||||
PATHFINDER_USE_MONOLITH_TWO_WAY,
|
||||
PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE,
|
||||
PATHFINDER_USE_MONOLITH_ONE_WAY_RANDOM,
|
||||
PATHFINDER_USE_WHIRLPOOL,
|
||||
TOWNS_BUILDINGS_PER_TURN_CAP,
|
||||
TOWNS_STARTING_DWELLING_CHANCES,
|
||||
COMBAT_ONE_HEX_TRIGGERS_OBSTACLES,
|
||||
@ -68,6 +73,7 @@ class DLL_LINKAGE IGameSettings
|
||||
{
|
||||
public:
|
||||
virtual const JsonNode & getValue(EGameSettings option) const = 0;
|
||||
virtual ~IGameSettings() = default;
|
||||
|
||||
bool getBoolean(EGameSettings option) const;
|
||||
int64_t getInteger(EGameSettings option) const;
|
||||
@ -75,12 +81,14 @@ public:
|
||||
std::vector<int> getVector(EGameSettings option) const;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE GameSettings final : public IGameSettings
|
||||
class DLL_LINKAGE GameSettings final : public IGameSettings, boost::noncopyable
|
||||
{
|
||||
std::vector<JsonNode> gameSettings;
|
||||
|
||||
public:
|
||||
GameSettings();
|
||||
~GameSettings();
|
||||
|
||||
void load(const JsonNode & input);
|
||||
const JsonNode & getValue(EGameSettings option) const override;
|
||||
|
||||
|
@ -903,11 +903,9 @@ void SetMovePoints::applyGs(CGameState * gs) const
|
||||
assert(hero);
|
||||
|
||||
if(absolute)
|
||||
hero->movement = val;
|
||||
hero->setMovementPoints(val);
|
||||
else
|
||||
hero->movement += val;
|
||||
|
||||
vstd::amax(hero->movement, 0); //not less than 0
|
||||
hero->setMovementPoints(hero->movementPointsRemaining() + val);
|
||||
}
|
||||
|
||||
void FoWChange::applyGs(CGameState *gs)
|
||||
@ -1276,7 +1274,7 @@ void TryMoveHero::applyGs(CGameState *gs)
|
||||
return;
|
||||
}
|
||||
|
||||
h->movement = movePoints;
|
||||
h->setMovementPoints(movePoints);
|
||||
|
||||
if((result == SUCCESS || result == BLOCKING_VISIT || result == EMBARK || result == DISEMBARK) && start != end)
|
||||
{
|
||||
@ -1422,11 +1420,11 @@ void HeroRecruited::applyGs(CGameState * gs) const
|
||||
{ // this is a fresh hero who hasn't appeared yet
|
||||
if (boatId >= 0) //Hero spawns on water
|
||||
{
|
||||
h->movement = h->maxMovePoints(false);
|
||||
h->setMovementPoints(h->movementPointsLimit(false));
|
||||
}
|
||||
else
|
||||
{
|
||||
h->movement = h->maxMovePoints(true);
|
||||
h->setMovementPoints(h->movementPointsLimit(true));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1479,7 +1477,7 @@ void GiveHero::applyGs(CGameState * gs) const
|
||||
h->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, h->type->heroClass->getIndex())->getTemplates().front();
|
||||
|
||||
h->setOwner(player);
|
||||
h->movement = h->maxMovePoints(true);
|
||||
h->setMovementPoints(h->movementPointsLimit(true));
|
||||
h->pos = h->convertFromVisitablePos(oldVisitablePos);
|
||||
gs->map->heroesOnMap.emplace_back(h);
|
||||
gs->getPlayerState(h->getOwner())->heroes.emplace_back(h);
|
||||
@ -2052,7 +2050,7 @@ void NewTurn::applyGs(CGameState *gs)
|
||||
logGlobal->error("Hero %d not found in NewTurn::applyGs", h.id.getNum());
|
||||
continue;
|
||||
}
|
||||
hero->movement = h.move;
|
||||
hero->setMovementPoints(h.move);
|
||||
hero->mana = h.mana;
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,8 @@ void CBankInstanceConstructor::initTypeData(const JsonNode & input)
|
||||
|
||||
levels = input["levels"].Vector();
|
||||
bankResetDuration = static_cast<si32>(input["resetDuration"].Float());
|
||||
blockVisit = input["blockedVisitable"].Bool();
|
||||
coastVisitable = input["coastVisitable"].Bool();
|
||||
}
|
||||
|
||||
BankConfig CBankInstanceConstructor::generateConfig(const JsonNode & level, CRandomGenerator & rng) const
|
||||
@ -58,6 +60,8 @@ BankConfig CBankInstanceConstructor::generateConfig(const JsonNode & level, CRan
|
||||
void CBankInstanceConstructor::randomizeObject(CBank * bank, CRandomGenerator & rng) const
|
||||
{
|
||||
bank->resetDuration = bankResetDuration;
|
||||
bank->blockVisit = blockVisit;
|
||||
bank->coastVisitable = coastVisitable;
|
||||
|
||||
si32 totalChance = 0;
|
||||
for(const auto & node : levels)
|
||||
|
@ -80,12 +80,18 @@ class CBankInstanceConstructor : public CDefaultObjectTypeHandler<CBank>
|
||||
BankConfig generateConfig(const JsonNode & conf, CRandomGenerator & rng) const;
|
||||
|
||||
JsonVector levels;
|
||||
|
||||
// all banks of this type will be reset N days after clearing,
|
||||
si32 bankResetDuration = 0;
|
||||
|
||||
// bank is only visitable from adjacent tile
|
||||
bool blockVisit;
|
||||
// bank is visitable from land even when bank is on water tile
|
||||
bool coastVisitable;
|
||||
protected:
|
||||
void initTypeData(const JsonNode & input) override;
|
||||
|
||||
public:
|
||||
// all banks of this type will be reset N days after clearing,
|
||||
si32 bankResetDuration = 0;
|
||||
|
||||
void randomizeObject(CBank * object, CRandomGenerator & rng) const override;
|
||||
|
||||
@ -97,6 +103,8 @@ public:
|
||||
{
|
||||
h & levels;
|
||||
h & bankResetDuration;
|
||||
h & blockVisit;
|
||||
h & coastVisitable;
|
||||
h & static_cast<CDefaultObjectTypeHandler<CBank>&>(*this);
|
||||
}
|
||||
};
|
||||
|
@ -43,6 +43,11 @@ void CBank::initObj(CRandomGenerator & rand)
|
||||
VLC->objtypeh->getHandlerFor(ID, subID)->configureObject(this, rand);
|
||||
}
|
||||
|
||||
bool CBank::isCoastVisitable() const
|
||||
{
|
||||
return coastVisitable;
|
||||
}
|
||||
|
||||
std::string CBank::getHoverText(PlayerColor player) const
|
||||
{
|
||||
// TODO: record visited players
|
||||
|
@ -21,6 +21,7 @@ class DLL_LINKAGE CBank : public CArmedInstance
|
||||
std::unique_ptr<BankConfig> bc;
|
||||
ui32 daycounter;
|
||||
ui32 resetDuration;
|
||||
bool coastVisitable;
|
||||
|
||||
void setPropertyDer(ui8 what, ui32 val) override;
|
||||
void doVisit(const CGHeroInstance * hero) const;
|
||||
@ -35,6 +36,7 @@ public:
|
||||
std::string getHoverText(PlayerColor player) const override;
|
||||
void newTurn(CRandomGenerator & rand) const override;
|
||||
bool wasVisited (PlayerColor player) const override;
|
||||
bool isCoastVisitable() const override;
|
||||
void onHeroVisit(const CGHeroInstance * h) const override;
|
||||
void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
|
||||
void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
|
||||
@ -45,6 +47,7 @@ public:
|
||||
h & daycounter;
|
||||
h & bc;
|
||||
h & resetDuration;
|
||||
h & coastVisitable;
|
||||
}
|
||||
|
||||
friend class CBankInstanceConstructor;
|
||||
|
@ -65,7 +65,7 @@ static int lowestSpeed(const CGHeroInstance * chi)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ui32 CGHeroInstance::getTileCost(const TerrainTile & dest, const TerrainTile & from, const TurnInfo * ti) const
|
||||
ui32 CGHeroInstance::getTileMovementCost(const TerrainTile & dest, const TerrainTile & from, const TurnInfo * ti) const
|
||||
{
|
||||
int64_t ret = GameConstants::BASE_MOVEMENT_COST;
|
||||
|
||||
@ -122,6 +122,11 @@ TerrainId CGHeroInstance::getNativeTerrain() const
|
||||
return nativeTerrain;
|
||||
}
|
||||
|
||||
bool CGHeroInstance::isCoastVisitable() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
BattleField CGHeroInstance::getBattlefield() const
|
||||
{
|
||||
return BattleField::NONE;
|
||||
@ -196,10 +201,20 @@ bool CGHeroInstance::canLearnSkill(const SecondarySkill & which) const
|
||||
return true;
|
||||
}
|
||||
|
||||
int CGHeroInstance::maxMovePoints(bool onLand) const
|
||||
int CGHeroInstance::movementPointsRemaining() const
|
||||
{
|
||||
return movement;
|
||||
}
|
||||
|
||||
void CGHeroInstance::setMovementPoints(int points)
|
||||
{
|
||||
movement = std::max(0, points);
|
||||
}
|
||||
|
||||
int CGHeroInstance::movementPointsLimit(bool onLand) const
|
||||
{
|
||||
TurnInfo ti(this);
|
||||
return maxMovePointsCached(onLand, &ti);
|
||||
return movementPointsLimitCached(onLand, &ti);
|
||||
}
|
||||
|
||||
int CGHeroInstance::getLowestCreatureSpeed() const
|
||||
@ -219,7 +234,7 @@ void CGHeroInstance::updateArmyMovementBonus(bool onLand, const TurnInfo * ti) c
|
||||
}
|
||||
}
|
||||
|
||||
int CGHeroInstance::maxMovePointsCached(bool onLand, const TurnInfo * ti) const
|
||||
int CGHeroInstance::movementPointsLimitCached(bool onLand, const TurnInfo * ti) const
|
||||
{
|
||||
updateArmyMovementBonus(onLand, ti);
|
||||
return ti->valOfBonuses(BonusType::MOVEMENT, !!onLand);
|
||||
@ -449,14 +464,14 @@ void CGHeroInstance::onHeroVisit(const CGHeroInstance * h) const
|
||||
const auto boatPos = visitablePos();
|
||||
if (cb->gameState()->map->getTile(boatPos).isWater())
|
||||
{
|
||||
smp.val = maxMovePoints(false);
|
||||
smp.val = movementPointsLimit(false);
|
||||
//Create a new boat for hero
|
||||
cb->createObject(boatPos, Obj::BOAT, getBoatType().getNum());
|
||||
boatId = cb->getTopObj(boatPos)->id;
|
||||
}
|
||||
else
|
||||
{
|
||||
smp.val = maxMovePoints(true);
|
||||
smp.val = movementPointsLimit(true);
|
||||
}
|
||||
cb->giveHero(id, h->tempOwner, boatId); //recreates def and adds hero to player
|
||||
cb->setObjProperty(id, ObjProperty::ID, Obj::HERO); //set ID to 34 AFTER hero gets correct flag color
|
||||
@ -1165,7 +1180,7 @@ int CGHeroInstance::movementPointsAfterEmbark(int MPsBefore, int basicCost, bool
|
||||
|
||||
EDiggingStatus CGHeroInstance::diggingStatus() const
|
||||
{
|
||||
if(static_cast<int>(movement) < maxMovePoints(true))
|
||||
if(static_cast<int>(movement) < movementPointsLimit(true))
|
||||
return EDiggingStatus::LACK_OF_MOVEMENT;
|
||||
if(!VLC->arth->objects[ArtifactID::GRAIL]->canBePutAt(this))
|
||||
return EDiggingStatus::BACKPACK_IS_FULL;
|
||||
|
@ -49,6 +49,7 @@ class DLL_LINKAGE CGHeroInstance : public CArmedInstance, public IBoatGenerator,
|
||||
private:
|
||||
std::set<SpellID> spells; //known spells (spell IDs)
|
||||
mutable int lowestCreatureSpeed;
|
||||
ui32 movement; //remaining movement points
|
||||
|
||||
public:
|
||||
|
||||
@ -67,7 +68,6 @@ public:
|
||||
si32 portrait; //may be custom
|
||||
si32 mana; // remaining spell points
|
||||
std::vector<std::pair<SecondarySkill,ui8> > secSkills; //first - ID of skill, second - level of skill (1 - basic, 2 - adv., 3 - expert); if hero has ability (-1, -1) it meansthat it should have default secondary abilities
|
||||
ui32 movement; //remaining movement points
|
||||
EHeroGender gender;
|
||||
|
||||
std::string nameCustom;
|
||||
@ -155,7 +155,6 @@ public:
|
||||
EAlignment getAlignment() const;
|
||||
bool needsLastStack()const override;
|
||||
|
||||
ui32 getTileCost(const TerrainTile & dest, const TerrainTile & from, const TurnInfo * ti) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling
|
||||
//INativeTerrainProvider
|
||||
FactionID getFaction() const override;
|
||||
TerrainId getNativeTerrain() const override;
|
||||
@ -196,9 +195,14 @@ public:
|
||||
void setSecSkillLevel(const SecondarySkill & which, int val, bool abs); // abs == 0 - changes by value; 1 - sets to value
|
||||
void levelUp(const std::vector<SecondarySkill> & skills);
|
||||
|
||||
int maxMovePoints(bool onLand) const;
|
||||
/// returns base movement cost for movement between specific tiles. Does not accounts for diagonal movement or last tile exception
|
||||
ui32 getTileMovementCost(const TerrainTile & dest, const TerrainTile & from, const TurnInfo * ti) const;
|
||||
|
||||
void setMovementPoints(int points);
|
||||
int movementPointsRemaining() const;
|
||||
int movementPointsLimit(bool onLand) const;
|
||||
//cached version is much faster, TurnInfo construction is costly
|
||||
int maxMovePointsCached(bool onLand, const TurnInfo * ti) const;
|
||||
int movementPointsLimitCached(bool onLand, const TurnInfo * ti) const;
|
||||
//update army movement bonus
|
||||
void updateArmyMovementBonus(bool onLand, const TurnInfo * ti) const;
|
||||
|
||||
@ -286,6 +290,7 @@ public:
|
||||
|
||||
void updateFrom(const JsonNode & data) override;
|
||||
|
||||
bool isCoastVisitable() const override;
|
||||
BattleField getBattlefield() const override;
|
||||
protected:
|
||||
void setPropertyDer(ui8 what, ui32 val) override;//synchr
|
||||
|
@ -282,6 +282,16 @@ bool CGObjectInstance::isVisitable() const
|
||||
return appearance->isVisitable();
|
||||
}
|
||||
|
||||
bool CGObjectInstance::isBlockedVisitable() const
|
||||
{
|
||||
return blockVisit;
|
||||
}
|
||||
|
||||
bool CGObjectInstance::isCoastVisitable() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CGObjectInstance::passableFor(PlayerColor color) const
|
||||
{
|
||||
return false;
|
||||
|
@ -34,8 +34,6 @@ public:
|
||||
ObjectInstanceID id;
|
||||
/// Defines appearance of object on map (animation, blocked tiles, blit order, etc)
|
||||
std::shared_ptr<const ObjectTemplate> appearance;
|
||||
/// If true hero can visit this object only from neighbouring tiles and can't stand on this object
|
||||
bool blockVisit;
|
||||
|
||||
std::string instanceName;
|
||||
std::string typeName;
|
||||
@ -49,6 +47,8 @@ public:
|
||||
|
||||
/// "center" tile from which the sight distance is calculated
|
||||
int3 getSightCenter() const;
|
||||
/// If true hero can visit this object only from neighbouring tiles and can't stand on this object
|
||||
bool blockVisit;
|
||||
|
||||
PlayerColor getOwner() const override
|
||||
{
|
||||
@ -68,7 +68,15 @@ public:
|
||||
bool coveringAt(int x, int y) const; //returns true if object covers with picture location (x, y) (h3m pos)
|
||||
std::set<int3> getBlockedPos() const; //returns set of positions blocked by this object
|
||||
std::set<int3> getBlockedOffsets() const; //returns set of relative positions blocked by this object
|
||||
bool isVisitable() const; //returns true if object is visitable
|
||||
|
||||
/// returns true if object is visitable
|
||||
bool isVisitable() const;
|
||||
|
||||
/// If true hero can visit this object only from neighbouring tiles and can't stand on this object
|
||||
virtual bool isBlockedVisitable() const;
|
||||
|
||||
/// If true this object can be visited by hero standing on the coast
|
||||
virtual bool isCoastVisitable() const;
|
||||
|
||||
virtual BattleField getBattlefield() const;
|
||||
|
||||
|
@ -246,7 +246,6 @@ public:
|
||||
{
|
||||
h & static_cast<IQuestObject&>(*this);
|
||||
h & static_cast<CGObjectInstance&>(*this);
|
||||
h & blockVisit;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1294,6 +1294,11 @@ CGBoat::CGBoat()
|
||||
layer = EPathfindingLayer::EEPathfindingLayer::SAIL;
|
||||
}
|
||||
|
||||
bool CGBoat::isCoastVisitable() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void CGSirens::initObj(CRandomGenerator & rand)
|
||||
{
|
||||
blockVisit = true;
|
||||
|
@ -359,6 +359,7 @@ public:
|
||||
std::array<std::string, PlayerColor::PLAYER_LIMIT_I> flagAnimations;
|
||||
|
||||
CGBoat();
|
||||
bool isCoastVisitable() const override;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
|
@ -313,7 +313,7 @@ int3 CMap::guardingCreaturePosition (int3 pos) const
|
||||
{
|
||||
for (CGObjectInstance* obj : posTile.visitableObjects)
|
||||
{
|
||||
if(obj->blockVisit)
|
||||
if(obj->isBlockedVisitable())
|
||||
{
|
||||
if (obj->ID == Obj::MONSTER) // Monster
|
||||
return pos;
|
||||
|
@ -63,7 +63,7 @@ struct DLL_LINKAGE CGPathNode
|
||||
CGPathNode * theNodeBefore;
|
||||
int3 coord; //coordinates
|
||||
ELayer layer;
|
||||
ui32 moveRemains; //remaining movement points after hero reaches the tile
|
||||
int moveRemains; //remaining movement points after hero reaches the tile
|
||||
ui8 turns; //how many turns we have to wait before reaching the tile - 0 means current turn
|
||||
|
||||
EPathAccessibility accessible;
|
||||
|
@ -621,7 +621,7 @@ int CPathfinderHelper::getMovementCost(
|
||||
|
||||
bool isAirLayer = (hero->boat && hero->boat->layer == EPathfindingLayer::AIR) || ti->hasBonusOfType(BonusType::FLYING_MOVEMENT);
|
||||
|
||||
int ret = hero->getTileCost(*dt, *ct, ti);
|
||||
int ret = hero->getTileMovementCost(*dt, *ct, ti);
|
||||
if(isSailLayer)
|
||||
{
|
||||
if(ct->hasFavorableWinds())
|
||||
|
@ -123,7 +123,7 @@ std::vector<CGPathNode *> NodeStorage::getInitialNodes()
|
||||
auto * initialNode = getNode(out.hpos, out.hero->boat ? out.hero->boat->layer : EPathfindingLayer::LAND);
|
||||
|
||||
initialNode->turns = 0;
|
||||
initialNode->moveRemains = out.hero->movement;
|
||||
initialNode->moveRemains = out.hero->movementPointsRemaining();
|
||||
initialNode->setCost(0.0);
|
||||
|
||||
if(!initialNode->coord.valid())
|
||||
|
@ -10,7 +10,8 @@
|
||||
#include "StdInc.h"
|
||||
#include "PathfinderOptions.h"
|
||||
|
||||
#include "../CConfigHandler.h"
|
||||
#include "../GameSettings.h"
|
||||
#include "../VCMI_Lib.h"
|
||||
#include "NodeStorage.h"
|
||||
#include "PathfindingRules.h"
|
||||
#include "CPathfinder.h"
|
||||
@ -18,20 +19,18 @@
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
PathfinderOptions::PathfinderOptions()
|
||||
: useFlying(true)
|
||||
, useWaterWalking(true)
|
||||
, useEmbarkAndDisembark(VLC->settings()->getBoolean(EGameSettings::PATHFINDER_USE_BOAT))
|
||||
, useTeleportTwoWay(VLC->settings()->getBoolean(EGameSettings::PATHFINDER_USE_MONOLITH_TWO_WAY))
|
||||
, useTeleportOneWay(VLC->settings()->getBoolean(EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE))
|
||||
, useTeleportOneWayRandom(VLC->settings()->getBoolean(EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_RANDOM))
|
||||
, useTeleportWhirlpool(VLC->settings()->getBoolean(EGameSettings::PATHFINDER_USE_WHIRLPOOL))
|
||||
, useCastleGate(false)
|
||||
, lightweightFlyingMode(false)
|
||||
, oneTurnSpecialLayersLimit(true)
|
||||
, originalMovementRules(false)
|
||||
{
|
||||
useFlying = settings["pathfinder"]["layers"]["flying"].Bool();
|
||||
useWaterWalking = settings["pathfinder"]["layers"]["waterWalking"].Bool();
|
||||
useEmbarkAndDisembark = settings["pathfinder"]["layers"]["sailing"].Bool();
|
||||
useTeleportTwoWay = settings["pathfinder"]["teleports"]["twoWay"].Bool();
|
||||
useTeleportOneWay = settings["pathfinder"]["teleports"]["oneWay"].Bool();
|
||||
useTeleportOneWayRandom = settings["pathfinder"]["teleports"]["oneWayRandom"].Bool();
|
||||
useTeleportWhirlpool = settings["pathfinder"]["teleports"]["whirlpool"].Bool();
|
||||
|
||||
useCastleGate = settings["pathfinder"]["teleports"]["castleGate"].Bool();
|
||||
|
||||
lightweightFlyingMode = settings["pathfinder"]["lightweightFlyingMode"].Bool();
|
||||
oneTurnSpecialLayersLimit = settings["pathfinder"]["oneTurnSpecialLayersLimit"].Bool();
|
||||
originalMovementRules = settings["pathfinder"]["originalMovementRules"].Bool();
|
||||
}
|
||||
|
||||
PathfinderConfig::PathfinderConfig(std::shared_ptr<INodeStorage> nodeStorage, std::vector<std::shared_ptr<IPathfindingRule>> rules):
|
||||
|
@ -42,7 +42,7 @@ namespace PathfinderUtil
|
||||
{
|
||||
for(const CGObjectInstance * obj : tinfo.visitableObjects)
|
||||
{
|
||||
if(obj->blockVisit)
|
||||
if(obj->isBlockedVisitable())
|
||||
return EPathAccessibility::BLOCKVIS;
|
||||
else if(obj->passableFor(player))
|
||||
return EPathAccessibility::ACCESSIBLE;
|
||||
@ -70,8 +70,7 @@ namespace PathfinderUtil
|
||||
break;
|
||||
|
||||
case ELayer::AIR:
|
||||
if(tinfo.blocked || tinfo.terType->isLand())
|
||||
return EPathAccessibility::FLYABLE;
|
||||
return EPathAccessibility::FLYABLE;
|
||||
|
||||
break;
|
||||
}
|
||||
|
@ -27,43 +27,65 @@ void MovementCostRule::process(
|
||||
const PathfinderConfig * pathfinderConfig,
|
||||
CPathfinderHelper * pathfinderHelper) const
|
||||
{
|
||||
float costAtNextTile = destination.cost;
|
||||
int turnAtNextTile = destination.turn;
|
||||
int moveAtNextTile = destination.movementLeft;
|
||||
int cost = pathfinderHelper->getMovementCost(source, destination, moveAtNextTile);
|
||||
int remains = moveAtNextTile - cost;
|
||||
int sourceLayerMaxMovePoints = pathfinderHelper->getMaxMovePoints(source.node->layer);
|
||||
const float currentCost = destination.cost;
|
||||
const int currentTurnsUsed = destination.turn;
|
||||
const int currentMovePointsLeft = destination.movementLeft;
|
||||
const int sourceLayerMaxMovePoints = pathfinderHelper->getMaxMovePoints(source.node->layer);
|
||||
|
||||
if(remains < 0)
|
||||
int moveCostPoints = pathfinderHelper->getMovementCost(source, destination, currentMovePointsLeft);
|
||||
float destinationCost = currentCost;
|
||||
int destTurnsUsed = currentTurnsUsed;
|
||||
int destMovePointsLeft = currentMovePointsLeft;
|
||||
|
||||
if(currentMovePointsLeft < moveCostPoints)
|
||||
{
|
||||
//occurs rarely, when hero with low movepoints tries to leave the road
|
||||
costAtNextTile += static_cast<float>(moveAtNextTile) / sourceLayerMaxMovePoints;//we spent all points of current turn
|
||||
pathfinderHelper->updateTurnInfo(++turnAtNextTile);
|
||||
// occurs rarely, when hero with low movepoints tries to leave the road
|
||||
// in this case, all remaining movement points from current turn are spent
|
||||
// and actual movement will happen on next turn, spending points from next turn pool
|
||||
|
||||
int destinationLayerMaxMovePoints = pathfinderHelper->getMaxMovePoints(destination.node->layer);
|
||||
destinationCost += static_cast<float>(currentMovePointsLeft) / sourceLayerMaxMovePoints;
|
||||
destTurnsUsed += 1;
|
||||
destMovePointsLeft = sourceLayerMaxMovePoints;
|
||||
|
||||
moveAtNextTile = destinationLayerMaxMovePoints;
|
||||
// update move cost - it might have changed since hero now makes next turn and replenished his pool
|
||||
moveCostPoints = pathfinderHelper->getMovementCost(source, destination, destMovePointsLeft);
|
||||
|
||||
cost = pathfinderHelper->getMovementCost(source, destination, moveAtNextTile); //cost must be updated, movement points changed :(
|
||||
remains = moveAtNextTile - cost;
|
||||
pathfinderHelper->updateTurnInfo(destTurnsUsed);
|
||||
}
|
||||
|
||||
if(destination.action == EPathNodeAction::EMBARK || destination.action == EPathNodeAction::DISEMBARK)
|
||||
{
|
||||
/// FREE_SHIP_BOARDING bonus only remove additional penalty
|
||||
/// land <-> sail transition still cost movement points as normal movement
|
||||
remains = pathfinderHelper->movementPointsAfterEmbark(moveAtNextTile, cost, (destination.action == EPathNodeAction::DISEMBARK));
|
||||
cost = moveAtNextTile - remains;
|
||||
// FREE_SHIP_BOARDING bonus only remove additional penalty
|
||||
// land <-> sail transition still cost movement points as normal movement
|
||||
|
||||
const int movementPointsAfterEmbark = pathfinderHelper->movementPointsAfterEmbark(destMovePointsLeft, moveCostPoints, (destination.action == EPathNodeAction::DISEMBARK));
|
||||
|
||||
const int destinationLayerMaxMovePoints = pathfinderHelper->getMaxMovePoints(destination.node->layer);
|
||||
const float costBeforeConversion = static_cast<float>(destMovePointsLeft) / sourceLayerMaxMovePoints;
|
||||
const float costAfterConversion = static_cast<float>(movementPointsAfterEmbark) / destinationLayerMaxMovePoints;
|
||||
const float costDelta = costBeforeConversion - costAfterConversion;
|
||||
|
||||
assert(costDelta >= 0);
|
||||
destMovePointsLeft = movementPointsAfterEmbark;
|
||||
destinationCost += costDelta;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Standard movement
|
||||
assert(destMovePointsLeft >= moveCostPoints);
|
||||
destMovePointsLeft -= moveCostPoints;
|
||||
destinationCost += static_cast<float>(moveCostPoints) / sourceLayerMaxMovePoints;
|
||||
}
|
||||
|
||||
costAtNextTile += static_cast<float>(cost) / sourceLayerMaxMovePoints;
|
||||
// pathfinder / priority queue does not supports negative costs
|
||||
assert(destinationCost >= currentCost);
|
||||
|
||||
destination.cost = costAtNextTile;
|
||||
destination.turn = turnAtNextTile;
|
||||
destination.movementLeft = remains;
|
||||
destination.cost = destinationCost;
|
||||
destination.turn = destTurnsUsed;
|
||||
destination.movementLeft = destMovePointsLeft;
|
||||
|
||||
if(destination.isBetterWay() &&
|
||||
((source.node->turns == turnAtNextTile && remains) || pathfinderHelper->passOneTurnLimitCheck(source)))
|
||||
((source.node->turns == destTurnsUsed && destMovePointsLeft) || pathfinderHelper->passOneTurnLimitCheck(source)))
|
||||
{
|
||||
pathfinderConfig->nodeStorage->commit(destination, source);
|
||||
|
||||
@ -157,7 +179,7 @@ void DestinationActionRule::process(
|
||||
}
|
||||
else if(destination.isGuardianTile)
|
||||
action = EPathNodeAction::BATTLE;
|
||||
else if(destination.nodeObject->blockVisit && !(pathfinderConfig->options.useCastleGate && destination.nodeObject->ID == Obj::TOWN))
|
||||
else if(destination.nodeObject->isBlockedVisitable() && !(pathfinderConfig->options.useCastleGate && destination.nodeObject->ID == Obj::TOWN))
|
||||
action = EPathNodeAction::BLOCKING_VISIT;
|
||||
|
||||
if(action == EPathNodeAction::NORMAL)
|
||||
@ -301,7 +323,7 @@ PathfinderBlockingRule::BlockingReason MovementToDestinationRule::getBlockingRea
|
||||
if(!destination.isNodeObjectVisitable())
|
||||
return BlockingReason::DESTINATION_BLOCKED;
|
||||
|
||||
if(destination.nodeObject->ID != Obj::BOAT && !destination.nodeHero)
|
||||
if(!destination.nodeHero && !destination.nodeObject->isCoastVisitable())
|
||||
return BlockingReason::DESTINATION_BLOCKED;
|
||||
}
|
||||
else if(destination.isNodeObjectVisitable() && destination.nodeObject->ID == Obj::BOAT)
|
||||
|
@ -107,9 +107,9 @@ int TurnInfo::valOfBonuses(BonusType type, int subtype) const
|
||||
int TurnInfo::getMaxMovePoints(const EPathfindingLayer & layer) const
|
||||
{
|
||||
if(maxMovePointsLand == -1)
|
||||
maxMovePointsLand = hero->maxMovePointsCached(true, this);
|
||||
maxMovePointsLand = hero->movementPointsLimitCached(true, this);
|
||||
if(maxMovePointsWater == -1)
|
||||
maxMovePointsWater = hero->maxMovePointsCached(false, this);
|
||||
maxMovePointsWater = hero->movementPointsLimitCached(false, this);
|
||||
|
||||
return layer == EPathfindingLayer::SAIL ? maxMovePointsWater : maxMovePointsLand;
|
||||
}
|
||||
|
@ -80,10 +80,10 @@ void Rewardable::Interface::grantRewardAfterLevelup(IGameCallback * cb, const Re
|
||||
{
|
||||
SetMovePoints smp;
|
||||
smp.hid = hero->id;
|
||||
smp.val = hero->movement;
|
||||
smp.val = hero->movementPointsRemaining();
|
||||
|
||||
if (info.reward.movePercentage >= 0) // percent from max
|
||||
smp.val = hero->maxMovePoints(hero->boat && hero->boat->layer == EPathfindingLayer::SAIL) * info.reward.movePercentage / 100;
|
||||
smp.val = hero->movementPointsLimit(hero->boat && hero->boat->layer == EPathfindingLayer::SAIL) * info.reward.movePercentage / 100;
|
||||
smp.val = std::max<si32>(0, smp.val + info.reward.movePoints);
|
||||
|
||||
cb->setMovePoints(&smp);
|
||||
|
@ -297,7 +297,7 @@ ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(SpellCastEnvironm
|
||||
return ESpellCastResult::ERROR;
|
||||
}
|
||||
|
||||
if(parameters.caster->getHeroCaster()->movement <= 0) //unlike town portal non-zero MP is enough
|
||||
if(parameters.caster->getHeroCaster()->movementPointsRemaining() <= 0) //unlike town portal non-zero MP is enough
|
||||
{
|
||||
env->complain("Hero needs movement points to cast Dimension Door!");
|
||||
return ESpellCastResult::ERROR;
|
||||
@ -335,8 +335,8 @@ ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(SpellCastEnvironm
|
||||
{
|
||||
SetMovePoints smp;
|
||||
smp.hid = ObjectInstanceID(parameters.caster->getCasterUnitId());
|
||||
if(movementCost < static_cast<int>(parameters.caster->getHeroCaster()->movement))
|
||||
smp.val = parameters.caster->getHeroCaster()->movement - movementCost;
|
||||
if(movementCost < static_cast<int>(parameters.caster->getHeroCaster()->movementPointsRemaining()))
|
||||
smp.val = parameters.caster->getHeroCaster()->movementPointsRemaining() - movementCost;
|
||||
else
|
||||
smp.val = 0;
|
||||
env->apply(&smp);
|
||||
@ -369,7 +369,7 @@ ESpellCastResult TownPortalMechanics::applyAdventureEffects(SpellCastEnvironment
|
||||
if(nullptr == destination)
|
||||
return ESpellCastResult::ERROR;
|
||||
|
||||
if(static_cast<int>(parameters.caster->getHeroCaster()->movement) < moveCost)
|
||||
if(static_cast<int>(parameters.caster->getHeroCaster()->movementPointsRemaining()) < moveCost)
|
||||
return ESpellCastResult::ERROR;
|
||||
|
||||
if(destination->visitingHero)
|
||||
@ -419,7 +419,7 @@ ESpellCastResult TownPortalMechanics::applyAdventureEffects(SpellCastEnvironment
|
||||
return ESpellCastResult::ERROR;
|
||||
}
|
||||
|
||||
if(static_cast<int>(parameters.caster->getHeroCaster()->movement) < moveCost)
|
||||
if(static_cast<int>(parameters.caster->getHeroCaster()->movementPointsRemaining()) < moveCost)
|
||||
{
|
||||
env->complain("This hero has not enough movement points!");
|
||||
return ESpellCastResult::ERROR;
|
||||
@ -441,7 +441,7 @@ ESpellCastResult TownPortalMechanics::applyAdventureEffects(SpellCastEnvironment
|
||||
{
|
||||
SetMovePoints smp;
|
||||
smp.hid = ObjectInstanceID(parameters.caster->getCasterUnitId());
|
||||
smp.val = std::max<ui32>(0, parameters.caster->getHeroCaster()->movement - moveCost);
|
||||
smp.val = std::max<ui32>(0, parameters.caster->getHeroCaster()->movementPointsRemaining() - moveCost);
|
||||
env->apply(&smp);
|
||||
}
|
||||
return ESpellCastResult::OK;
|
||||
@ -468,7 +468,7 @@ ESpellCastResult TownPortalMechanics::beginCast(SpellCastEnvironment * env, cons
|
||||
|
||||
const int moveCost = movementCost(parameters);
|
||||
|
||||
if(static_cast<int>(parameters.caster->getHeroCaster()->movement) < moveCost)
|
||||
if(static_cast<int>(parameters.caster->getHeroCaster()->movementPointsRemaining()) < moveCost)
|
||||
{
|
||||
InfoWindow iw;
|
||||
iw.player = parameters.caster->getCasterOwner();
|
||||
|
@ -1773,9 +1773,9 @@ void CGameHandler::newTurn()
|
||||
if (hero->isInitialized() && hero->stacks.size())
|
||||
{
|
||||
// reset retreated or surrendered heroes
|
||||
auto maxmove = hero->maxMovePoints(true);
|
||||
auto maxmove = hero->movementPointsLimit(true);
|
||||
// if movement is greater than maxmove, we should decrease it
|
||||
if (hero->movement != maxmove || hero->mana < hero->manaLimit())
|
||||
if (hero->movementPointsRemaining() != maxmove || hero->mana < hero->manaLimit())
|
||||
{
|
||||
NewTurn::Hero hth;
|
||||
hth.id = hero->id;
|
||||
@ -1864,7 +1864,7 @@ void CGameHandler::newTurn()
|
||||
hth.id = h->id;
|
||||
auto ti = std::make_unique<TurnInfo>(h, 1);
|
||||
// TODO: this code executed when bonuses of previous day not yet updated (this happen in NewTurn::applyGs). See issue 2356
|
||||
hth.move = h->maxMovePointsCached(gs->map->getTile(h->visitablePos()).terType->isLand(), ti.get());
|
||||
hth.move = h->movementPointsLimitCached(gs->map->getTile(h->visitablePos()).terType->isLand(), ti.get());
|
||||
hth.mana = h->getManaNewTurn();
|
||||
|
||||
n.heroes.insert(hth);
|
||||
@ -2280,7 +2280,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
|
||||
tmh.start = h->pos;
|
||||
tmh.end = dst;
|
||||
tmh.result = TryMoveHero::FAILED;
|
||||
tmh.movePoints = h->movement;
|
||||
tmh.movePoints = h->movementPointsRemaining();
|
||||
|
||||
//check if destination tile is available
|
||||
auto pathfinderHelper = std::make_unique<CPathfinderHelper>(gs, h, PathfinderOptions());
|
||||
@ -2288,13 +2288,13 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
|
||||
|
||||
const bool canFly = pathfinderHelper->hasBonusOfType(BonusType::FLYING_MOVEMENT) || (h->boat && h->boat->layer == EPathfindingLayer::AIR);
|
||||
const bool canWalkOnSea = pathfinderHelper->hasBonusOfType(BonusType::WATER_WALKING) || (h->boat && h->boat->layer == EPathfindingLayer::WATER);
|
||||
const int cost = pathfinderHelper->getMovementCost(h->visitablePos(), hmpos, nullptr, nullptr, h->movement);
|
||||
const int cost = pathfinderHelper->getMovementCost(h->visitablePos(), hmpos, nullptr, nullptr, h->movementPointsRemaining());
|
||||
|
||||
//it's a rock or blocked and not visitable tile
|
||||
//OR hero is on land and dest is water and (there is not present only one object - boat)
|
||||
if (((!t.terType->isPassable() || (t.blocked && !t.visitable && !canFly))
|
||||
&& complain("Cannot move hero, destination tile is blocked!"))
|
||||
|| ((!h->boat && !canWalkOnSea && !canFly && t.terType->isWater() && (t.visitableObjects.size() < 1 || (t.visitableObjects.back()->ID != Obj::BOAT && t.visitableObjects.back()->ID != Obj::HERO))) //hero is not on boat/water walking and dst water tile doesn't contain boat/hero (objs visitable from land) -> we test back cause boat may be on top of another object (#276)
|
||||
|| ((!h->boat && !canWalkOnSea && !canFly && t.terType->isWater() && (t.visitableObjects.size() < 1 || !t.visitableObjects.back()->isCoastVisitable())) //hero is not on boat/water walking and dst water tile doesn't contain boat/hero (objs visitable from land) -> we test back cause boat may be on top of another object (#276)
|
||||
&& complain("Cannot move hero, destination tile is on water!"))
|
||||
|| ((h->boat && h->boat->layer == EPathfindingLayer::SAIL && t.terType->isLand() && t.blocked)
|
||||
&& complain("Cannot disembark hero, tile is blocked!"))
|
||||
@ -2302,7 +2302,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
|
||||
&& complain("Tiles are not neighboring!"))
|
||||
|| ((h->inTownGarrison)
|
||||
&& complain("Can not move garrisoned hero!"))
|
||||
|| (((int)h->movement < cost && dst != h->pos && !teleporting)
|
||||
|| (((int)h->movementPointsRemaining() < cost && dst != h->pos && !teleporting)
|
||||
&& complain("Hero doesn't have any movement points left!"))
|
||||
|| ((transit && !canFly && !CGTeleport::isTeleport(t.topVisitableObj()))
|
||||
&& complain("Hero cannot transit over this tile!"))
|
||||
@ -2369,10 +2369,10 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
|
||||
{
|
||||
for (CGObjectInstance *obj : t.visitableObjects)
|
||||
{
|
||||
if(h->boat && !obj->blockVisit && !h->boat->onboardVisitAllowed)
|
||||
if(h->boat && !obj->isBlockedVisitable() && !h->boat->onboardVisitAllowed)
|
||||
return doMove(TryMoveHero::SUCCESS, this->IGNORE_GUARDS, DONT_VISIT_DEST, REMAINING_ON_TILE);
|
||||
|
||||
if (obj != h && obj->blockVisit && !obj->passableFor(h->tempOwner))
|
||||
if (obj != h && obj->isBlockedVisitable() && !obj->passableFor(h->tempOwner))
|
||||
{
|
||||
EVisitDest visitDest = VISIT_DEST;
|
||||
if(h->boat && !h->boat->onboardVisitAllowed)
|
||||
@ -2387,14 +2387,14 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
|
||||
|
||||
if (!transit && embarking)
|
||||
{
|
||||
tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, false, ti);
|
||||
tmh.movePoints = h->movementPointsAfterEmbark(h->movementPointsRemaining(), cost, false, ti);
|
||||
return doMove(TryMoveHero::EMBARK, IGNORE_GUARDS, DONT_VISIT_DEST, LEAVING_TILE);
|
||||
// In H3 embark ignore guards
|
||||
}
|
||||
|
||||
if (disembarking)
|
||||
{
|
||||
tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, true, ti);
|
||||
tmh.movePoints = h->movementPointsAfterEmbark(h->movementPointsRemaining(), cost, true, ti);
|
||||
return doMove(TryMoveHero::DISEMBARK, CHECK_FOR_GUARDS, VISIT_DEST, LEAVING_TILE);
|
||||
}
|
||||
|
||||
@ -2420,8 +2420,8 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
|
||||
|
||||
//still here? it is standard movement!
|
||||
{
|
||||
tmh.movePoints = (int)h->movement >= cost
|
||||
? h->movement - cost
|
||||
tmh.movePoints = (int)h->movementPointsRemaining() >= cost
|
||||
? h->movementPointsRemaining() - cost
|
||||
: 0;
|
||||
|
||||
EGuardLook lookForGuards = CHECK_FOR_GUARDS;
|
||||
|
Loading…
x
Reference in New Issue
Block a user