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<TurnInfo> 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::SettingOption> 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<Bonus> getFirst(const CSelector &select);
 	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
 	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<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;
 }
 
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<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
 {
 	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<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);
+	}
+
+	return 10;
 }
 
-void CGHeroInstance::updateArmyMovementBonus(bool onLand, const TurnInfo * ti) const
+std::unique_ptr<TurnInfo> 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<TurnInfo>(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<TurnInfoCache>(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<TurnInfo> turnInfoLocal;
 	if(!ti)
 	{
-		turnInfoLocal = std::make_unique<TurnInfo>(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> turnInfoCache;
 
 	std::set<SpellID> 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<TurnInfo> 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<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(ct->hasFavorableWinds())
-			ret = static_cast<int>(ret * 2.0 / 3);
+			movementCost = static_cast<int>(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<int>(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<int>(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<int>(ret * M_SQRT2);
+		int old = movementCost;
+		movementCost = static_cast<int>(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<int>(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<TurnInfo *> turnsInfo;
+	std::vector<std::unique_ptr<TurnInfo>> 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<bool>(bl->getFirst(Selector::type()(BonusType::FREE_SHIP_BOARDING)));
-	flyingMovement = static_cast<bool>(bl->getFirst(Selector::type()(BonusType::FLYING_MOVEMENT)));
-	flyingMovementVal = bl->valOfBonuses(Selector::type()(BonusType::FLYING_MOVEMENT));
-	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));
+	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<BonusCache>(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<TerrainId>();
+			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<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
 {
-	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<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));
-	}
+	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<TerrainId> noTerrainPenalty;
-		bool freeShipBoarding;
-		bool flyingMovement;
-		int flyingMovementVal;
-		bool waterWalking;
-		int waterWalkingVal;
-		int pathfindingVal;
+	TConstBonusListPtr bonusList;
+	std::mutex bonusListMutex;
+	std::atomic<int64_t> bonusListVersion = 0;
+public:
+	TConstBonusListPtr getBonusList(const CGHeroInstance * target, const CSelector & bonusSelector);
+};
 
-		BonusCache(const TConstBonusListPtr & bonusList);
-	};
-	std::unique_ptr<BonusCache> 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<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 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<CPathfinderHelper>(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<SetMovePoints> NewTurnProcessor::updateHeroesMovementPoints()
 	{
 		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
 			int32_t newMovementPoints = h->movementPointsLimitCached(gameHandler->gameState()->map->getTile(h->visitablePos()).isLand(), ti.get());