mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Merge pull request #133 from vcmi/feature/pathfinderLayers
Pathfinder layers and related changes: fly and water walking implemented
This commit is contained in:
		| @@ -2061,101 +2061,6 @@ PlayerRelations::PlayerRelations CGameState::getPlayerRelations( PlayerColor col | ||||
| 	return PlayerRelations::ENEMIES; | ||||
| } | ||||
|  | ||||
| void CGameState::getNeighbours(const TerrainTile &srct, int3 tile, std::vector<int3> &vec, const boost::logic::tribool &onLand, bool limitCoastSailing) | ||||
| { | ||||
| 	static const int3 dirs[] = { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0), | ||||
| 					int3(1,1,0),int3(-1,1,0),int3(1,-1,0),int3(-1,-1,0) }; | ||||
|  | ||||
| 	//vec.reserve(8); //optimization | ||||
| 	for (auto & dir : dirs) | ||||
| 	{ | ||||
| 		const int3 hlp = tile + dir; | ||||
| 		if(!map->isInTheMap(hlp)) | ||||
| 			continue; | ||||
|  | ||||
| 		const TerrainTile &hlpt = map->getTile(hlp); | ||||
|  | ||||
| // 		//we cannot visit things from blocked tiles | ||||
| // 		if(srct.blocked && !srct.visitable && hlpt.visitable && srct.blockingObjects.front()->ID != HEROI_TYPE) | ||||
| // 		{ | ||||
| // 			continue; | ||||
| // 		} | ||||
|  | ||||
|         if(srct.terType == ETerrainType::WATER && limitCoastSailing && hlpt.terType == ETerrainType::WATER && dir.x && dir.y) //diagonal move through water | ||||
| 		{ | ||||
| 			int3 hlp1 = tile, | ||||
| 				hlp2 = tile; | ||||
| 			hlp1.x += dir.x; | ||||
| 			hlp2.y += dir.y; | ||||
|  | ||||
|             if(map->getTile(hlp1).terType != ETerrainType::WATER || map->getTile(hlp2).terType != ETerrainType::WATER) | ||||
| 				continue; | ||||
| 		} | ||||
|  | ||||
|         if((indeterminate(onLand)  ||  onLand == (hlpt.terType!=ETerrainType::WATER) ) | ||||
|             && hlpt.terType != ETerrainType::ROCK) | ||||
| 		{ | ||||
| 			vec.push_back(hlp); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| int CGameState::getMovementCost(const CGHeroInstance *h, const int3 &src, const int3 &dest, int remainingMovePoints, bool checkLast) | ||||
| { | ||||
| 	if(src == dest) //same tile | ||||
| 		return 0; | ||||
|  | ||||
| 	TerrainTile &s = map->getTile(src), | ||||
| 		&d = map->getTile(dest); | ||||
|  | ||||
| 	//get basic cost | ||||
| 	int ret = h->getTileCost(d,s); | ||||
|  | ||||
| 	if(d.blocked && h->canFly()) | ||||
| 	{ | ||||
| 		ret *= (100.0 + h->valOfBonuses(Bonus::FLYING_MOVEMENT)) / 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()) | ||||
| 		{ | ||||
| 			ret *= (100.0 + h->valOfBonuses(Bonus::WATER_WALKING)) / 100.0; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if(src.x != dest.x  &&  src.y != dest.y) //it's diagonal move | ||||
| 	{ | ||||
| 		int old = ret; | ||||
| 		ret *= 1.414213; | ||||
| 		//diagonal move costs too much but normal move is possible - allow diagonal move for remaining move points | ||||
| 		if(ret > remainingMovePoints  &&  remainingMovePoints >= old) | ||||
| 		{ | ||||
| 			return remainingMovePoints; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|  | ||||
| 	int left = remainingMovePoints-ret; | ||||
| 	if(checkLast  &&  left > 0  &&  remainingMovePoints-ret < 250) //it might be the last tile - if no further move possible we take all move points | ||||
| 	{ | ||||
| 		std::vector<int3> vec; | ||||
| 		vec.reserve(8); //optimization | ||||
| 		getNeighbours(d, dest, vec, s.terType != ETerrainType::WATER, true); | ||||
| 		for(auto & elem : vec) | ||||
| 		{ | ||||
| 			int fcost = getMovementCost(h, dest, elem, left, false); | ||||
| 			if(fcost <= left) | ||||
| 			{ | ||||
| 				return ret; | ||||
| 			} | ||||
| 		} | ||||
| 		ret = remainingMovePoints; | ||||
| 	} | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| void CGameState::apply(CPack *pack) | ||||
| { | ||||
| 	ui16 typ = typeList.getTypeID(pack); | ||||
|   | ||||
| @@ -339,8 +339,6 @@ public: | ||||
| 	bool isVisible(int3 pos, PlayerColor player); | ||||
| 	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 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 ----- | ||||
|   | ||||
							
								
								
									
										1095
									
								
								lib/CPathfinder.cpp
									
									
									
									
									
								
							
							
						
						
									
										1095
									
								
								lib/CPathfinder.cpp
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -5,6 +5,8 @@ | ||||
| #include "IGameCallback.h" | ||||
| #include "int3.h" | ||||
|  | ||||
| #include <boost/heap/priority_queue.hpp> | ||||
|  | ||||
| /* | ||||
|  * CPathfinder.h, part of VCMI engine | ||||
|  * | ||||
| @@ -18,26 +20,45 @@ | ||||
| class CGHeroInstance; | ||||
| class CGObjectInstance; | ||||
| struct TerrainTile; | ||||
| class CPathfinderHelper; | ||||
|  | ||||
| struct DLL_LINKAGE CGPathNode | ||||
| { | ||||
| 	enum EAccessibility | ||||
| 	typedef EPathfindingLayer ELayer; | ||||
|  | ||||
| 	enum ENodeAction : ui8 | ||||
| 	{ | ||||
| 		UNKNOWN = 0, | ||||
| 		EMBARK = 1, | ||||
| 		DISEMBARK, | ||||
| 		NORMAL, | ||||
| 		BATTLE, | ||||
| 		VISIT, | ||||
| 		BLOCKING_VISIT | ||||
| 	}; | ||||
|  | ||||
| 	enum EAccessibility : ui8 | ||||
| 	{ | ||||
| 		NOT_SET = 0, | ||||
| 		ACCESSIBLE = 1, //tile can be entered and passed | ||||
| 		VISITABLE, //tile can be entered as the last tile in path | ||||
| 		BLOCKVIS,  //visitable from neighbouring tile but not passable | ||||
| 		FLYABLE, //can only be accessed in air layer | ||||
| 		BLOCKED //tile can't be entered nor visited | ||||
| 	}; | ||||
|  | ||||
| 	EAccessibility accessible; | ||||
| 	ui8 land; | ||||
| 	ui8 turns; //how many turns we have to wait before reachng the tile - 0 means current turn | ||||
| 	ui32 moveRemains; //remaining tiles after hero reaches the tile | ||||
| 	CGPathNode * theNodeBefore; | ||||
| 	int3 coord; //coordinates | ||||
| 	ui32 moveRemains; //remaining tiles after hero reaches the tile | ||||
| 	ui8 turns; //how many turns we have to wait before reachng the tile - 0 means current turn | ||||
| 	ELayer layer; | ||||
| 	EAccessibility accessible; | ||||
| 	ENodeAction action; | ||||
| 	bool locked; | ||||
|  | ||||
| 	CGPathNode(); | ||||
| 	void reset(); | ||||
| 	void update(const int3 & Coord, const ELayer Layer, const EAccessibility Accessible); | ||||
| 	bool reachable() const; | ||||
| }; | ||||
|  | ||||
| @@ -52,27 +73,36 @@ struct DLL_LINKAGE CGPath | ||||
|  | ||||
| struct DLL_LINKAGE CPathsInfo | ||||
| { | ||||
| 	typedef EPathfindingLayer ELayer; | ||||
|  | ||||
| 	mutable boost::mutex pathMx; | ||||
|  | ||||
| 	const CGHeroInstance *hero; | ||||
| 	const CGHeroInstance * hero; | ||||
| 	int3 hpos; | ||||
| 	int3 sizes; | ||||
| 	CGPathNode ***nodes; //[w][h][level] | ||||
| 	boost::multi_array<CGPathNode, 4> nodes; //[w][h][level][layer] | ||||
|  | ||||
| 	CPathsInfo(const int3 &Sizes); | ||||
| 	CPathsInfo(const int3 & Sizes); | ||||
| 	~CPathsInfo(); | ||||
| 	const CGPathNode * getPathInfo( int3 tile ) const; | ||||
| 	bool getPath(const int3 &dst, CGPath &out) const; | ||||
| 	int getDistance( int3 tile ) const; | ||||
| 	const CGPathNode * getPathInfo(const int3 & tile) const; | ||||
| 	bool getPath(CGPath & out, const int3 & dst) const; | ||||
| 	int getDistance(const int3 & tile) const; | ||||
| 	const CGPathNode * getNode(const int3 & coord) const; | ||||
|  | ||||
| 	CGPathNode * getNode(const int3 & coord, const ELayer layer); | ||||
| }; | ||||
|  | ||||
| class CPathfinder : private CGameInfoCallback | ||||
| { | ||||
| public: | ||||
| 	CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero); | ||||
| 	friend class CPathfinderHelper; | ||||
|  | ||||
| 	CPathfinder(CPathsInfo & _out, CGameState * _gs, const CGHeroInstance * _hero); | ||||
| 	void calculatePaths(); //calculates possible paths for hero, uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists | ||||
|  | ||||
| private: | ||||
| 	typedef EPathfindingLayer ELayer; | ||||
|  | ||||
| 	struct PathfinderOptions | ||||
| 	{ | ||||
| 		bool useFlying; | ||||
| @@ -83,42 +113,153 @@ private: | ||||
| 		bool useTeleportOneWayRandom; // One-way monoliths with more than one known exit | ||||
| 		bool useTeleportWhirlpool; // Force enabled if hero protected or unaffected (have one stack of one creature) | ||||
|  | ||||
| 		/// TODO: Find out with client and server code, merge with normal teleporters. | ||||
| 		/// Likely proper implementation would require some refactoring of CGTeleport. | ||||
| 		/// So for now this is unfinished and disabled by default. | ||||
| 		bool useCastleGate; | ||||
|  | ||||
| 		/// If true transition into air layer only possible from initial node. | ||||
| 		/// This is drastically decrease path calculation complexity (and time). | ||||
| 		/// Downside is less MP effective paths calculation. | ||||
| 		/// | ||||
| 		/// TODO: If this option end up useful for slow devices it's can be improved: | ||||
| 		/// - Allow transition into air layer not only from initial position, but also from teleporters. | ||||
| 		///   Movement into air can be also allowed when hero disembarked. | ||||
| 		/// - Other idea is to allow transition into air within certain radius of N tiles around hero. | ||||
| 		///   Patrol support need similar functionality so it's won't be ton of useless code. | ||||
| 		///   Such limitation could be useful as it's can be scaled depend on device performance. | ||||
| 		bool lightweightFlyingMode; | ||||
|  | ||||
| 		/// This option enable one turn limitation for flying and water walking. | ||||
| 		/// So if we're out of MP while cp is blocked or water tile we won't add dest tile to queue. | ||||
| 		/// | ||||
| 		/// Following imitation is default H3 mechanics, but someone may want to disable it in mods. | ||||
| 		/// After all this limit should benefit performance on maps with tons of water or blocked tiles. | ||||
| 		/// | ||||
| 		/// TODO: | ||||
| 		/// - Behavior when option is disabled not implemented and will lead to crashes. | ||||
| 		bool oneTurnSpecialLayersLimit; | ||||
|  | ||||
| 		/// VCMI have different movement rules to solve flaws original engine has. | ||||
| 		/// If this option enabled you'll able to do following things in fly: | ||||
| 		/// - Move from blocked tiles to visitable one | ||||
| 		/// - Move from guarded tiles to blockvis tiles without being attacked | ||||
| 		/// - Move from guarded tiles to guarded visitable tiles with being attacked after | ||||
| 		/// TODO: | ||||
| 		/// - Option should also allow same tile land <-> air layer transitions. | ||||
| 		///   Current implementation only allow go into (from) air layer only to neighbour tiles. | ||||
| 		///   I find it's reasonable limitation, but it's will make some movements more expensive than in H3. | ||||
| 		bool originalMovementRules; | ||||
|  | ||||
| 		PathfinderOptions(); | ||||
| 	} options; | ||||
|  | ||||
| 	CPathsInfo &out; | ||||
| 	const CGHeroInstance *hero; | ||||
| 	CPathsInfo & out; | ||||
| 	const CGHeroInstance * hero; | ||||
| 	const std::vector<std::vector<std::vector<ui8> > > &FoW; | ||||
| 	unique_ptr<CPathfinderHelper> hlp; | ||||
|  | ||||
| 	std::list<CGPathNode*> mq; //BFS queue -> nodes to be checked | ||||
| 	struct NodeComparer | ||||
| 	{ | ||||
| 		bool operator()(const CGPathNode * lhs, const CGPathNode * rhs) const | ||||
| 		{ | ||||
| 			if(rhs->turns > lhs->turns) | ||||
| 				return false; | ||||
| 			else if(rhs->turns == lhs->turns && rhs->moveRemains < lhs->moveRemains) | ||||
| 				return false; | ||||
|  | ||||
| 			return true; | ||||
| 		} | ||||
| 	}; | ||||
| 	boost::heap::priority_queue<CGPathNode *, boost::heap::compare<NodeComparer> > pq; | ||||
|  | ||||
| 	std::vector<int3> neighbourTiles; | ||||
| 	std::vector<int3> neighbours; | ||||
|  | ||||
| 	CGPathNode *cp; //current (source) path node -> we took it from the queue | ||||
| 	CGPathNode *dp; //destination node -> it's a neighbour of cp that we consider | ||||
| 	const TerrainTile *ct, *dt; //tile info for both nodes | ||||
| 	const CGObjectInstance *sTileObj; | ||||
| 	ui8 useEmbarkCost; //0 - usual movement; 1 - embark; 2 - disembark | ||||
| 	CGPathNode * cp; //current (source) path node -> we took it from the queue | ||||
| 	CGPathNode * dp; //destination node -> it's a neighbour of cp that we consider | ||||
| 	const TerrainTile * ct, * dt; //tile info for both nodes | ||||
| 	const CGObjectInstance * ctObj, * dtObj; | ||||
| 	CGPathNode::ENodeAction destAction; | ||||
|  | ||||
| 	void addNeighbours(const int3 &coord); | ||||
| 	void addTeleportExits(bool noTeleportExcludes = false); | ||||
| 	void addNeighbours(); | ||||
| 	void addTeleportExits(); | ||||
|  | ||||
| 	bool isMovementPossible(); //checks if current move will be between sea<->land. If so, checks it legality (returns false if movement is not possible) and sets useEmbarkCost | ||||
| 	bool checkDestinationTile(); | ||||
| 	bool isLayerTransitionPossible(const ELayer dstLayer) const; | ||||
| 	bool isLayerTransitionPossible() const; | ||||
| 	bool isMovementToDestPossible() const; | ||||
| 	bool isMovementAfterDestPossible() const; | ||||
| 	CGPathNode::ENodeAction getDestAction() const; | ||||
|  | ||||
| 	int3 getSourceGuardPosition(); | ||||
| 	bool isSourceGuarded(); | ||||
| 	bool isDestinationGuarded(); | ||||
| 	bool isDestinationGuardian(); | ||||
| 	bool isSourceInitialPosition() const; | ||||
| 	bool isSourceVisitableObj() const; | ||||
| 	bool isSourceGuarded() const; | ||||
| 	bool isDestVisitableObj() const; | ||||
| 	bool isDestinationGuarded(const bool ignoreAccessibility = true) const; | ||||
| 	bool isDestinationGuardian() const; | ||||
|  | ||||
| 	void initializeGraph(); | ||||
|  | ||||
| 	CGPathNode *getNode(const int3 &coord); | ||||
| 	CGPathNode::EAccessibility evaluateAccessibility(const int3 &pos, const TerrainTile *tinfo) const; | ||||
| 	bool canMoveBetween(const int3 &a, const int3 &b) const; //checks only for visitable objects that may make moving between tiles impossible, not other conditions (like tiles itself accessibility) | ||||
| 	CGPathNode::EAccessibility evaluateAccessibility(const int3 & pos, const TerrainTile * tinfo, const ELayer layer) const; | ||||
| 	bool isVisitableObj(const CGObjectInstance * obj, const ELayer layer) const; | ||||
| 	bool canSeeObj(const CGObjectInstance * obj) const; | ||||
| 	bool canMoveBetween(const int3 & a, const int3 & b) const; //checks only for visitable objects that may make moving between tiles impossible, not other conditions (like tiles itself accessibility) | ||||
|  | ||||
| 	bool isAllowedTeleportEntrance(const CGTeleport * obj) const; | ||||
| 	bool addTeleportTwoWay(const CGTeleport * obj) const; | ||||
| 	bool addTeleportOneWay(const CGTeleport * obj) const; | ||||
| 	bool addTeleportOneWayRandom(const CGTeleport * obj) const; | ||||
| 	bool addTeleportWhirlpool(const CGWhirlpool * obj) const; | ||||
|  | ||||
| }; | ||||
|  | ||||
| struct DLL_LINKAGE TurnInfo | ||||
| { | ||||
| 	/// 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::vector<bool> noTerrainPenalty; | ||||
| 		bool freeShipBoarding; | ||||
| 		bool flyingMovement; | ||||
| 		int flyingMovementVal; | ||||
| 		bool waterWalking; | ||||
| 		int waterWalkingVal; | ||||
|  | ||||
| 		BonusCache(TBonusListPtr bonusList); | ||||
| 	}; | ||||
| 	unique_ptr<BonusCache> bonusCache; | ||||
|  | ||||
| 	const CGHeroInstance * hero; | ||||
| 	TBonusListPtr bonuses; | ||||
| 	mutable int maxMovePointsLand; | ||||
| 	mutable int maxMovePointsWater; | ||||
| 	int nativeTerrain; | ||||
|  | ||||
| 	TurnInfo(const CGHeroInstance * Hero, const int Turn = 0); | ||||
| 	bool isLayerAvailable(const EPathfindingLayer layer) const; | ||||
| 	bool hasBonusOfType(const Bonus::BonusType type, const int subtype = -1) const; | ||||
| 	int valOfBonuses(const Bonus::BonusType type, const int subtype = -1) const; | ||||
| 	int getMaxMovePoints(const EPathfindingLayer layer) const; | ||||
| }; | ||||
|  | ||||
| class DLL_LINKAGE CPathfinderHelper | ||||
| { | ||||
| public: | ||||
| 	CPathfinderHelper(const CGHeroInstance * Hero, const CPathfinder::PathfinderOptions & Options); | ||||
| 	void updateTurnInfo(const int turn = 0); | ||||
| 	bool isLayerAvailable(const EPathfindingLayer layer) const; | ||||
| 	const TurnInfo * getTurnInfo() const; | ||||
| 	bool hasBonusOfType(const Bonus::BonusType type, const int subtype = -1) const; | ||||
| 	int getMaxMovePoints(const EPathfindingLayer layer) const; | ||||
|  | ||||
| 	static void getNeighbours(const CMap * map, const TerrainTile & srct, const int3 & tile, std::vector<int3> & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing); | ||||
|  | ||||
| 	static int getMovementCost(const CGHeroInstance * h, const int3 & src, const int3 & dst, const TerrainTile * ct, const TerrainTile * dt, const int remainingMovePoints =- 1, const TurnInfo * ti = nullptr, const bool checkLast = true); | ||||
| 	static int getMovementCost(const CGHeroInstance * h, const int3 & dst); | ||||
|  | ||||
| private: | ||||
| 	int turn; | ||||
| 	const CGHeroInstance * hero; | ||||
| 	std::vector<TurnInfo *> turnsInfo; | ||||
| 	const CPathfinder::PathfinderOptions & options; | ||||
| }; | ||||
|   | ||||
| @@ -25,56 +25,6 @@ const PlayerColor PlayerColor::NEUTRAL = PlayerColor(255); | ||||
| const PlayerColor PlayerColor::PLAYER_LIMIT = PlayerColor(PLAYER_LIMIT_I); | ||||
| const TeamID TeamID::NO_TEAM = TeamID(255); | ||||
|  | ||||
| #define ID_LIKE_OPERATORS_INTERNAL(A, B, AN, BN)	\ | ||||
| bool operator==(const A & a, const B & b)			\ | ||||
| {													\ | ||||
| 	return AN == BN ;								\ | ||||
| }													\ | ||||
| bool operator!=(const A & a, const B & b)			\ | ||||
| {													\ | ||||
| 	return AN != BN ;								\ | ||||
| }													\ | ||||
| bool operator<(const A & a, const B & b)			\ | ||||
| {													\ | ||||
| 	return AN < BN ;								\ | ||||
| }													\ | ||||
| bool operator<=(const A & a, const B & b)			\ | ||||
| {													\ | ||||
| 	return AN <= BN ;								\ | ||||
| }													\ | ||||
| bool operator>(const A & a, const B & b)			\ | ||||
| {													\ | ||||
| 	return AN > BN ;								\ | ||||
| }													\ | ||||
| bool operator>=(const A & a, const B & b)			\ | ||||
| {													\ | ||||
| 	return AN >= BN ;								\ | ||||
| } | ||||
|  | ||||
| #define ID_LIKE_OPERATORS(CLASS_NAME, ENUM_NAME)	\ | ||||
| 	ID_LIKE_OPERATORS_INTERNAL(CLASS_NAME, CLASS_NAME, a.num, b.num)	\ | ||||
| 	ID_LIKE_OPERATORS_INTERNAL(CLASS_NAME, ENUM_NAME, a.num, b)	\ | ||||
| 	ID_LIKE_OPERATORS_INTERNAL(ENUM_NAME, CLASS_NAME, a, b.num) | ||||
|  | ||||
|  | ||||
| ID_LIKE_OPERATORS(SecondarySkill, SecondarySkill::ESecondarySkill) | ||||
|  | ||||
| ID_LIKE_OPERATORS(Obj, Obj::EObj) | ||||
|  | ||||
| ID_LIKE_OPERATORS(ETerrainType, ETerrainType::EETerrainType) | ||||
|  | ||||
| ID_LIKE_OPERATORS(ArtifactID, ArtifactID::EArtifactID) | ||||
|  | ||||
| ID_LIKE_OPERATORS(ArtifactPosition, ArtifactPosition::EArtifactPosition) | ||||
|  | ||||
| ID_LIKE_OPERATORS(CreatureID, CreatureID::ECreatureID) | ||||
|  | ||||
| ID_LIKE_OPERATORS(SpellID, SpellID::ESpellID) | ||||
|  | ||||
| ID_LIKE_OPERATORS(BuildingID, BuildingID::EBuildingID) | ||||
|  | ||||
| ID_LIKE_OPERATORS(BFieldType, BFieldType::EBFieldType) | ||||
|  | ||||
| CArtifact * ArtifactID::toArtifact() const | ||||
| { | ||||
| 	return VLC->arth->artifacts[*this]; | ||||
| @@ -130,7 +80,7 @@ std::ostream & operator<<(std::ostream & os, const Battle::ActionType actionType | ||||
| 	else return os << it->second; | ||||
| } | ||||
|  | ||||
| std::ostream & operator<<(std::ostream & os, const ETerrainType actionType) | ||||
| std::ostream & operator<<(std::ostream & os, const ETerrainType terrainType) | ||||
| { | ||||
| 	static const std::map<ETerrainType::EETerrainType, std::string> terrainTypeToString = | ||||
| 	{ | ||||
| @@ -147,9 +97,10 @@ std::ostream & operator<<(std::ostream & os, const ETerrainType actionType) | ||||
| 		DEFINE_ELEMENT(LAVA), | ||||
| 		DEFINE_ELEMENT(WATER), | ||||
| 		DEFINE_ELEMENT(ROCK) | ||||
| 	#undef DEFINE_ELEMENT | ||||
| 	}; | ||||
|  | ||||
| 	auto it = terrainTypeToString.find(actionType.num); | ||||
| 	auto it = terrainTypeToString.find(terrainType.num); | ||||
| 	if (it == terrainTypeToString.end()) return os << "<Unknown type>"; | ||||
| 	else return os << it->second; | ||||
| } | ||||
| @@ -160,3 +111,23 @@ std::string ETerrainType::toString() const | ||||
| 	ss << *this; | ||||
| 	return ss.str(); | ||||
| } | ||||
|  | ||||
| std::ostream & operator<<(std::ostream & os, const EPathfindingLayer pathfindingLayer) | ||||
| { | ||||
| 	static const std::map<EPathfindingLayer::EEPathfindingLayer, std::string> pathfinderLayerToString | ||||
| 	{ | ||||
| 	#define DEFINE_ELEMENT(element) {EPathfindingLayer::element, #element} | ||||
| 		DEFINE_ELEMENT(WRONG), | ||||
| 		DEFINE_ELEMENT(AUTO), | ||||
| 		DEFINE_ELEMENT(LAND), | ||||
| 		DEFINE_ELEMENT(SAIL), | ||||
| 		DEFINE_ELEMENT(WATER), | ||||
| 		DEFINE_ELEMENT(AIR), | ||||
| 		DEFINE_ELEMENT(NUM_LAYERS) | ||||
| 	#undef DEFINE_ELEMENT | ||||
| 	}; | ||||
|  | ||||
| 	auto it = pathfinderLayerToString.find(pathfindingLayer.num); | ||||
| 	if (it == pathfinderLayerToString.end()) return os << "<Unknown type>"; | ||||
| 	else return os << it->second; | ||||
| } | ||||
|   | ||||
| @@ -93,18 +93,37 @@ CLASS_NAME & advance(int i)							\ | ||||
| } | ||||
|  | ||||
|  | ||||
| #define ID_LIKE_OPERATORS_INTERNAL_DECLS(A, B)			\ | ||||
| bool DLL_LINKAGE operator==(const A & a, const B & b);	\ | ||||
| bool DLL_LINKAGE operator!=(const A & a, const B & b);	\ | ||||
| bool DLL_LINKAGE operator<(const A & a, const B & b);	\ | ||||
| bool DLL_LINKAGE operator<=(const A & a, const B & b);	\ | ||||
| bool DLL_LINKAGE operator>(const A & a, const B & b);	\ | ||||
| bool DLL_LINKAGE operator>=(const A & a, const B & b); | ||||
| // Operators are performance-critical and to be inlined they must be in header | ||||
| #define ID_LIKE_OPERATORS_INTERNAL(A, B, AN, BN)	\ | ||||
| STRONG_INLINE bool operator==(const A & a, const B & b)			\ | ||||
| {													\ | ||||
| 	return AN == BN ;								\ | ||||
| }													\ | ||||
| STRONG_INLINE bool operator!=(const A & a, const B & b)			\ | ||||
| {													\ | ||||
| 	return AN != BN ;								\ | ||||
| }													\ | ||||
| STRONG_INLINE bool operator<(const A & a, const B & b)			\ | ||||
| {													\ | ||||
| 	return AN < BN ;								\ | ||||
| }													\ | ||||
| STRONG_INLINE bool operator<=(const A & a, const B & b)			\ | ||||
| {													\ | ||||
| 	return AN <= BN ;								\ | ||||
| }													\ | ||||
| STRONG_INLINE bool operator>(const A & a, const B & b)			\ | ||||
| {													\ | ||||
| 	return AN > BN ;								\ | ||||
| }													\ | ||||
| STRONG_INLINE bool operator>=(const A & a, const B & b)			\ | ||||
| {													\ | ||||
| 	return AN >= BN ;								\ | ||||
| } | ||||
|  | ||||
| #define ID_LIKE_OPERATORS_DECLS(CLASS_NAME, ENUM_NAME)			\ | ||||
| 	ID_LIKE_OPERATORS_INTERNAL_DECLS(CLASS_NAME, CLASS_NAME)	\ | ||||
| 	ID_LIKE_OPERATORS_INTERNAL_DECLS(CLASS_NAME, ENUM_NAME)		\ | ||||
| 	ID_LIKE_OPERATORS_INTERNAL_DECLS(ENUM_NAME, CLASS_NAME) | ||||
| #define ID_LIKE_OPERATORS(CLASS_NAME, ENUM_NAME)	\ | ||||
| 	ID_LIKE_OPERATORS_INTERNAL(CLASS_NAME, CLASS_NAME, a.num, b.num)	\ | ||||
| 	ID_LIKE_OPERATORS_INTERNAL(CLASS_NAME, ENUM_NAME, a.num, b)	\ | ||||
| 	ID_LIKE_OPERATORS_INTERNAL(ENUM_NAME, CLASS_NAME, a, b.num) | ||||
|  | ||||
|  | ||||
| #define OP_DECL_INT(CLASS_NAME, OP)					\ | ||||
| @@ -296,7 +315,7 @@ public: | ||||
| 	ESecondarySkill num; | ||||
| }; | ||||
|  | ||||
| ID_LIKE_OPERATORS_DECLS(SecondarySkill, SecondarySkill::ESecondarySkill) | ||||
| ID_LIKE_OPERATORS(SecondarySkill, SecondarySkill::ESecondarySkill) | ||||
|  | ||||
| namespace EAlignment | ||||
| { | ||||
| @@ -384,7 +403,7 @@ public: | ||||
| 	EBuildingID num; | ||||
| }; | ||||
|  | ||||
| ID_LIKE_OPERATORS_DECLS(BuildingID, BuildingID::EBuildingID) | ||||
| ID_LIKE_OPERATORS(BuildingID, BuildingID::EBuildingID) | ||||
|  | ||||
| namespace EBuildingState | ||||
| { | ||||
| @@ -664,7 +683,7 @@ public: | ||||
| 	EObj num; | ||||
| }; | ||||
|  | ||||
| ID_LIKE_OPERATORS_DECLS(Obj, Obj::EObj) | ||||
| ID_LIKE_OPERATORS(Obj, Obj::EObj) | ||||
|  | ||||
| namespace SecSkillLevel | ||||
| { | ||||
| @@ -754,10 +773,29 @@ public: | ||||
| 	std::string toString() const; | ||||
| }; | ||||
|  | ||||
| DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const ETerrainType actionType); | ||||
| DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const ETerrainType terrainType); | ||||
|  | ||||
| ID_LIKE_OPERATORS_DECLS(ETerrainType, ETerrainType::EETerrainType) | ||||
| ID_LIKE_OPERATORS(ETerrainType, ETerrainType::EETerrainType) | ||||
|  | ||||
| class DLL_LINKAGE EPathfindingLayer | ||||
| { | ||||
| public: | ||||
| 	enum EEPathfindingLayer : ui8 | ||||
| 	{ | ||||
| 		LAND = 0, SAIL = 1, WATER, AIR, NUM_LAYERS, WRONG, AUTO | ||||
| 	}; | ||||
|  | ||||
| 	EPathfindingLayer(EEPathfindingLayer _num = WRONG) : num(_num) | ||||
| 	{} | ||||
|  | ||||
| 	ID_LIKE_CLASS_COMMON(EPathfindingLayer, EEPathfindingLayer) | ||||
|  | ||||
| 	EEPathfindingLayer num; | ||||
| }; | ||||
|  | ||||
| DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EPathfindingLayer pathfindingLayer); | ||||
|  | ||||
| ID_LIKE_OPERATORS(EPathfindingLayer, EPathfindingLayer::EEPathfindingLayer) | ||||
|  | ||||
| class BFieldType | ||||
| { | ||||
| @@ -780,7 +818,7 @@ public: | ||||
| 	EBFieldType num; | ||||
| }; | ||||
|  | ||||
| ID_LIKE_OPERATORS_DECLS(BFieldType, BFieldType::EBFieldType) | ||||
| ID_LIKE_OPERATORS(BFieldType, BFieldType::EBFieldType) | ||||
|  | ||||
| namespace EPlayerStatus | ||||
| { | ||||
| @@ -820,7 +858,7 @@ public: | ||||
| 	EArtifactPosition num; | ||||
| }; | ||||
|  | ||||
| ID_LIKE_OPERATORS_DECLS(ArtifactPosition, ArtifactPosition::EArtifactPosition) | ||||
| ID_LIKE_OPERATORS(ArtifactPosition, ArtifactPosition::EArtifactPosition) | ||||
|  | ||||
| class ArtifactID | ||||
| { | ||||
| @@ -865,7 +903,7 @@ public: | ||||
| 	EArtifactID num; | ||||
| }; | ||||
|  | ||||
| ID_LIKE_OPERATORS_DECLS(ArtifactID, ArtifactID::EArtifactID) | ||||
| ID_LIKE_OPERATORS(ArtifactID, ArtifactID::EArtifactID) | ||||
|  | ||||
| class CreatureID | ||||
| { | ||||
| @@ -909,7 +947,7 @@ public: | ||||
| 	ECreatureID num; | ||||
| }; | ||||
|  | ||||
| ID_LIKE_OPERATORS_DECLS(CreatureID, CreatureID::ECreatureID) | ||||
| ID_LIKE_OPERATORS(CreatureID, CreatureID::ECreatureID) | ||||
|  | ||||
| class SpellID | ||||
| { | ||||
| @@ -953,7 +991,7 @@ public: | ||||
| 	ESpellID num; | ||||
| }; | ||||
|  | ||||
| ID_LIKE_OPERATORS_DECLS(SpellID, SpellID::ESpellID) | ||||
| ID_LIKE_OPERATORS(SpellID, SpellID::ESpellID) | ||||
|  | ||||
| enum class ESpellSchool: ui8 | ||||
| { | ||||
| @@ -963,8 +1001,6 @@ enum class ESpellSchool: ui8 | ||||
| 	EARTH 	= 3 | ||||
| }; | ||||
|  | ||||
| ID_LIKE_OPERATORS_DECLS(SpellID, SpellID::ESpellID) | ||||
|  | ||||
| // Typedef declarations | ||||
| typedef ui8 TFaction; | ||||
| typedef si64 TExpType; | ||||
| @@ -975,8 +1011,8 @@ typedef si32 TQuantity; | ||||
| typedef int TRmgTemplateZoneId; | ||||
|  | ||||
| #undef ID_LIKE_CLASS_COMMON | ||||
| #undef ID_LIKE_OPERATORS_DECLS | ||||
| #undef ID_LIKE_OPERATORS_INTERNAL_DECLS | ||||
| #undef ID_LIKE_OPERATORS | ||||
| #undef ID_LIKE_OPERATORS_INTERNAL | ||||
| #undef INSTID_LIKE_CLASS_COMMON | ||||
| #undef OP_DECL_INT | ||||
|  | ||||
|   | ||||
| @@ -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 TurnInfo * ti) const | ||||
| { | ||||
| 	unsigned ret = GameConstants::BASE_MOVEMENT_COST; | ||||
|  | ||||
| @@ -80,30 +80,40 @@ ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &fro | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 	else if(!hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType)) | ||||
| 	else if(ti->nativeTerrain != from.terType && !ti->hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, 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. | ||||
| 		// Current VCMI behaviour will ignore neutrals in calculations so army in VCMI | ||||
| 		// will always have best penalty without any influence from player-defined stacks order | ||||
|  | ||||
| 		for(auto stack : stacks) | ||||
| 		{ | ||||
| 			int nativeTerrain = VLC->townh->factions[stack.second->type->faction]->nativeTerrain; | ||||
| 			if(nativeTerrain != -1 && nativeTerrain != from.terType) | ||||
| 			{ | ||||
| 				ret = VLC->heroh->terrCosts[from.terType]; | ||||
| 				ret -= getSecSkillLevel(SecondarySkill::PATHFINDING) * 25; | ||||
| 				if(ret < GameConstants::BASE_MOVEMENT_COST) | ||||
| 					ret = GameConstants::BASE_MOVEMENT_COST; | ||||
|  | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 		ret = VLC->heroh->terrCosts[from.terType]; | ||||
| 		ret -= getSecSkillLevel(SecondarySkill::PATHFINDING) * 25; | ||||
| 		if(ret < GameConstants::BASE_MOVEMENT_COST) | ||||
| 			ret = GameConstants::BASE_MOVEMENT_COST; | ||||
| 	} | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| int CGHeroInstance::getNativeTerrain() const | ||||
| { | ||||
| 	// 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. | ||||
| 	// Current VCMI behaviour will ignore neutrals in calculations so army in VCMI | ||||
| 	// will always have best penalty without any influence from player-defined stacks order | ||||
|  | ||||
| 	// TODO: What should we do if all hero stacks are neutral creatures? | ||||
| 	int nativeTerrain = -1; | ||||
| 	for(auto stack : stacks) | ||||
| 	{ | ||||
| 		int stackNativeTerrain = VLC->townh->factions[stack.second->type->faction]->nativeTerrain; | ||||
| 		if(stackNativeTerrain == -1) | ||||
| 			continue; | ||||
|  | ||||
| 		if(nativeTerrain == -1) | ||||
| 			nativeTerrain = stackNativeTerrain; | ||||
| 		else if(nativeTerrain != stackNativeTerrain) | ||||
| 			return -1; | ||||
| 	} | ||||
|  | ||||
| 	return nativeTerrain; | ||||
| } | ||||
|  | ||||
| int3 CGHeroInstance::convertPosition(int3 src, bool toh3m) //toh3m=true: manifest->h3m; toh3m=false: h3m->manifest | ||||
| { | ||||
| 	if (toh3m) | ||||
| @@ -129,16 +139,6 @@ int3 CGHeroInstance::getPosition(bool h3m) const //h3m=true - returns position o | ||||
| 	} | ||||
| } | ||||
|  | ||||
| bool CGHeroInstance::canFly() const | ||||
| { | ||||
| 	return hasBonusOfType(Bonus::FLYING_MOVEMENT); | ||||
| } | ||||
|  | ||||
| bool CGHeroInstance::canWalkOnSea() const | ||||
| { | ||||
| 	return hasBonusOfType(Bonus::WATER_WALKING); | ||||
| } | ||||
|  | ||||
| ui8 CGHeroInstance::getSecSkillLevel(SecondarySkill skill) const | ||||
| { | ||||
| 	for(auto & elem : secSkills) | ||||
| @@ -181,8 +181,11 @@ bool CGHeroInstance::canLearnSkill() const | ||||
| 	return secSkills.size() < GameConstants::SKILL_PER_HERO; | ||||
| } | ||||
|  | ||||
| int CGHeroInstance::maxMovePoints(bool onLand) const | ||||
| int CGHeroInstance::maxMovePoints(bool onLand, const TurnInfo * ti) const | ||||
| { | ||||
| 	if(!ti) | ||||
| 		ti = new TurnInfo(this); | ||||
|  | ||||
| 	int base; | ||||
|  | ||||
| 	if(onLand) | ||||
| @@ -201,10 +204,10 @@ int CGHeroInstance::maxMovePoints(bool onLand) const | ||||
| 	} | ||||
|  | ||||
| 	const Bonus::BonusType bt = onLand ? Bonus::LAND_MOVEMENT : Bonus::SEA_MOVEMENT; | ||||
| 	const int bonus = valOfBonuses(Bonus::MOVEMENT) + valOfBonuses(bt); | ||||
| 	const int bonus = ti->valOfBonuses(Bonus::MOVEMENT) + ti->valOfBonuses(bt); | ||||
|  | ||||
| 	const int subtype = onLand ? SecondarySkill::LOGISTICS : SecondarySkill::NAVIGATION; | ||||
| 	const double modifier = valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, subtype) / 100.0; | ||||
| 	const double modifier = ti->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, subtype) / 100.0; | ||||
|  | ||||
| 	return int(base* (1+modifier)) + bonus; | ||||
| } | ||||
| @@ -1171,10 +1174,15 @@ CBonusSystemNode * CGHeroInstance::whereShouldBeAttached(CGameState *gs) | ||||
| 		return CArmedInstance::whereShouldBeAttached(gs); | ||||
| } | ||||
|  | ||||
| int CGHeroInstance::movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark /*= false*/) const | ||||
| int CGHeroInstance::movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark /*= false*/, const TurnInfo * ti) const | ||||
| { | ||||
| 	if(hasBonusOfType(Bonus::FREE_SHIP_BOARDING)) | ||||
| 		return (MPsBefore - basicCost) * static_cast<double>(maxMovePoints(disembark)) / maxMovePoints(!disembark); | ||||
| 	if(!ti) | ||||
| 		ti = new TurnInfo(this); | ||||
|  | ||||
| 	int mp1 = ti->getMaxMovePoints(disembark ? EPathfindingLayer::LAND : EPathfindingLayer::SAIL); | ||||
| 	int mp2 = ti->getMaxMovePoints(disembark ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND); | ||||
| 	if(ti->hasBonusOfType(Bonus::FREE_SHIP_BOARDING)) | ||||
| 		return (MPsBefore - basicCost) * static_cast<double>(mp1) / mp2; | ||||
|  | ||||
| 	return 0; //take all MPs otherwise | ||||
| } | ||||
|   | ||||
| @@ -21,6 +21,7 @@ class CHero; | ||||
| class CGBoat; | ||||
| class CGTownInstance; | ||||
| struct TerrainTile; | ||||
| struct TurnInfo; | ||||
|  | ||||
| class CGHeroPlaceholder : public CGObjectInstance | ||||
| { | ||||
| @@ -129,12 +130,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 TurnInfo * ti) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling | ||||
| 	int getNativeTerrain() const; | ||||
| 	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; | ||||
| 	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 | ||||
|  | ||||
| @@ -161,8 +161,8 @@ public: | ||||
| 	void setSecSkillLevel(SecondarySkill which, int val, bool abs);// abs == 0 - changes by value; 1 - sets to value | ||||
| 	void levelUp(std::vector<SecondarySkill> skills); | ||||
|  | ||||
| 	int maxMovePoints(bool onLand) const; | ||||
| 	int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark = false) const; | ||||
| 	int maxMovePoints(bool onLand, const TurnInfo * ti = nullptr) const; | ||||
| 	int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark = false, const TurnInfo * ti = nullptr) const; | ||||
|  | ||||
| 	static int3 convertPosition(int3 src, bool toh3m); //toh3m=true: manifest->h3m; toh3m=false: h3m->manifest | ||||
| 	double getFightingStrength() const; // takes attack / defense skill into account | ||||
|   | ||||
| @@ -132,11 +132,13 @@ Obj TerrainTile::topVisitableId(bool excludeTop) const | ||||
|  | ||||
| CGObjectInstance * TerrainTile::topVisitableObj(bool excludeTop) const | ||||
| { | ||||
| 	auto visitableObj = visitableObjects; | ||||
| 	if(excludeTop && visitableObj.size()) | ||||
| 		visitableObj.pop_back(); | ||||
| 	if(visitableObjects.empty() || (excludeTop && visitableObjects.size() == 1)) | ||||
| 		return nullptr; | ||||
|  | ||||
| 	return visitableObj.size() ? visitableObj.back() : nullptr; | ||||
| 	if(excludeTop) | ||||
| 		return visitableObjects[visitableObjects.size()-2]; | ||||
|  | ||||
| 	return visitableObjects.back(); | ||||
| } | ||||
|  | ||||
| bool TerrainTile::isCoastal() const | ||||
|   | ||||
		Reference in New Issue
	
	Block a user