From dea1eba20bce5eadaec3faff7aee565851e43df7 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 28 Dec 2024 12:02:27 +0000 Subject: [PATCH] Rework and optimize turnInfo used by pathfinder --- AI/Nullkiller/Pathfinding/Actors.cpp | 6 +- AI/Nullkiller/Pathfinding/Actors.h | 1 - config/gameConfig.json | 27 +-- lib/GameSettings.cpp | 2 + lib/IGameSettings.h | 2 + lib/bonuses/BonusList.cpp | 9 +- lib/bonuses/BonusList.h | 4 +- lib/bonuses/Updaters.cpp | 12 -- lib/mapObjects/CGHeroInstance.cpp | 80 ++++----- lib/mapObjects/CGHeroInstance.h | 10 +- lib/pathfinder/CPathfinder.cpp | 86 +++++---- lib/pathfinder/CPathfinder.h | 6 +- lib/pathfinder/TurnInfo.cpp | 237 +++++++++++++++---------- lib/pathfinder/TurnInfo.h | 85 ++++++--- server/CGameHandler.cpp | 4 +- server/processors/NewTurnProcessor.cpp | 2 +- 16 files changed, 300 insertions(+), 273 deletions(-) diff --git a/AI/Nullkiller/Pathfinding/Actors.cpp b/AI/Nullkiller/Pathfinding/Actors.cpp index 8db9230cc..489f830ac 100644 --- a/AI/Nullkiller/Pathfinding/Actors.cpp +++ b/AI/Nullkiller/Pathfinding/Actors.cpp @@ -47,11 +47,10 @@ ChainActor::ChainActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t initialTurn = 0; armyValue = getHeroArmyStrengthWithCommander(hero, hero); heroFightingStrength = hero->getHeroStrength(); - tiCache.reset(new TurnInfo(hero)); } 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), 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"); #endif - return hero->movementPointsLimitCached(layer, tiCache.get()); + return hero->movementPointsLimit(layer); } std::string ChainActor::toString() const @@ -133,7 +132,6 @@ void ChainActor::setBaseActor(HeroActor * base) heroFightingStrength = base->heroFightingStrength; armyCost = base->armyCost; actorAction = base->actorAction; - tiCache = base->tiCache; actorExchangeCount = base->actorExchangeCount; } diff --git a/AI/Nullkiller/Pathfinding/Actors.h b/AI/Nullkiller/Pathfinding/Actors.h index 1f653fbd3..8caf02e03 100644 --- a/AI/Nullkiller/Pathfinding/Actors.h +++ b/AI/Nullkiller/Pathfinding/Actors.h @@ -73,7 +73,6 @@ public: float heroFightingStrength; uint8_t actorExchangeCount; TResources armyCost; - std::shared_ptr tiCache; ChainActor() = default; virtual ~ChainActor() = default; diff --git a/config/gameConfig.json b/config/gameConfig.json index ac40a419f..e962291a4 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -306,6 +306,10 @@ "tavernInvite" : false, // minimal primary skills for heroes "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": @@ -560,29 +564,6 @@ "type" : "MANA_PER_KNOWLEDGE_PERCENTAGE", //1000% mana per knowledge "val" : 1000, "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" } } }, diff --git a/lib/GameSettings.cpp b/lib/GameSettings.cpp index a8df9add3..c1e688490 100644 --- a/lib/GameSettings.cpp +++ b/lib/GameSettings.cpp @@ -76,6 +76,8 @@ const std::vector GameSettings::settingProperties = {EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS, "heroes", "retreatOnWinWithoutTroops" }, {EGameSettings::HEROES_STARTING_STACKS_CHANCES, "heroes", "startingStackChances" }, {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_CHRONICLES, "mapFormat", "chronicles" }, {EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS, "mapFormat", "hornOfTheAbyss" }, diff --git a/lib/IGameSettings.h b/lib/IGameSettings.h index 3cb92e127..b4a0f6f72 100644 --- a/lib/IGameSettings.h +++ b/lib/IGameSettings.h @@ -49,6 +49,8 @@ enum class EGameSettings HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS, HEROES_STARTING_STACKS_CHANCES, HEROES_TAVERN_INVITE, + HEROES_MOVEMENT_POINTS_LAND, + HEROES_MOVEMENT_POINTS_SEA, MAP_FORMAT_ARMAGEDDONS_BLADE, MAP_FORMAT_CHRONICLES, MAP_FORMAT_HORN_OF_THE_ABYSS, diff --git a/lib/bonuses/BonusList.cpp b/lib/bonuses/BonusList.cpp index 1739f560a..5e4048573 100644 --- a/lib/bonuses/BonusList.cpp +++ b/lib/bonuses/BonusList.cpp @@ -83,10 +83,10 @@ void BonusList::stackBonuses() } } -int BonusList::totalValue() const +int BonusList::totalValue(int baseValue) const { if (bonuses.empty()) - return 0; + return baseValue; struct BonusCollection { @@ -104,6 +104,7 @@ int BonusList::totalValue() const }; BonusCollection accumulated; + accumulated.base = baseValue; int indexMaxCount = 0; int indexMinCount = 0; @@ -208,12 +209,12 @@ void BonusList::getAllBonuses(BonusList &out) const out.push_back(b); } -int BonusList::valOfBonuses(const CSelector &select) const +int BonusList::valOfBonuses(const CSelector &select, int baseValue) const { BonusList ret; CSelector limit = nullptr; getBonuses(ret, select, limit); - return ret.totalValue(); + return ret.totalValue(baseValue); } JsonNode BonusList::toJsonNode() const diff --git a/lib/bonuses/BonusList.h b/lib/bonuses/BonusList.h index 66b4f2b42..cc266494f 100644 --- a/lib/bonuses/BonusList.h +++ b/lib/bonuses/BonusList.h @@ -58,14 +58,14 @@ public: // BonusList functions void stackBonuses(); - int totalValue() const; + int totalValue(int baseValue = 0) const; void getBonuses(BonusList &out, const CSelector &selector, const CSelector &limit = nullptr) const; void getAllBonuses(BonusList &out) const; //special find functions std::shared_ptr getFirst(const CSelector &select); std::shared_ptr getFirst(const CSelector &select) const; - int valOfBonuses(const CSelector &select) const; + int valOfBonuses(const CSelector &select, int baseValue = 0) const; // conversion / output JsonNode toJsonNode() const; diff --git a/lib/bonuses/Updaters.cpp b/lib/bonuses/Updaters.cpp index 064818a58..7ce42981a 100644 --- a/lib/bonuses/Updaters.cpp +++ b/lib/bonuses/Updaters.cpp @@ -111,18 +111,6 @@ ArmyMovementUpdater::ArmyMovementUpdater(int base, int divider, int multiplier, std::shared_ptr ArmyMovementUpdater::createUpdatedBonus(const std::shared_ptr & b, const CBonusSystemNode & context) const { - if(b->type == BonusType::MOVEMENT && context.getNodeType() == CBonusSystemNode::HERO) - { - auto speed = static_cast(context).getLowestCreatureSpeed(); - si32 armySpeed = speed * base / divider; - auto counted = armySpeed * multiplier; - auto newBonus = std::make_shared(*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; } diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 24c35b781..ffd94191c 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -73,31 +73,6 @@ void CGHeroPlaceholder::serializeJsonOptions(JsonSerializeFormat & handler) 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(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 { int64_t ret = GameConstants::BASE_MOVEMENT_COST; @@ -107,13 +82,10 @@ ui32 CGHeroInstance::getTileMovementCost(const TerrainTile & dest, const Terrain { ret = from.getRoad()->movementCost; } - else if(ti->nativeTerrain != from.getTerrainID() &&//the terrain is not native - ti->nativeTerrain != ETerrainId::ANY_TERRAIN && //no special creature bonus - !ti->hasBonusOfType(BonusType::NO_TERRAIN_PENALTY, BonusSubtypeID(from.getTerrainID()))) //no special movement bonus + else if(ti->hasNoTerrainPenalty(from.getTerrainID())) //no special movement bonus { - ret = VLC->terrainTypeHandler->getById(from.getTerrainID())->moveCost; - ret -= ti->valOfBonuses(BonusType::ROUGH_TERRAIN_DISCOUNT); + ret -= ti->getRoughTerrainDiscountValue(); if(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 { - return valOfBonuses(BonusType::MOVEMENT, onLand ? BonusCustomSubtype::heroMovementLand : BonusCustomSubtype::heroMovementSea); + auto ti = getTurnInfo(0); + return onLand ? ti->getMovePointsLimitLand() : ti->getMovePointsLimitWater(); } 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(BonusType::STACKS_SPEED)); + + if(stacksCount() != 0) + { + int minimalSpeed = std::numeric_limits::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); + } + + return 10; } -void CGHeroInstance::updateArmyMovementBonus(bool onLand, const TurnInfo * ti) const +std::unique_ptr CGHeroInstance::getTurnInfo(int days) const { - auto realLowestSpeed = lowestSpeed(this); - if(lowestCreatureSpeed != realLowestSpeed) - { - lowestCreatureSpeed = realLowestSpeed; - //Let updaters run again - treeHasChanged(); - ti->updateHeroBonuses(BonusType::MOVEMENT); - } + return std::make_unique(turnInfoCache.get(), this, days); } int CGHeroInstance::movementPointsLimitCached(bool onLand, const TurnInfo * ti) const { - updateArmyMovementBonus(onLand, ti); - return ti->valOfBonuses(BonusType::MOVEMENT, onLand ? BonusCustomSubtype::heroMovementLand : BonusCustomSubtype::heroMovementSea); + if (onLand) + return ti->getMovePointsLimitLand(); + else + return ti->getMovePointsLimitWater(); } CGHeroInstance::CGHeroInstance(IGameCallback * cb) @@ -295,8 +281,8 @@ CGHeroInstance::CGHeroInstance(IGameCallback * cb) gender(EHeroGender::DEFAULT), primarySkills(this), magicSchoolMastery(this), - manaPerKnowledgeCached(this, Selector::type()(BonusType::MANA_PER_KNOWLEDGE_PERCENTAGE)), - lowestCreatureSpeed(0) + turnInfoCache(std::make_unique(this)), + manaPerKnowledgeCached(this, Selector::type()(BonusType::MANA_PER_KNOWLEDGE_PERCENTAGE)) { setNodeType(HERO); ID = Obj::HERO; @@ -1365,11 +1351,11 @@ int CGHeroInstance::movementPointsAfterEmbark(int MPsBefore, int basicCost, bool std::unique_ptr turnInfoLocal; if(!ti) { - turnInfoLocal = std::make_unique(this); + turnInfoLocal = getTurnInfo(0); ti = turnInfoLocal.get(); } - if(!ti->hasBonusOfType(BonusType::FREE_SHIP_BOARDING)) + if(!ti->hasFreeShipBoarding()) return 0; // take all MPs by default auto boatLayer = boat ? boat->layer : EPathfindingLayer::SAIL; diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index 4ff89ec5b..569922ef5 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -25,8 +25,10 @@ class CGBoat; class CGTownInstance; class CMap; class UpgradeInfo; +class TurnInfo; + struct TerrainTile; -struct TurnInfo; +struct TurnInfoCache; class DLL_LINKAGE CGHeroPlaceholder : public CGObjectInstance { @@ -62,9 +64,9 @@ private: PrimarySkillsCache primarySkills; MagicSchoolMasteryCache magicSchoolMastery; BonusValueCache manaPerKnowledgeCached; + std::unique_ptr turnInfoCache; std::set spells; //known spells (spell IDs) - mutable int lowestCreatureSpeed; ui32 movement; //remaining movement points public: @@ -224,11 +226,11 @@ public: int movementPointsLimit(bool onLand) const; //cached version is much faster, TurnInfo construction is costly 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; + std::unique_ptr getTurnInfo(int days) const; + 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 getHeroStrength() const; // includes fighting and magic strength diff --git a/lib/pathfinder/CPathfinder.cpp b/lib/pathfinder/CPathfinder.cpp index deeb3fa5c..87c073fca 100644 --- a/lib/pathfinder/CPathfinder.cpp +++ b/lib/pathfinder/CPathfinder.cpp @@ -514,11 +514,7 @@ CPathfinderHelper::CPathfinderHelper(CGameState * gs, const CGHeroInstance * Her canCastWaterWalk = Hero->canCastThisSpell(waterWalk.toSpell()); } -CPathfinderHelper::~CPathfinderHelper() -{ - for(auto * ti : turnsInfo) - delete ti; -} +CPathfinderHelper::~CPathfinderHelper() = default; void CPathfinderHelper::updateTurnInfo(const int Turn) { @@ -526,10 +522,7 @@ void CPathfinderHelper::updateTurnInfo(const int Turn) { turn = Turn; if(turn >= turnsInfo.size()) - { - auto * ti = new TurnInfo(hero, turn); - turnsInfo.push_back(ti); - } + turnsInfo.push_back(hero->getTurnInfo(turn)); } } @@ -561,12 +554,7 @@ bool CPathfinderHelper::isLayerAvailable(const EPathfindingLayer & layer) const const TurnInfo * CPathfinderHelper::getTurnInfo() const { - return turnsInfo[turn]; -} - -bool CPathfinderHelper::hasBonusOfType(const BonusType type) const -{ - return turnsInfo[turn]->hasBonusOfType(type); + return turnsInfo[turn].get(); } int CPathfinderHelper::getMaxMovePoints(const EPathfindingLayer & layer) const @@ -575,15 +563,16 @@ int CPathfinderHelper::getMaxMovePoints(const EPathfindingLayer & layer) const } void CPathfinderHelper::getNeighbours( - const TerrainTile & srcTile, + const TerrainTile & sourceTile, const int3 & srcCoord, NeighbourTilesVector & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing) const { 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, +0, +0), /* source pos */ int3(+1, +0, +0), int3(-1, -1, +0), int3(0, -1, +0), int3(+1, -1, +0) @@ -596,12 +585,12 @@ void CPathfinderHelper::getNeighbours( continue; const TerrainTile & destTile = map->getTile(destCoord); - const TerrainType* terrain = destTile.getTerrain(); - if(!terrain->isPassable()) + const TerrainType * destTerrain = destTile.getTerrain(); + if(!destTerrain->isPassable()) continue; /// 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 verticalNeighbour = srcCoord + int3{0, dir.y, 0}; @@ -609,7 +598,7 @@ void CPathfinderHelper::getNeighbours( continue; } - if(indeterminate(onLand) || onLand == terrain->isLand()) + if(indeterminate(onLand) || onLand == destTerrain->isLand()) { vec.push_back(destCoord); } @@ -663,54 +652,59 @@ int CPathfinderHelper::getMovementCost( bool isWaterLayer; 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 isWaterLayer = static_cast(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(ct->hasFavorableWinds()) - ret = static_cast(ret * 2.0 / 3); + movementCost = static_cast(movementCost * 2.0 / 3); } else if(isAirLayer) - vstd::amin(ret, GameConstants::BASE_MOVEMENT_COST + ti->valOfBonuses(BonusType::FLYING_MOVEMENT)); - else if(isWaterLayer && ti->hasBonusOfType(BonusType::WATER_WALKING)) - ret = static_cast(ret * (100.0 + ti->valOfBonuses(BonusType::WATER_WALKING)) / 100.0); + vstd::amin(movementCost, GameConstants::BASE_MOVEMENT_COST + ti->getFlyingMovementValue()); + else if(isWaterLayer && ti->hasWaterWalking()) + movementCost = static_cast(movementCost * (100.0 + ti->getWaterWalkingValue()) / 100.0); if(src.x != dst.x && src.y != dst.y) //it's diagonal move { - int old = ret; - ret = static_cast(ret * M_SQRT2); + int old = movementCost; + movementCost = static_cast(movementCost * M_SQRT2); //diagonal move costs too much but normal move is possible - allow diagonal move for remaining move points // https://heroes.thelazy.net/index.php/Movement#Diagonal_move_exception - if(ret > remainingMovePoints && remainingMovePoints >= old) + if(movementCost > remainingMovePoints && remainingMovePoints >= old) { return remainingMovePoints; } } - const int left = remainingMovePoints - ret; - constexpr auto maxCostOfOneStep = static_cast(175 * M_SQRT2); // diagonal move on Swamp - 247 MP - if(checkLast && left > 0 && left <= maxCostOfOneStep) //it might be the last tile - if no further move possible we take all move points + //it might be the last tile - if no further move possible we take all move points + const int pointsLeft = remainingMovePoints - movementCost; + if(checkLast && pointsLeft > 0) { - NeighbourTilesVector vec; + int minimalNextMoveCost = hero->getTileMovementCost(*dt, *ct, ti); - getNeighbours(*dt, dst, vec, ct->isLand(), true); - for(const auto & elem : vec) - { - int fcost = getMovementCost(dst, elem, nullptr, nullptr, left, false); - if(fcost <= left) - { - return ret; - } - } - ret = remainingMovePoints; + if (pointsLeft < minimalNextMoveCost) + return remainingMovePoints; + +// NeighbourTilesVector vec; +// +// getNeighbours(*dt, dst, vec, ct->isLand(), true); +// for(const auto & elem : vec) +// { +// int fcost = getMovementCost(dst, elem, nullptr, nullptr, pointsLeft, false); +// if(fcost <= pointsLeft) +// { +// return movementCost; +// } +// } +// movementCost = remainingMovePoints; } - return ret; + return movementCost; } VCMI_LIB_NAMESPACE_END diff --git a/lib/pathfinder/CPathfinder.h b/lib/pathfinder/CPathfinder.h index 9a903ced1..6fe8b17f2 100644 --- a/lib/pathfinder/CPathfinder.h +++ b/lib/pathfinder/CPathfinder.h @@ -16,7 +16,7 @@ VCMI_LIB_NAMESPACE_BEGIN class CGWhirlpool; -struct TurnInfo; +class TurnInfo; struct PathfinderOptions; // Optimized storage - tile can have 0-8 neighbour tiles @@ -78,7 +78,7 @@ public: int turn; PlayerColor owner; const CGHeroInstance * hero; - std::vector turnsInfo; + std::vector> turnsInfo; const PathfinderOptions & options; bool canCastFly; bool canCastWaterWalk; @@ -93,7 +93,7 @@ public: void updateTurnInfo(const int turn = 0); bool isLayerAvailable(const EPathfindingLayer & layer) const; const TurnInfo * getTurnInfo() const; - bool hasBonusOfType(BonusType type) const; + //bool hasBonusOfType(BonusType type) const; int getMaxMovePoints(const EPathfindingLayer & layer) const; TeleporterTilesVector getCastleGates(const PathNodeInfo & source) const; diff --git a/lib/pathfinder/TurnInfo.cpp b/lib/pathfinder/TurnInfo.cpp index 17258de4e..9ef4489e3 100644 --- a/lib/pathfinder/TurnInfo.cpp +++ b/lib/pathfinder/TurnInfo.cpp @@ -10,40 +10,154 @@ #include "StdInc.h" #include "TurnInfo.h" +#include "../IGameCallback.h" +#include "../IGameSettings.h" #include "../TerrainHandler.h" #include "../VCMI_Lib.h" #include "../bonuses/BonusList.h" +#include "../json/JsonNode.h" #include "../mapObjects/CGHeroInstance.h" #include "../mapObjects/MiscObjects.h" 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) - { - auto selector = Selector::typeSubtype(BonusType::NO_TERRAIN_PENALTY, BonusSubtypeID(terrain->getId())); - if (bl->getFirst(selector)) - noTerrainPenalty.insert(terrain->getId()); - } + std::lock_guard guard(bonusListMutex); - freeShipBoarding = static_cast(bl->getFirst(Selector::type()(BonusType::FREE_SHIP_BOARDING))); - flyingMovement = static_cast(bl->getFirst(Selector::type()(BonusType::FLYING_MOVEMENT))); - flyingMovementVal = bl->valOfBonuses(Selector::type()(BonusType::FLYING_MOVEMENT)); - waterWalking = static_cast(bl->getFirst(Selector::type()(BonusType::WATER_WALKING))); - waterWalkingVal = bl->valOfBonuses(Selector::type()(BonusType::WATER_WALKING)); - pathfindingVal = bl->valOfBonuses(Selector::type()(BonusType::ROUGH_TERRAIN_DISCOUNT)); + if (target->getTreeVersion() == bonusListVersion) + return bonusList; + + bonusList = target->getBonuses(bonusSelector); + bonusListVersion = target->getTreeVersion(); + + return bonusList; } -TurnInfo::TurnInfo(const CGHeroInstance * Hero, const int turn): - hero(Hero), - maxMovePointsLand(-1), - maxMovePointsWater(-1), - turn(turn) +int TurnInfo::hasWaterWalking() const { - bonuses = hero->getAllBonuses(Selector::days(turn), Selector::all, "all_days" + std::to_string(turn)); - bonusCache = std::make_unique(bonuses); - nativeTerrain = hero->getNativeTerrain(); + return waterWalkingTest; +} + +int TurnInfo::hasFlyingMovement() const +{ + return flyingMovementTest; +} + +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(); + noterrainPenalty.at(affectedTerrain.num) = true; + } + } } bool TurnInfo::isLayerAvailable(const EPathfindingLayer & layer) const @@ -51,19 +165,19 @@ bool TurnInfo::isLayerAvailable(const EPathfindingLayer & layer) const switch(layer.toEnum()) { case EPathfindingLayer::AIR: - if(hero && hero->boat && hero->boat->layer == EPathfindingLayer::AIR) + if(target && target->boat && target->boat->layer == EPathfindingLayer::AIR) break; - if(!hasBonusOfType(BonusType::FLYING_MOVEMENT)) + if(!hasFlyingMovement()) return false; break; case EPathfindingLayer::WATER: - if(hero && hero->boat && hero->boat->layer == EPathfindingLayer::WATER) + if(target && target->boat && target->boat->layer == EPathfindingLayer::WATER) break; - if(!hasBonusOfType(BonusType::WATER_WALKING)) + if(!hasWaterWalking()) return false; break; @@ -72,80 +186,9 @@ bool TurnInfo::isLayerAvailable(const EPathfindingLayer & layer) const 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()); - } - - return static_cast( - 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 { - if(maxMovePointsLand == -1) - 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(bonuses->getFirst(Selector::type()(BonusType::FREE_SHIP_BOARDING))); - break; - case BonusType::FLYING_MOVEMENT: - bonusCache->flyingMovement = static_cast(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(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)); - } + return layer == EPathfindingLayer::SAIL ? getMovePointsLimitWater() : getMovePointsLimitLand(); } VCMI_LIB_NAMESPACE_END diff --git a/lib/pathfinder/TurnInfo.h b/lib/pathfinder/TurnInfo.h index 02e4597df..70e7a16e1 100644 --- a/lib/pathfinder/TurnInfo.h +++ b/lib/pathfinder/TurnInfo.h @@ -10,43 +10,74 @@ #pragma once #include "../bonuses/Bonus.h" -#include "../GameConstants.h" +#include "../bonuses/BonusSelector.h" +#include "../bonuses/BonusCache.h" VCMI_LIB_NAMESPACE_BEGIN class CGHeroInstance; -struct DLL_LINKAGE TurnInfo +class TurnInfoBonusList { - /// This is certainly not the best design ever and certainly can be improved - /// Unfortunately for pathfinder that do hundreds of thousands calls onus system add too big overhead - struct BonusCache { - std::set noTerrainPenalty; - bool freeShipBoarding; - bool flyingMovement; - int flyingMovementVal; - bool waterWalking; - int waterWalkingVal; - int pathfindingVal; + TConstBonusListPtr bonusList; + std::mutex bonusListMutex; + std::atomic bonusListVersion = 0; +public: + TConstBonusListPtr getBonusList(const CGHeroInstance * target, const CSelector & bonusSelector); +}; - BonusCache(const TConstBonusListPtr & bonusList); - }; - std::unique_ptr bonusCache; +struct TurnInfoCache +{ + TurnInfoBonusList waterWalking; + TurnInfoBonusList flyingMovement; + TurnInfoBonusList noTerrainPenalty; + TurnInfoBonusList freeShipBoarding; + TurnInfoBonusList roughTerrainDiscount; + TurnInfoBonusList movementPointsLimitLand; + TurnInfoBonusList movementPointsLimitWater; - const CGHeroInstance * hero; - mutable TConstBonusListPtr bonuses; - mutable int maxMovePointsLand; - mutable int maxMovePointsWater; - TerrainId nativeTerrain; - int turn; + const CGHeroInstance * target; - TurnInfo(const CGHeroInstance * Hero, const int Turn = 0); + mutable std::atomic heroLowestSpeedVersion = 0; + mutable std::atomic 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 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 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; }; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 07c8f50db..0e4688db0 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -846,8 +846,8 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme auto pathfinderHelper = std::make_unique(gs, h, PathfinderOptions(this)); auto ti = pathfinderHelper->getTurnInfo(); - 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 bool canFly = ti->hasFlyingMovement() || (h->boat && h->boat->layer == EPathfindingLayer::AIR); + 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 bool movingOntoObstacle = t.blocked() && !t.visitable(); diff --git a/server/processors/NewTurnProcessor.cpp b/server/processors/NewTurnProcessor.cpp index abd130793..41dc0bdc4 100644 --- a/server/processors/NewTurnProcessor.cpp +++ b/server/processors/NewTurnProcessor.cpp @@ -584,7 +584,7 @@ std::vector NewTurnProcessor::updateHeroesMovementPoints() { for (CGHeroInstance *h : elem.second.getHeroes()) { - auto ti = std::make_unique(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 int32_t newMovementPoints = h->movementPointsLimitCached(gameHandler->gameState()->map->getTile(h->visitablePos()).isLand(), ti.get());