1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-03-19 21:10:12 +02:00

Rework and optimize turnInfo used by pathfinder

This commit is contained in:
Ivan Savenko 2024-12-28 12:02:27 +00:00
parent 579b64cd39
commit dea1eba20b
16 changed files with 300 additions and 273 deletions

View File

@ -47,11 +47,10 @@ ChainActor::ChainActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t
initialTurn = 0; initialTurn = 0;
armyValue = getHeroArmyStrengthWithCommander(hero, hero); armyValue = getHeroArmyStrengthWithCommander(hero, hero);
heroFightingStrength = hero->getHeroStrength(); heroFightingStrength = hero->getHeroStrength();
tiCache.reset(new TurnInfo(hero));
} }
ChainActor::ChainActor(const ChainActor * carrier, const ChainActor * other, const CCreatureSet * heroArmy) ChainActor::ChainActor(const ChainActor * carrier, const ChainActor * other, const CCreatureSet * heroArmy)
:hero(carrier->hero), tiCache(carrier->tiCache), heroRole(carrier->heroRole), isMovable(true), creatureSet(heroArmy), chainMask(carrier->chainMask | other->chainMask), :hero(carrier->hero), heroRole(carrier->heroRole), isMovable(true), creatureSet(heroArmy), chainMask(carrier->chainMask | other->chainMask),
baseActor(this), carrierParent(carrier), otherParent(other), heroFightingStrength(carrier->heroFightingStrength), baseActor(this), carrierParent(carrier), otherParent(other), heroFightingStrength(carrier->heroFightingStrength),
actorExchangeCount(carrier->actorExchangeCount + other->actorExchangeCount), armyCost(carrier->armyCost + other->armyCost), actorAction() actorExchangeCount(carrier->actorExchangeCount + other->actorExchangeCount), armyCost(carrier->armyCost + other->armyCost), actorAction()
{ {
@ -75,7 +74,7 @@ int ChainActor::maxMovePoints(CGPathNode::ELayer layer)
throw std::logic_error("Asking movement points for static actor"); throw std::logic_error("Asking movement points for static actor");
#endif #endif
return hero->movementPointsLimitCached(layer, tiCache.get()); return hero->movementPointsLimit(layer);
} }
std::string ChainActor::toString() const std::string ChainActor::toString() const
@ -133,7 +132,6 @@ void ChainActor::setBaseActor(HeroActor * base)
heroFightingStrength = base->heroFightingStrength; heroFightingStrength = base->heroFightingStrength;
armyCost = base->armyCost; armyCost = base->armyCost;
actorAction = base->actorAction; actorAction = base->actorAction;
tiCache = base->tiCache;
actorExchangeCount = base->actorExchangeCount; actorExchangeCount = base->actorExchangeCount;
} }

View File

@ -73,7 +73,6 @@ public:
float heroFightingStrength; float heroFightingStrength;
uint8_t actorExchangeCount; uint8_t actorExchangeCount;
TResources armyCost; TResources armyCost;
std::shared_ptr<TurnInfo> tiCache;
ChainActor() = default; ChainActor() = default;
virtual ~ChainActor() = default; virtual ~ChainActor() = default;

View File

@ -306,6 +306,10 @@
"tavernInvite" : false, "tavernInvite" : false,
// minimal primary skills for heroes // minimal primary skills for heroes
"minimalPrimarySkills": [ 0, 0, 1, 1] "minimalPrimarySkills": [ 0, 0, 1, 1]
/// movement points hero can get on start of the turn when on land, depending on speed of slowest creature (0-based list)
"movementPointsLand" : [ 1500, 1500, 1500, 1500, 1560, 1630, 1700, 1760, 1830, 1900, 1960, 2000 ],
/// movement points hero can get on start of the turn when on sea, depending on speed of slowest creature (0-based list)
"movementPointsSea" : [ 1500 ],
}, },
"towns": "towns":
@ -560,29 +564,6 @@
"type" : "MANA_PER_KNOWLEDGE_PERCENTAGE", //1000% mana per knowledge "type" : "MANA_PER_KNOWLEDGE_PERCENTAGE", //1000% mana per knowledge
"val" : 1000, "val" : 1000,
"valueType" : "BASE_NUMBER" "valueType" : "BASE_NUMBER"
},
"landMovement" :
{
"type" : "MOVEMENT", //Basic land movement
"subtype" : "heroMovementLand",
"val" : 1300,
"valueType" : "BASE_NUMBER",
"updater" : {
"type" : "ARMY_MOVEMENT", //Enable army movement bonus
"parameters" : [
20, // Movement points for lowest speed numerator
3, // Movement points for lowest speed denominator
10, // Resulting value, rounded down, will be multiplied by this number
700 // All army movement bonus cannot be higher than this value (so, max movement will be 1300 + 700 for this settings)
]
}
},
"seaMovement" :
{
"type" : "MOVEMENT", //Basic sea movement
"subtype" : "heroMovementSea",
"val" : 1500,
"valueType" : "BASE_NUMBER"
} }
} }
}, },

View File

@ -76,6 +76,8 @@ const std::vector<GameSettings::SettingOption> GameSettings::settingProperties =
{EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS, "heroes", "retreatOnWinWithoutTroops" }, {EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS, "heroes", "retreatOnWinWithoutTroops" },
{EGameSettings::HEROES_STARTING_STACKS_CHANCES, "heroes", "startingStackChances" }, {EGameSettings::HEROES_STARTING_STACKS_CHANCES, "heroes", "startingStackChances" },
{EGameSettings::HEROES_TAVERN_INVITE, "heroes", "tavernInvite" }, {EGameSettings::HEROES_TAVERN_INVITE, "heroes", "tavernInvite" },
{EGameSettings::HEROES_MOVEMENT_POINTS_LAND, "heroes", "movementPointsLand" },
{EGameSettings::HEROES_MOVEMENT_POINTS_SEA, "heroes", "movementPointsSea" },
{EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE, "mapFormat", "armageddonsBlade" }, {EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE, "mapFormat", "armageddonsBlade" },
{EGameSettings::MAP_FORMAT_CHRONICLES, "mapFormat", "chronicles" }, {EGameSettings::MAP_FORMAT_CHRONICLES, "mapFormat", "chronicles" },
{EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS, "mapFormat", "hornOfTheAbyss" }, {EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS, "mapFormat", "hornOfTheAbyss" },

View File

@ -49,6 +49,8 @@ enum class EGameSettings
HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS, HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS,
HEROES_STARTING_STACKS_CHANCES, HEROES_STARTING_STACKS_CHANCES,
HEROES_TAVERN_INVITE, HEROES_TAVERN_INVITE,
HEROES_MOVEMENT_POINTS_LAND,
HEROES_MOVEMENT_POINTS_SEA,
MAP_FORMAT_ARMAGEDDONS_BLADE, MAP_FORMAT_ARMAGEDDONS_BLADE,
MAP_FORMAT_CHRONICLES, MAP_FORMAT_CHRONICLES,
MAP_FORMAT_HORN_OF_THE_ABYSS, MAP_FORMAT_HORN_OF_THE_ABYSS,

View File

@ -83,10 +83,10 @@ void BonusList::stackBonuses()
} }
} }
int BonusList::totalValue() const int BonusList::totalValue(int baseValue) const
{ {
if (bonuses.empty()) if (bonuses.empty())
return 0; return baseValue;
struct BonusCollection struct BonusCollection
{ {
@ -104,6 +104,7 @@ int BonusList::totalValue() const
}; };
BonusCollection accumulated; BonusCollection accumulated;
accumulated.base = baseValue;
int indexMaxCount = 0; int indexMaxCount = 0;
int indexMinCount = 0; int indexMinCount = 0;
@ -208,12 +209,12 @@ void BonusList::getAllBonuses(BonusList &out) const
out.push_back(b); out.push_back(b);
} }
int BonusList::valOfBonuses(const CSelector &select) const int BonusList::valOfBonuses(const CSelector &select, int baseValue) const
{ {
BonusList ret; BonusList ret;
CSelector limit = nullptr; CSelector limit = nullptr;
getBonuses(ret, select, limit); getBonuses(ret, select, limit);
return ret.totalValue(); return ret.totalValue(baseValue);
} }
JsonNode BonusList::toJsonNode() const JsonNode BonusList::toJsonNode() const

View File

@ -58,14 +58,14 @@ public:
// BonusList functions // BonusList functions
void stackBonuses(); void stackBonuses();
int totalValue() const; int totalValue(int baseValue = 0) const;
void getBonuses(BonusList &out, const CSelector &selector, const CSelector &limit = nullptr) const; void getBonuses(BonusList &out, const CSelector &selector, const CSelector &limit = nullptr) const;
void getAllBonuses(BonusList &out) const; void getAllBonuses(BonusList &out) const;
//special find functions //special find functions
std::shared_ptr<Bonus> getFirst(const CSelector &select); std::shared_ptr<Bonus> getFirst(const CSelector &select);
std::shared_ptr<const Bonus> getFirst(const CSelector &select) const; std::shared_ptr<const Bonus> getFirst(const CSelector &select) const;
int valOfBonuses(const CSelector &select) const; int valOfBonuses(const CSelector &select, int baseValue = 0) const;
// conversion / output // conversion / output
JsonNode toJsonNode() const; JsonNode toJsonNode() const;

View File

@ -111,18 +111,6 @@ ArmyMovementUpdater::ArmyMovementUpdater(int base, int divider, int multiplier,
std::shared_ptr<Bonus> ArmyMovementUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const std::shared_ptr<Bonus> ArmyMovementUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
{ {
if(b->type == BonusType::MOVEMENT && context.getNodeType() == CBonusSystemNode::HERO)
{
auto speed = static_cast<const CGHeroInstance &>(context).getLowestCreatureSpeed();
si32 armySpeed = speed * base / divider;
auto counted = armySpeed * multiplier;
auto newBonus = std::make_shared<Bonus>(*b);
newBonus->source = BonusSource::ARMY;
newBonus->val += vstd::amin(counted, max);
return newBonus;
}
if(b->type != BonusType::MOVEMENT)
logGlobal->error("ArmyMovementUpdater should only be used for MOVEMENT bonus!");
return b; return b;
} }

View File

@ -73,31 +73,6 @@ void CGHeroPlaceholder::serializeJsonOptions(JsonSerializeFormat & handler)
handler.serializeInt("powerRank", powerRank.value()); handler.serializeInt("powerRank", powerRank.value());
} }
static int lowestSpeed(const CGHeroInstance * chi)
{
static const CSelector selectorSTACKS_SPEED = Selector::type()(BonusType::STACKS_SPEED);
static const std::string keySTACKS_SPEED = "type_" + std::to_string(static_cast<si32>(BonusType::STACKS_SPEED));
if(!chi->stacksCount())
{
if(chi->commander && chi->commander->alive)
{
return chi->commander->valOfBonuses(selectorSTACKS_SPEED, keySTACKS_SPEED);
}
logGlobal->error("Hero %d (%s) has no army!", chi->id.getNum(), chi->getNameTranslated());
return 20;
}
auto i = chi->Slots().begin();
//TODO? should speed modifiers (eg from artifacts) affect hero movement?
int ret = (i++)->second->valOfBonuses(selectorSTACKS_SPEED, keySTACKS_SPEED);
for(; i != chi->Slots().end(); i++)
ret = std::min(ret, i->second->valOfBonuses(selectorSTACKS_SPEED, keySTACKS_SPEED));
return ret;
}
ui32 CGHeroInstance::getTileMovementCost(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; int64_t ret = GameConstants::BASE_MOVEMENT_COST;
@ -107,13 +82,10 @@ ui32 CGHeroInstance::getTileMovementCost(const TerrainTile & dest, const Terrain
{ {
ret = from.getRoad()->movementCost; ret = from.getRoad()->movementCost;
} }
else if(ti->nativeTerrain != from.getTerrainID() &&//the terrain is not native else if(ti->hasNoTerrainPenalty(from.getTerrainID())) //no special movement bonus
ti->nativeTerrain != ETerrainId::ANY_TERRAIN && //no special creature bonus
!ti->hasBonusOfType(BonusType::NO_TERRAIN_PENALTY, BonusSubtypeID(from.getTerrainID()))) //no special movement bonus
{ {
ret = VLC->terrainTypeHandler->getById(from.getTerrainID())->moveCost; ret = VLC->terrainTypeHandler->getById(from.getTerrainID())->moveCost;
ret -= ti->valOfBonuses(BonusType::ROUGH_TERRAIN_DISCOUNT); ret -= ti->getRoughTerrainDiscountValue();
if(ret < GameConstants::BASE_MOVEMENT_COST) if(ret < GameConstants::BASE_MOVEMENT_COST)
ret = GameConstants::BASE_MOVEMENT_COST; ret = GameConstants::BASE_MOVEMENT_COST;
} }
@ -257,30 +229,44 @@ void CGHeroInstance::setMovementPoints(int points)
int CGHeroInstance::movementPointsLimit(bool onLand) const int CGHeroInstance::movementPointsLimit(bool onLand) const
{ {
return valOfBonuses(BonusType::MOVEMENT, onLand ? BonusCustomSubtype::heroMovementLand : BonusCustomSubtype::heroMovementSea); auto ti = getTurnInfo(0);
return onLand ? ti->getMovePointsLimitLand() : ti->getMovePointsLimitWater();
} }
int CGHeroInstance::getLowestCreatureSpeed() const int CGHeroInstance::getLowestCreatureSpeed() const
{ {
return lowestCreatureSpeed; static const CSelector selectorSTACKS_SPEED = Selector::type()(BonusType::STACKS_SPEED);
static const std::string cachingStr = "type_" + std::to_string(static_cast<si32>(BonusType::STACKS_SPEED));
if(stacksCount() != 0)
{
int minimalSpeed = std::numeric_limits<int>::max();
//TODO? should speed modifiers (eg from artifacts) affect hero movement?
for(const auto & slot : Slots())
minimalSpeed = std::min(minimalSpeed, slot.second->valOfBonuses(selectorSTACKS_SPEED, cachingStr));
return minimalSpeed;
}
else
{
if(commander && commander->alive)
return commander->valOfBonuses(selectorSTACKS_SPEED, cachingStr);
} }
void CGHeroInstance::updateArmyMovementBonus(bool onLand, const TurnInfo * ti) const return 10;
{
auto realLowestSpeed = lowestSpeed(this);
if(lowestCreatureSpeed != realLowestSpeed)
{
lowestCreatureSpeed = realLowestSpeed;
//Let updaters run again
treeHasChanged();
ti->updateHeroBonuses(BonusType::MOVEMENT);
} }
std::unique_ptr<TurnInfo> CGHeroInstance::getTurnInfo(int days) const
{
return std::make_unique<TurnInfo>(turnInfoCache.get(), this, days);
} }
int CGHeroInstance::movementPointsLimitCached(bool onLand, const TurnInfo * ti) const int CGHeroInstance::movementPointsLimitCached(bool onLand, const TurnInfo * ti) const
{ {
updateArmyMovementBonus(onLand, ti); if (onLand)
return ti->valOfBonuses(BonusType::MOVEMENT, onLand ? BonusCustomSubtype::heroMovementLand : BonusCustomSubtype::heroMovementSea); return ti->getMovePointsLimitLand();
else
return ti->getMovePointsLimitWater();
} }
CGHeroInstance::CGHeroInstance(IGameCallback * cb) CGHeroInstance::CGHeroInstance(IGameCallback * cb)
@ -295,8 +281,8 @@ CGHeroInstance::CGHeroInstance(IGameCallback * cb)
gender(EHeroGender::DEFAULT), gender(EHeroGender::DEFAULT),
primarySkills(this), primarySkills(this),
magicSchoolMastery(this), magicSchoolMastery(this),
manaPerKnowledgeCached(this, Selector::type()(BonusType::MANA_PER_KNOWLEDGE_PERCENTAGE)), turnInfoCache(std::make_unique<TurnInfoCache>(this)),
lowestCreatureSpeed(0) manaPerKnowledgeCached(this, Selector::type()(BonusType::MANA_PER_KNOWLEDGE_PERCENTAGE))
{ {
setNodeType(HERO); setNodeType(HERO);
ID = Obj::HERO; ID = Obj::HERO;
@ -1365,11 +1351,11 @@ int CGHeroInstance::movementPointsAfterEmbark(int MPsBefore, int basicCost, bool
std::unique_ptr<TurnInfo> turnInfoLocal; std::unique_ptr<TurnInfo> turnInfoLocal;
if(!ti) if(!ti)
{ {
turnInfoLocal = std::make_unique<TurnInfo>(this); turnInfoLocal = getTurnInfo(0);
ti = turnInfoLocal.get(); ti = turnInfoLocal.get();
} }
if(!ti->hasBonusOfType(BonusType::FREE_SHIP_BOARDING)) if(!ti->hasFreeShipBoarding())
return 0; // take all MPs by default return 0; // take all MPs by default
auto boatLayer = boat ? boat->layer : EPathfindingLayer::SAIL; auto boatLayer = boat ? boat->layer : EPathfindingLayer::SAIL;

View File

@ -25,8 +25,10 @@ class CGBoat;
class CGTownInstance; class CGTownInstance;
class CMap; class CMap;
class UpgradeInfo; class UpgradeInfo;
class TurnInfo;
struct TerrainTile; struct TerrainTile;
struct TurnInfo; struct TurnInfoCache;
class DLL_LINKAGE CGHeroPlaceholder : public CGObjectInstance class DLL_LINKAGE CGHeroPlaceholder : public CGObjectInstance
{ {
@ -62,9 +64,9 @@ private:
PrimarySkillsCache primarySkills; PrimarySkillsCache primarySkills;
MagicSchoolMasteryCache magicSchoolMastery; MagicSchoolMasteryCache magicSchoolMastery;
BonusValueCache manaPerKnowledgeCached; BonusValueCache manaPerKnowledgeCached;
std::unique_ptr<TurnInfoCache> turnInfoCache;
std::set<SpellID> spells; //known spells (spell IDs) std::set<SpellID> spells; //known spells (spell IDs)
mutable int lowestCreatureSpeed;
ui32 movement; //remaining movement points ui32 movement; //remaining movement points
public: public:
@ -224,11 +226,11 @@ public:
int movementPointsLimit(bool onLand) const; int movementPointsLimit(bool onLand) const;
//cached version is much faster, TurnInfo construction is costly //cached version is much faster, TurnInfo construction is costly
int movementPointsLimitCached(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;
int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark = false, const TurnInfo * ti = nullptr) const; int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark = false, const TurnInfo * ti = nullptr) const;
std::unique_ptr<TurnInfo> getTurnInfo(int days) const;
double getFightingStrength() const; // takes attack / defense skill into account double getFightingStrength() const; // takes attack / defense skill into account
double getMagicStrength() const; // takes knowledge / spell power skill but also current mana, whether the hero owns a spell-book and whether that books contains anything into account double getMagicStrength() const; // takes knowledge / spell power skill but also current mana, whether the hero owns a spell-book and whether that books contains anything into account
double getHeroStrength() const; // includes fighting and magic strength double getHeroStrength() const; // includes fighting and magic strength

View File

@ -514,11 +514,7 @@ CPathfinderHelper::CPathfinderHelper(CGameState * gs, const CGHeroInstance * Her
canCastWaterWalk = Hero->canCastThisSpell(waterWalk.toSpell()); canCastWaterWalk = Hero->canCastThisSpell(waterWalk.toSpell());
} }
CPathfinderHelper::~CPathfinderHelper() CPathfinderHelper::~CPathfinderHelper() = default;
{
for(auto * ti : turnsInfo)
delete ti;
}
void CPathfinderHelper::updateTurnInfo(const int Turn) void CPathfinderHelper::updateTurnInfo(const int Turn)
{ {
@ -526,10 +522,7 @@ void CPathfinderHelper::updateTurnInfo(const int Turn)
{ {
turn = Turn; turn = Turn;
if(turn >= turnsInfo.size()) if(turn >= turnsInfo.size())
{ turnsInfo.push_back(hero->getTurnInfo(turn));
auto * ti = new TurnInfo(hero, turn);
turnsInfo.push_back(ti);
}
} }
} }
@ -561,12 +554,7 @@ bool CPathfinderHelper::isLayerAvailable(const EPathfindingLayer & layer) const
const TurnInfo * CPathfinderHelper::getTurnInfo() const const TurnInfo * CPathfinderHelper::getTurnInfo() const
{ {
return turnsInfo[turn]; return turnsInfo[turn].get();
}
bool CPathfinderHelper::hasBonusOfType(const BonusType type) const
{
return turnsInfo[turn]->hasBonusOfType(type);
} }
int CPathfinderHelper::getMaxMovePoints(const EPathfindingLayer & layer) const int CPathfinderHelper::getMaxMovePoints(const EPathfindingLayer & layer) const
@ -575,15 +563,16 @@ int CPathfinderHelper::getMaxMovePoints(const EPathfindingLayer & layer) const
} }
void CPathfinderHelper::getNeighbours( void CPathfinderHelper::getNeighbours(
const TerrainTile & srcTile, const TerrainTile & sourceTile,
const int3 & srcCoord, const int3 & srcCoord,
NeighbourTilesVector & vec, NeighbourTilesVector & vec,
const boost::logic::tribool & onLand, const boost::logic::tribool & onLand,
const bool limitCoastSailing) const const bool limitCoastSailing) const
{ {
CMap * map = gs->map; CMap * map = gs->map;
const TerrainType * sourceTerrain = sourceTile.getTerrain();
static const int3 dirs[] = { constexpr std::array dirs = {
int3(-1, +1, +0), int3(0, +1, +0), int3(+1, +1, +0), int3(-1, +1, +0), int3(0, +1, +0), int3(+1, +1, +0),
int3(-1, +0, +0), /* source pos */ int3(+1, +0, +0), int3(-1, +0, +0), /* source pos */ int3(+1, +0, +0),
int3(-1, -1, +0), int3(0, -1, +0), int3(+1, -1, +0) int3(-1, -1, +0), int3(0, -1, +0), int3(+1, -1, +0)
@ -596,12 +585,12 @@ void CPathfinderHelper::getNeighbours(
continue; continue;
const TerrainTile & destTile = map->getTile(destCoord); const TerrainTile & destTile = map->getTile(destCoord);
const TerrainType* terrain = destTile.getTerrain(); const TerrainType * destTerrain = destTile.getTerrain();
if(!terrain->isPassable()) if(!destTerrain->isPassable())
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(srcTile.isWater() && limitCoastSailing && terrain->isWater() && dir.x && dir.y) //diagonal move through water if(sourceTerrain->isWater() && limitCoastSailing && destTerrain->isWater() && dir.x && dir.y) //diagonal move through water
{ {
const int3 horizontalNeighbour = srcCoord + int3{dir.x, 0, 0}; const int3 horizontalNeighbour = srcCoord + int3{dir.x, 0, 0};
const int3 verticalNeighbour = srcCoord + int3{0, dir.y, 0}; const int3 verticalNeighbour = srcCoord + int3{0, dir.y, 0};
@ -609,7 +598,7 @@ void CPathfinderHelper::getNeighbours(
continue; continue;
} }
if(indeterminate(onLand) || onLand == terrain->isLand()) if(indeterminate(onLand) || onLand == destTerrain->isLand())
{ {
vec.push_back(destCoord); vec.push_back(destCoord);
} }
@ -663,54 +652,59 @@ int CPathfinderHelper::getMovementCost(
bool isWaterLayer; bool isWaterLayer;
if(indeterminate(isDstWaterLayer)) if(indeterminate(isDstWaterLayer))
isWaterLayer = ((hero->boat && hero->boat->layer == EPathfindingLayer::WATER) || ti->hasBonusOfType(BonusType::WATER_WALKING)) && dt->isWater(); isWaterLayer = ((hero->boat && hero->boat->layer == EPathfindingLayer::WATER) || ti->hasWaterWalking()) && dt->isWater();
else else
isWaterLayer = static_cast<bool>(isDstWaterLayer); isWaterLayer = static_cast<bool>(isDstWaterLayer);
bool isAirLayer = (hero->boat && hero->boat->layer == EPathfindingLayer::AIR) || ti->hasBonusOfType(BonusType::FLYING_MOVEMENT); bool isAirLayer = (hero->boat && hero->boat->layer == EPathfindingLayer::AIR) || ti->hasFlyingMovement();
int ret = hero->getTileMovementCost(*dt, *ct, ti); int movementCost = hero->getTileMovementCost(*dt, *ct, ti);
if(isSailLayer) if(isSailLayer)
{ {
if(ct->hasFavorableWinds()) if(ct->hasFavorableWinds())
ret = static_cast<int>(ret * 2.0 / 3); movementCost = static_cast<int>(movementCost * 2.0 / 3);
} }
else if(isAirLayer) else if(isAirLayer)
vstd::amin(ret, GameConstants::BASE_MOVEMENT_COST + ti->valOfBonuses(BonusType::FLYING_MOVEMENT)); vstd::amin(movementCost, GameConstants::BASE_MOVEMENT_COST + ti->getFlyingMovementValue());
else if(isWaterLayer && ti->hasBonusOfType(BonusType::WATER_WALKING)) else if(isWaterLayer && ti->hasWaterWalking())
ret = static_cast<int>(ret * (100.0 + ti->valOfBonuses(BonusType::WATER_WALKING)) / 100.0); movementCost = static_cast<int>(movementCost * (100.0 + ti->getWaterWalkingValue()) / 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 = movementCost;
ret = static_cast<int>(ret * M_SQRT2); movementCost = static_cast<int>(movementCost * 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 // https://heroes.thelazy.net/index.php/Movement#Diagonal_move_exception
if(ret > remainingMovePoints && remainingMovePoints >= old) if(movementCost > remainingMovePoints && remainingMovePoints >= old)
{ {
return remainingMovePoints; return remainingMovePoints;
} }
} }
const int left = remainingMovePoints - ret; //it might be the last tile - if no further move possible we take all move points
constexpr auto maxCostOfOneStep = static_cast<int>(175 * M_SQRT2); // diagonal move on Swamp - 247 MP const int pointsLeft = remainingMovePoints - movementCost;
if(checkLast && left > 0 && left <= maxCostOfOneStep) //it might be the last tile - if no further move possible we take all move points if(checkLast && pointsLeft > 0)
{ {
NeighbourTilesVector vec; int minimalNextMoveCost = hero->getTileMovementCost(*dt, *ct, ti);
getNeighbours(*dt, dst, vec, ct->isLand(), true); if (pointsLeft < minimalNextMoveCost)
for(const auto & elem : vec) return remainingMovePoints;
{
int fcost = getMovementCost(dst, elem, nullptr, nullptr, left, false); // NeighbourTilesVector vec;
if(fcost <= left) //
{ // getNeighbours(*dt, dst, vec, ct->isLand(), true);
return ret; // for(const auto & elem : vec)
} // {
} // int fcost = getMovementCost(dst, elem, nullptr, nullptr, pointsLeft, false);
ret = remainingMovePoints; // if(fcost <= pointsLeft)
// {
// return movementCost;
// }
// }
// movementCost = remainingMovePoints;
} }
return ret; return movementCost;
} }
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END

View File

@ -16,7 +16,7 @@
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
class CGWhirlpool; class CGWhirlpool;
struct TurnInfo; class TurnInfo;
struct PathfinderOptions; struct PathfinderOptions;
// Optimized storage - tile can have 0-8 neighbour tiles // Optimized storage - tile can have 0-8 neighbour tiles
@ -78,7 +78,7 @@ public:
int turn; int turn;
PlayerColor owner; PlayerColor owner;
const CGHeroInstance * hero; const CGHeroInstance * hero;
std::vector<TurnInfo *> turnsInfo; std::vector<std::unique_ptr<TurnInfo>> turnsInfo;
const PathfinderOptions & options; const PathfinderOptions & options;
bool canCastFly; bool canCastFly;
bool canCastWaterWalk; bool canCastWaterWalk;
@ -93,7 +93,7 @@ public:
void updateTurnInfo(const int turn = 0); void updateTurnInfo(const int turn = 0);
bool isLayerAvailable(const EPathfindingLayer & layer) const; bool isLayerAvailable(const EPathfindingLayer & layer) const;
const TurnInfo * getTurnInfo() const; const TurnInfo * getTurnInfo() const;
bool hasBonusOfType(BonusType type) const; //bool hasBonusOfType(BonusType type) const;
int getMaxMovePoints(const EPathfindingLayer & layer) const; int getMaxMovePoints(const EPathfindingLayer & layer) const;
TeleporterTilesVector getCastleGates(const PathNodeInfo & source) const; TeleporterTilesVector getCastleGates(const PathNodeInfo & source) const;

View File

@ -10,40 +10,154 @@
#include "StdInc.h" #include "StdInc.h"
#include "TurnInfo.h" #include "TurnInfo.h"
#include "../IGameCallback.h"
#include "../IGameSettings.h"
#include "../TerrainHandler.h" #include "../TerrainHandler.h"
#include "../VCMI_Lib.h" #include "../VCMI_Lib.h"
#include "../bonuses/BonusList.h" #include "../bonuses/BonusList.h"
#include "../json/JsonNode.h"
#include "../mapObjects/CGHeroInstance.h" #include "../mapObjects/CGHeroInstance.h"
#include "../mapObjects/MiscObjects.h" #include "../mapObjects/MiscObjects.h"
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
TurnInfo::BonusCache::BonusCache(const TConstBonusListPtr & bl) TConstBonusListPtr TurnInfoBonusList::getBonusList(const CGHeroInstance * target, const CSelector & bonusSelector)
{ {
for(const auto & terrain : VLC->terrainTypeHandler->objects) std::lock_guard guard(bonusListMutex);
{
auto selector = Selector::typeSubtype(BonusType::NO_TERRAIN_PENALTY, BonusSubtypeID(terrain->getId())); if (target->getTreeVersion() == bonusListVersion)
if (bl->getFirst(selector)) return bonusList;
noTerrainPenalty.insert(terrain->getId());
bonusList = target->getBonuses(bonusSelector);
bonusListVersion = target->getTreeVersion();
return bonusList;
} }
freeShipBoarding = static_cast<bool>(bl->getFirst(Selector::type()(BonusType::FREE_SHIP_BOARDING))); int TurnInfo::hasWaterWalking() const
flyingMovement = static_cast<bool>(bl->getFirst(Selector::type()(BonusType::FLYING_MOVEMENT))); {
flyingMovementVal = bl->valOfBonuses(Selector::type()(BonusType::FLYING_MOVEMENT)); return waterWalkingTest;
waterWalking = static_cast<bool>(bl->getFirst(Selector::type()(BonusType::WATER_WALKING)));
waterWalkingVal = bl->valOfBonuses(Selector::type()(BonusType::WATER_WALKING));
pathfindingVal = bl->valOfBonuses(Selector::type()(BonusType::ROUGH_TERRAIN_DISCOUNT));
} }
TurnInfo::TurnInfo(const CGHeroInstance * Hero, const int turn): int TurnInfo::hasFlyingMovement() const
hero(Hero),
maxMovePointsLand(-1),
maxMovePointsWater(-1),
turn(turn)
{ {
bonuses = hero->getAllBonuses(Selector::days(turn), Selector::all, "all_days" + std::to_string(turn)); return flyingMovementTest;
bonusCache = std::make_unique<BonusCache>(bonuses); }
nativeTerrain = hero->getNativeTerrain();
int TurnInfo::hasNoTerrainPenalty(const TerrainId &terrain) const
{
return noterrainPenalty[terrain.num];
}
int TurnInfo::hasFreeShipBoarding() const
{
return freeShipBoardingTest;
}
int TurnInfo::getFlyingMovementValue() const
{
return flyingMovementValue;
}
int TurnInfo::getWaterWalkingValue() const
{
return waterWalkingValue;
}
int TurnInfo::getRoughTerrainDiscountValue() const
{
return roughTerrainDiscountValue;
}
int TurnInfo::getMovePointsLimitLand() const
{
return movePointsLimitLand;
}
int TurnInfo::getMovePointsLimitWater() const
{
return movePointsLimitWater;
}
TurnInfo::TurnInfo(TurnInfoCache * sharedCache, const CGHeroInstance * target, int Turn)
: noterrainPenalty(VLC->terrainTypeHandler->size())
, target(target)
{
CSelector daySelector = Selector::days(Turn);
int lowestSpeed;
if (target->getTreeVersion() == sharedCache->heroLowestSpeedVersion)
{
lowestSpeed = sharedCache->heroLowestSpeedValue;
}
else
{
lowestSpeed = target->getLowestCreatureSpeed();
sharedCache->heroLowestSpeedValue = lowestSpeed;
sharedCache->heroLowestSpeedVersion = target->getTreeVersion();
}
{
static const CSelector selector = Selector::type()(BonusType::WATER_WALKING);
const auto & bonuses = sharedCache->waterWalking.getBonusList(target, selector);
waterWalkingTest = bonuses->getFirst(selector) != nullptr;
waterWalkingValue = bonuses->valOfBonuses(selector);
}
{
static const CSelector selector = Selector::type()(BonusType::FLYING_MOVEMENT);
const auto & bonuses = sharedCache->flyingMovement.getBonusList(target, selector);
flyingMovementTest = bonuses->getFirst(selector) != nullptr;
flyingMovementValue = bonuses->valOfBonuses(selector);
}
{
static const CSelector selector = Selector::type()(BonusType::FREE_SHIP_BOARDING);
const auto & bonuses = sharedCache->flyingMovement.getBonusList(target, selector);
freeShipBoardingTest = bonuses->getFirst(selector) != nullptr;
}
{
static const CSelector selector = Selector::type()(BonusType::ROUGH_TERRAIN_DISCOUNT);
const auto & bonuses = sharedCache->flyingMovement.getBonusList(target, selector);
roughTerrainDiscountValue = bonuses->getFirst(selector) != nullptr;
}
{
static const CSelector selector = Selector::typeSubtype(BonusType::MOVEMENT, BonusCustomSubtype::heroMovementSea);
const auto & vectorSea = target->cb->getSettings().getValue(EGameSettings::HEROES_MOVEMENT_POINTS_SEA).Vector();
const auto & bonuses = sharedCache->flyingMovement.getBonusList(target, selector);
int baseMovementPointsSea;
if (lowestSpeed < vectorSea.size())
baseMovementPointsSea = vectorSea[lowestSpeed].Integer();
else
baseMovementPointsSea = vectorSea.back().Integer();
movePointsLimitWater = bonuses->valOfBonuses(selector, baseMovementPointsSea);
}
{
static const CSelector selector = Selector::typeSubtype(BonusType::MOVEMENT, BonusCustomSubtype::heroMovementSea);
const auto & vectorLand = target->cb->getSettings().getValue(EGameSettings::HEROES_MOVEMENT_POINTS_LAND).Vector();
const auto & bonuses = sharedCache->flyingMovement.getBonusList(target, selector);
int baseMovementPointsLand;
if (lowestSpeed < vectorLand.size())
baseMovementPointsLand = vectorLand[lowestSpeed].Integer();
else
baseMovementPointsLand = vectorLand.back().Integer();
movePointsLimitLand = bonuses->valOfBonuses(selector, baseMovementPointsLand);
}
{
static const CSelector selector = Selector::type()(BonusType::NO_TERRAIN_PENALTY);
const auto & bonuses = sharedCache->flyingMovement.getBonusList(target, selector);
for (const auto & bonus : *bonuses)
{
TerrainId affectedTerrain = bonus->subtype.as<TerrainId>();
noterrainPenalty.at(affectedTerrain.num) = true;
}
}
} }
bool TurnInfo::isLayerAvailable(const EPathfindingLayer & layer) const bool TurnInfo::isLayerAvailable(const EPathfindingLayer & layer) const
@ -51,19 +165,19 @@ bool TurnInfo::isLayerAvailable(const EPathfindingLayer & layer) const
switch(layer.toEnum()) switch(layer.toEnum())
{ {
case EPathfindingLayer::AIR: case EPathfindingLayer::AIR:
if(hero && hero->boat && hero->boat->layer == EPathfindingLayer::AIR) if(target && target->boat && target->boat->layer == EPathfindingLayer::AIR)
break; break;
if(!hasBonusOfType(BonusType::FLYING_MOVEMENT)) if(!hasFlyingMovement())
return false; return false;
break; break;
case EPathfindingLayer::WATER: case EPathfindingLayer::WATER:
if(hero && hero->boat && hero->boat->layer == EPathfindingLayer::WATER) if(target && target->boat && target->boat->layer == EPathfindingLayer::WATER)
break; break;
if(!hasBonusOfType(BonusType::WATER_WALKING)) if(!hasWaterWalking())
return false; return false;
break; break;
@ -72,80 +186,9 @@ bool TurnInfo::isLayerAvailable(const EPathfindingLayer & layer) const
return true; return true;
} }
bool TurnInfo::hasBonusOfType(BonusType type) const
{
return hasBonusOfType(type, BonusSubtypeID());
}
bool TurnInfo::hasBonusOfType(BonusType type, BonusSubtypeID subtype) const
{
switch(type)
{
case BonusType::FREE_SHIP_BOARDING:
return bonusCache->freeShipBoarding;
case BonusType::FLYING_MOVEMENT:
return bonusCache->flyingMovement;
case BonusType::WATER_WALKING:
return bonusCache->waterWalking;
case BonusType::NO_TERRAIN_PENALTY:
return bonusCache->noTerrainPenalty.count(subtype.as<TerrainId>());
}
return static_cast<bool>(
bonuses->getFirst(Selector::type()(type).And(Selector::subtype()(subtype))));
}
int TurnInfo::valOfBonuses(BonusType type) const
{
return valOfBonuses(type, BonusSubtypeID());
}
int TurnInfo::valOfBonuses(BonusType type, BonusSubtypeID subtype) const
{
switch(type)
{
case BonusType::FLYING_MOVEMENT:
return bonusCache->flyingMovementVal;
case BonusType::WATER_WALKING:
return bonusCache->waterWalkingVal;
case BonusType::ROUGH_TERRAIN_DISCOUNT:
return bonusCache->pathfindingVal;
}
return bonuses->valOfBonuses(Selector::type()(type).And(Selector::subtype()(subtype)));
}
int TurnInfo::getMaxMovePoints(const EPathfindingLayer & layer) const int TurnInfo::getMaxMovePoints(const EPathfindingLayer & layer) const
{ {
if(maxMovePointsLand == -1) return layer == EPathfindingLayer::SAIL ? getMovePointsLimitWater() : getMovePointsLimitLand();
maxMovePointsLand = hero->movementPointsLimitCached(true, this);
if(maxMovePointsWater == -1)
maxMovePointsWater = hero->movementPointsLimitCached(false, this);
return layer == EPathfindingLayer::SAIL ? maxMovePointsWater : maxMovePointsLand;
}
void TurnInfo::updateHeroBonuses(BonusType type) const
{
switch(type)
{
case BonusType::FREE_SHIP_BOARDING:
bonusCache->freeShipBoarding = static_cast<bool>(bonuses->getFirst(Selector::type()(BonusType::FREE_SHIP_BOARDING)));
break;
case BonusType::FLYING_MOVEMENT:
bonusCache->flyingMovement = static_cast<bool>(bonuses->getFirst(Selector::type()(BonusType::FLYING_MOVEMENT)));
bonusCache->flyingMovementVal = bonuses->valOfBonuses(Selector::type()(BonusType::FLYING_MOVEMENT));
break;
case BonusType::WATER_WALKING:
bonusCache->waterWalking = static_cast<bool>(bonuses->getFirst(Selector::type()(BonusType::WATER_WALKING)));
bonusCache->waterWalkingVal = bonuses->valOfBonuses(Selector::type()(BonusType::WATER_WALKING));
break;
case BonusType::ROUGH_TERRAIN_DISCOUNT:
bonusCache->pathfindingVal = bonuses->valOfBonuses(Selector::type()(BonusType::ROUGH_TERRAIN_DISCOUNT));
break;
default:
bonuses = hero->getAllBonuses(Selector::days(turn), Selector::all, "all_days" + std::to_string(turn));
}
} }
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END

View File

@ -10,43 +10,74 @@
#pragma once #pragma once
#include "../bonuses/Bonus.h" #include "../bonuses/Bonus.h"
#include "../GameConstants.h" #include "../bonuses/BonusSelector.h"
#include "../bonuses/BonusCache.h"
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
class CGHeroInstance; class CGHeroInstance;
struct DLL_LINKAGE TurnInfo class TurnInfoBonusList
{ {
/// This is certainly not the best design ever and certainly can be improved TConstBonusListPtr bonusList;
/// Unfortunately for pathfinder that do hundreds of thousands calls onus system add too big overhead std::mutex bonusListMutex;
struct BonusCache { std::atomic<int64_t> bonusListVersion = 0;
std::set<TerrainId> noTerrainPenalty; public:
bool freeShipBoarding; TConstBonusListPtr getBonusList(const CGHeroInstance * target, const CSelector & bonusSelector);
bool flyingMovement;
int flyingMovementVal;
bool waterWalking;
int waterWalkingVal;
int pathfindingVal;
BonusCache(const TConstBonusListPtr & bonusList);
}; };
std::unique_ptr<BonusCache> bonusCache;
const CGHeroInstance * hero; struct TurnInfoCache
mutable TConstBonusListPtr bonuses; {
mutable int maxMovePointsLand; TurnInfoBonusList waterWalking;
mutable int maxMovePointsWater; TurnInfoBonusList flyingMovement;
TerrainId nativeTerrain; TurnInfoBonusList noTerrainPenalty;
int turn; TurnInfoBonusList freeShipBoarding;
TurnInfoBonusList roughTerrainDiscount;
TurnInfoBonusList movementPointsLimitLand;
TurnInfoBonusList movementPointsLimitWater;
TurnInfo(const CGHeroInstance * Hero, const int Turn = 0); const CGHeroInstance * target;
mutable std::atomic<int64_t> heroLowestSpeedVersion = 0;
mutable std::atomic<int64_t> heroLowestSpeedValue = 0;
explicit TurnInfoCache(const CGHeroInstance * target):
target(target)
{}
};
class DLL_LINKAGE TurnInfo
{
private:
const CGHeroInstance * target;
// stores cached values per each terrain
std::vector<bool> noterrainPenalty;
int flyingMovementValue;
int waterWalkingValue;
int roughTerrainDiscountValue;
int movePointsLimitLand;
int movePointsLimitWater;
bool waterWalkingTest;
bool flyingMovementTest;
bool freeShipBoardingTest;
public:
int hasWaterWalking() const;
int hasFlyingMovement() const;
int hasNoTerrainPenalty(const TerrainId & terrain) const;
int hasFreeShipBoarding() const;
int getFlyingMovementValue() const;
int getWaterWalkingValue() const;
int getRoughTerrainDiscountValue() const;
int getMovePointsLimitLand() const;
int getMovePointsLimitWater() const;
TurnInfo(TurnInfoCache * sharedCache, const CGHeroInstance * target, int Turn);
bool isLayerAvailable(const EPathfindingLayer & layer) const; bool isLayerAvailable(const EPathfindingLayer & layer) const;
bool hasBonusOfType(const BonusType type) const;
bool hasBonusOfType(const BonusType type, const BonusSubtypeID subtype) const;
int valOfBonuses(const BonusType type) const;
int valOfBonuses(const BonusType type, const BonusSubtypeID subtype) const;
void updateHeroBonuses(BonusType type) const;
int getMaxMovePoints(const EPathfindingLayer & layer) const; int getMaxMovePoints(const EPathfindingLayer & layer) const;
}; };

View File

@ -846,8 +846,8 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme
auto pathfinderHelper = std::make_unique<CPathfinderHelper>(gs, h, PathfinderOptions(this)); auto pathfinderHelper = std::make_unique<CPathfinderHelper>(gs, h, PathfinderOptions(this));
auto ti = pathfinderHelper->getTurnInfo(); auto ti = pathfinderHelper->getTurnInfo();
const bool canFly = pathfinderHelper->hasBonusOfType(BonusType::FLYING_MOVEMENT) || (h->boat && h->boat->layer == EPathfindingLayer::AIR); const bool canFly = ti->hasFlyingMovement() || (h->boat && h->boat->layer == EPathfindingLayer::AIR);
const bool canWalkOnSea = pathfinderHelper->hasBonusOfType(BonusType::WATER_WALKING) || (h->boat && h->boat->layer == EPathfindingLayer::WATER); const bool canWalkOnSea = ti->hasWaterWalking() || (h->boat && h->boat->layer == EPathfindingLayer::WATER);
const int cost = pathfinderHelper->getMovementCost(h->visitablePos(), hmpos, nullptr, nullptr, h->movementPointsRemaining()); const int cost = pathfinderHelper->getMovementCost(h->visitablePos(), hmpos, nullptr, nullptr, h->movementPointsRemaining());
const bool movingOntoObstacle = t.blocked() && !t.visitable(); const bool movingOntoObstacle = t.blocked() && !t.visitable();

View File

@ -584,7 +584,7 @@ std::vector<SetMovePoints> NewTurnProcessor::updateHeroesMovementPoints()
{ {
for (CGHeroInstance *h : elem.second.getHeroes()) for (CGHeroInstance *h : elem.second.getHeroes())
{ {
auto ti = std::make_unique<TurnInfo>(h, 1); auto ti = h->getTurnInfo(1);
// NOTE: this code executed when bonuses of previous day not yet updated (this happen in NewTurn::applyGs). See issue 2356 // NOTE: this code executed when bonuses of previous day not yet updated (this happen in NewTurn::applyGs). See issue 2356
int32_t newMovementPoints = h->movementPointsLimitCached(gameHandler->gameState()->map->getTile(h->visitablePos()).isLand(), ti.get()); int32_t newMovementPoints = h->movementPointsLimitCached(gameHandler->gameState()->map->getTile(h->visitablePos()).isLand(), ti.get());