浏览代码

Pathfinding: implement duration checking for fly and water walking

Now pathfinder take into account different bonuses for different tuns. So if you only have FLYING_MOVEMENT bonus from Fly spell for one turn then pathfinder will only let you use air layer within one turn only.

That work for cost calculations too. Let's say you have two bonuses:
 - FLYING_MOVEMENT with 20% penalty for next 2 turns
 - FLYING_MOVEMENT with 40% penalty for 5 turns
Now pathfinder using correct penalty for each turn so movements in air layer going to be more expensive on 3-5 turns.
ArseniyShestakov 10 年之前
父节点
当前提交
d3c8ca7c1c
共有 7 个文件被更改,包括 47 次插入26 次删除
  1. 9 7
      lib/CGameState.cpp
  2. 1 1
      lib/CGameState.h
  3. 24 2
      lib/CPathfinder.cpp
  4. 1 0
      lib/CPathfinder.h
  5. 4 9
      lib/mapObjects/CGHeroInstance.cpp
  6. 2 3
      lib/mapObjects/CGHeroInstance.h
  7. 6 4
      server/CGameHandler.cpp

+ 9 - 7
lib/CGameState.cpp

@@ -2100,7 +2100,7 @@ void CGameState::getNeighbours(const TerrainTile &srct, int3 tile, std::vector<i
 	}
 }
 
-int CGameState::getMovementCost(const CGHeroInstance *h, const int3 &src, const int3 &dest, int remainingMovePoints, bool checkLast)
+int CGameState::getMovementCost(const CGHeroInstance *h, const int3 &src, const int3 &dest, int remainingMovePoints, const int &turn, bool checkLast)
 {
 	if(src == dest) //same tile
 		return 0;
@@ -2109,19 +2109,21 @@ int CGameState::getMovementCost(const CGHeroInstance *h, const int3 &src, const
 		&d = map->getTile(dest);
 
 	//get basic cost
-	int ret = h->getTileCost(d,s);
+	int ret = h->getTileCost(d, s, turn);
 
-	if(d.blocked && h->canFly())
+	auto flyBonus = h->getBonusAtTurn(Bonus::FLYING_MOVEMENT, turn);
+	auto waterWalkingBonus = h->getBonusAtTurn(Bonus::WATER_WALKING, turn);
+	if(d.blocked && flyBonus)
 	{
-		ret *= (100.0 + h->valOfBonuses(Bonus::FLYING_MOVEMENT)) / 100.0;
+		ret *= (100.0 + flyBonus->val) / 100.0;
 	}
 	else if(d.terType == ETerrainType::WATER)
 	{
 		if(h->boat && s.hasFavourableWinds() && d.hasFavourableWinds()) //Favourable Winds
 			ret *= 0.666;
-		else if(!h->boat && h->canWalkOnSea())
+		else if(!h->boat && waterWalkingBonus)
 		{
-			ret *= (100.0 + h->valOfBonuses(Bonus::WATER_WALKING)) / 100.0;
+			ret *= (100.0 + waterWalkingBonus->val) / 100.0;
 		}
 	}
 
@@ -2145,7 +2147,7 @@ int CGameState::getMovementCost(const CGHeroInstance *h, const int3 &src, const
 		getNeighbours(d, dest, vec, s.terType != ETerrainType::WATER, true);
 		for(auto & elem : vec)
 		{
-			int fcost = getMovementCost(h, dest, elem, left, false);
+			int fcost = getMovementCost(h, dest, elem, left, turn, false);
 			if(fcost <= left)
 			{
 				return ret;

+ 1 - 1
lib/CGameState.h

@@ -340,7 +340,7 @@ public:
 	bool isVisible(const CGObjectInstance *obj, boost::optional<PlayerColor> player);
 
 	void getNeighbours(const TerrainTile &srct, int3 tile, std::vector<int3> &vec, const boost::logic::tribool &onLand, bool limitCoastSailing);
-	int getMovementCost(const CGHeroInstance *h, const int3 &src, const int3 &dest, int remainingMovePoints=-1, bool checkLast=true);
+	int getMovementCost(const CGHeroInstance * h, const int3 &src, const int3 &dest, int remainingMovePoints =- 1, const int &turn = 0, bool checkLast = true);
 	int getDate(Date::EDateType mode=Date::DAY) const; //mode=0 - total days in game, mode=1 - day of week, mode=2 - current week, mode=3 - current month
 
 	// ----- getters, setters -----

+ 24 - 2
lib/CPathfinder.cpp

@@ -48,9 +48,9 @@ CPathfinder::CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance
 		throw std::runtime_error("Wrong checksum");
 	}
 
-	if(hero->canFly())
+	if(hero->getBonusAtTurn(Bonus::FLYING_MOVEMENT))
 		options.useFlying = true;
-	if(hero->canWalkOnSea())
+	if(hero->getBonusAtTurn(Bonus::WATER_WALKING))
 		options.useWaterWalking = true;
 	if(CGWhirlpool::isProtected(hero))
 		options.useTeleportWhirlpool = true;
@@ -132,6 +132,9 @@ void CPathfinder::calculatePaths()
 				if(!passOneTurnLimitCheck(cp->turns != turn))
 					continue;
 
+				if(!isLayerAvailable(i, turn))
+					continue;
+
 				if(cp->layer != i && !isLayerTransitionPossible())
 					continue;
 
@@ -274,6 +277,25 @@ void CPathfinder::addTeleportExits(bool noTeleportExcludes)
 	}
 }
 
+bool CPathfinder::isLayerAvailable(const ELayer &layer, const int &turn) const
+{
+	switch(layer)
+	{
+		case ELayer::AIR:
+			if(!hero->getBonusAtTurn(Bonus::FLYING_MOVEMENT, turn))
+				return false;
+
+			break;
+
+		case ELayer::WATER:
+			if(!hero->getBonusAtTurn(Bonus::WATER_WALKING, turn))
+				return false;
+			break;
+	}
+
+	return true;
+}
+
 bool CPathfinder::isLayerTransitionPossible() const
 {
 	if((cp->layer == ELayer::AIR || cp->layer ==  ELayer::WATER)

+ 1 - 0
lib/CPathfinder.h

@@ -154,6 +154,7 @@ private:
 	void addNeighbours(const int3 &coord);
 	void addTeleportExits(bool noTeleportExcludes = false);
 
+	bool isLayerAvailable(const ELayer &layer, const int &turn) const;
 	bool isLayerTransitionPossible() const;
 	bool isMovementToDestPossible();
 	bool isMovementAfterDestPossible() const;

+ 4 - 9
lib/mapObjects/CGHeroInstance.cpp

@@ -56,7 +56,7 @@ static int lowestSpeed(const CGHeroInstance * chi)
 	return ret;
 }
 
-ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &from) const
+ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &from, const int &turn) const
 {
 	unsigned ret = GameConstants::BASE_MOVEMENT_COST;
 
@@ -80,7 +80,7 @@ ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &fro
 			break;
 		}
 	}
-	else if(!hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType))
+	else if(!getBonusAtTurn(Bonus::NO_TERRAIN_PENALTY, turn, from.terType))
 	{
 		// NOTE: in H3 neutral stacks will ignore terrain penalty only if placed as topmost stack(s) in hero army.
 		// This is clearly bug in H3 however intended behaviour is not clear.
@@ -129,14 +129,9 @@ int3 CGHeroInstance::getPosition(bool h3m) const //h3m=true - returns position o
 	}
 }
 
-bool CGHeroInstance::canFly() const
+const Bonus * CGHeroInstance::getBonusAtTurn(const Bonus::BonusType &type, const int &turn, const TBonusSubtype &subType) const
 {
-	return hasBonusOfType(Bonus::FLYING_MOVEMENT);
-}
-
-bool CGHeroInstance::canWalkOnSea() const
-{
-	return hasBonusOfType(Bonus::WATER_WALKING);
+	return getBonus(Selector::type(type).And(Selector::days(turn)).And(Selector::subtype(subType)));
 }
 
 ui8 CGHeroInstance::getSecSkillLevel(SecondarySkill skill) const

+ 2 - 3
lib/mapObjects/CGHeroInstance.h

@@ -129,12 +129,11 @@ public:
 	EAlignment::EAlignment getAlignment() const;
 	const std::string &getBiography() const;
 	bool needsLastStack()const override;
-	ui32 getTileCost(const TerrainTile &dest, const TerrainTile &from) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling
+	ui32 getTileCost(const TerrainTile &dest, const TerrainTile &from, const int &turn) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling
 	ui32 getLowestCreatureSpeed() const;
 	int3 getPosition(bool h3m = false) const; //h3m=true - returns position of hero object; h3m=false - returns position of hero 'manifestation'
 	si32 manaRegain() const; //how many points of mana can hero regain "naturally" in one day
-	bool canFly() const;
-	bool canWalkOnSea() const;
+	const Bonus * getBonusAtTurn(const Bonus::BonusType &type, const int &turn = 0, const TBonusSubtype &subType = -1) const;
 	int getCurrentLuck(int stack=-1, bool town=false) const;
 	int getSpellCost(const CSpell *sp) const; //do not use during battles -> bonuses from army would be ignored
 

+ 6 - 4
server/CGameHandler.cpp

@@ -1779,12 +1779,14 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo
 	tmh.movePoints = h->movement;
 
 	//check if destination tile is available
+	bool canFly = h->getBonusAtTurn(Bonus::FLYING_MOVEMENT);
+	bool canWalkOnSea = h->getBonusAtTurn(Bonus::WATER_WALKING);
 
 	//it's a rock or blocked and not visitable tile
 	//OR hero is on land and dest is water and (there is not present only one object - boat)
-	if(((t.terType == ETerrainType::ROCK  ||  (t.blocked && !t.visitable && !h->canFly() ))
+	if(((t.terType == ETerrainType::ROCK  ||  (t.blocked && !t.visitable && !canFly))
 			&& complain("Cannot move hero, destination tile is blocked!"))
-		|| ((!h->boat && !h->canWalkOnSea() && !h->canFly() && t.terType == ETerrainType::WATER && (t.visitableObjects.size() < 1 ||  (t.visitableObjects.back()->ID != Obj::BOAT && t.visitableObjects.back()->ID != Obj::HERO)))  //hero is not on boat/water walking and dst water tile doesn't contain boat/hero (objs visitable from land) -> we test back cause boat may be on top of another object (#276)
+		|| ((!h->boat && !canWalkOnSea && !canFly && t.terType == ETerrainType::WATER && (t.visitableObjects.size() < 1 ||  (t.visitableObjects.back()->ID != Obj::BOAT && t.visitableObjects.back()->ID != Obj::HERO)))  //hero is not on boat/water walking and dst water tile doesn't contain boat/hero (objs visitable from land) -> we test back cause boat may be on top of another object (#276)
 			&& complain("Cannot move hero, destination tile is on water!"))
 		|| ((h->boat && t.terType != ETerrainType::WATER && t.blocked)
 			&& complain("Cannot disembark hero, tile is blocked!"))
@@ -1794,7 +1796,7 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo
 			&& complain("Can not move garrisoned hero!"))
 		|| ((h->movement < cost  &&  dst != h->pos  &&  !teleporting)
 			&& complain("Hero doesn't have any movement points left!"))
-		|| ((transit && !h->canFly() && !CGTeleport::isTeleport(t.topVisitableObj()))
+		|| ((transit && !canFly && !CGTeleport::isTeleport(t.topVisitableObj()))
 			&& complain("Hero cannot transit over this tile!"))
 		/*|| (states.checkFlag(h->tempOwner, &PlayerStatus::engagedIntoBattle)
 			&& complain("Cannot move hero during the battle"))*/)
@@ -1913,7 +1915,7 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo
 			if(CGTeleport::isTeleport(t.topVisitableObj()))
 				visitDest = DONT_VISIT_DEST;
 
-			if(h->canFly())
+			if(canFly)
 			{
 				lookForGuards = IGNORE_GUARDS;
 				visitDest = DONT_VISIT_DEST;