Browse Source

Merge pull request #143 from vcmi/feature/patrolSupport

Patrol support for AI heroes
ArseniyShestakov 10 years ago
parent
commit
9e7e5b81e4

+ 1 - 0
ChangeLog

@@ -12,6 +12,7 @@ ADVETURE AI:
 * Fixed AI trying to go through underground rock
 * Fixed several cases causing AI wandering aimlessly
 * AI can again pick best artifacts and exchange artifacts between heroes
+* AI heroes with patrol enabled won't leave patrol area anymore
 
 RANDOM MAP GENERATOR:
 * Changed fractalization algorithm so it can create cycles

+ 44 - 3
lib/CPathfinder.cpp

@@ -38,7 +38,7 @@ CPathfinder::PathfinderOptions::PathfinderOptions()
 }
 
 CPathfinder::CPathfinder(CPathsInfo & _out, CGameState * _gs, const CGHeroInstance * _hero)
-	: CGameInfoCallback(_gs, boost::optional<PlayerColor>()), out(_out), hero(_hero), FoW(getPlayerTeam(hero->tempOwner)->fogOfWarMap)
+	: CGameInfoCallback(_gs, boost::optional<PlayerColor>()), out(_out), hero(_hero), FoW(getPlayerTeam(hero->tempOwner)->fogOfWarMap), patrolTiles({})
 {
 	assert(hero);
 	assert(hero == getHero(hero->id));
@@ -53,6 +53,7 @@ CPathfinder::CPathfinder(CPathsInfo & _out, CGameState * _gs, const CGHeroInstan
 
 	hlp = make_unique<CPathfinderHelper>(hero, options);
 
+	initializePatrol();
 	initializeGraph();
 	neighbourTiles.reserve(8);
 	neighbours.reserve(16);
@@ -96,8 +97,10 @@ void CPathfinder::calculatePaths()
 	CGPathNode * initialNode = out.getNode(out.hpos, hero->boat ? ELayer::SAIL : ELayer::LAND);
 	initialNode->turns = 0;
 	initialNode->moveRemains = hero->movement;
-	pq.push(initialNode);
+	if(isHeroPatrolLocked())
+		return;
 
+	pq.push(initialNode);
 	while(!pq.empty())
 	{
 		cp = pq.top();
@@ -120,6 +123,9 @@ void CPathfinder::calculatePaths()
 		addNeighbours();
 		for(auto & neighbour : neighbours)
 		{
+			if(!isPatrolMovementAllowed(neighbour))
+				continue;
+
 			dt = &gs->map->getTile(neighbour);
 			dtObj = dt->topVisitableObj();
 			for(ELayer i = ELayer::LAND; i <= ELayer::AIR; i.advance(1))
@@ -216,7 +222,9 @@ void CPathfinder::addNeighbours()
 void CPathfinder::addTeleportExits()
 {
 	neighbours.clear();
-	if(!isSourceVisitableObj())
+	/// For now we disable teleports usage for patrol movement
+	/// VCAI not aware about patrol and may stuck while attempt to use teleport
+	if(!isSourceVisitableObj() || patrolState == PATROL_RADIUS)
 		return;
 
 	const CGTeleport * objTeleport = dynamic_cast<const CGTeleport *>(ctObj);
@@ -257,6 +265,22 @@ void CPathfinder::addTeleportExits()
 	}
 }
 
+bool CPathfinder::isHeroPatrolLocked() const
+{
+	return patrolState == PATROL_LOCKED;
+}
+
+bool CPathfinder::isPatrolMovementAllowed(const int3 & dst) const
+{
+	if(patrolState == PATROL_RADIUS)
+	{
+		if(!vstd::contains(patrolTiles, dst))
+			return false;
+	}
+
+	return true;
+}
+
 bool CPathfinder::isLayerTransitionPossible(const ELayer destLayer) const
 {
 	/// No layer transition allowed when previous node action is BATTLE
@@ -565,6 +589,23 @@ bool CPathfinder::isDestinationGuardian() const
 	return gs->guardingCreaturePosition(cp->coord) == dp->coord;
 }
 
+void CPathfinder::initializePatrol()
+{
+	auto state = PATROL_NONE;
+	if(hero->patrol.patrolling && !getPlayer(hero->tempOwner)->human)
+	{
+		if(hero->patrol.patrolRadious)
+		{
+			state = PATROL_RADIUS;
+			gs->getTilesInRange(patrolTiles, hero->patrol.initialPos, hero->patrol.patrolRadious, boost::optional<PlayerColor>(), 0, true);
+		}
+		else
+			state = PATROL_LOCKED;
+	}
+
+	patrolState = state;
+}
+
 void CPathfinder::initializeGraph()
 {
 	auto updateNode = [&](int3 pos, ELayer layer, const TerrainTile * tinfo)

+ 11 - 0
lib/CPathfinder.h

@@ -161,6 +161,13 @@ private:
 	const std::vector<std::vector<std::vector<ui8> > > &FoW;
 	unique_ptr<CPathfinderHelper> hlp;
 
+	enum EPatrolState {
+		PATROL_NONE = 0,
+		PATROL_LOCKED = 1,
+		PATROL_RADIUS
+	} patrolState;
+	std::unordered_set<int3, ShashInt3> patrolTiles;
+
 	struct NodeComparer
 	{
 		bool operator()(const CGPathNode * lhs, const CGPathNode * rhs) const
@@ -187,6 +194,9 @@ private:
 	void addNeighbours();
 	void addTeleportExits();
 
+	bool isHeroPatrolLocked() const;
+	bool isPatrolMovementAllowed(const int3 & dst) const;
+
 	bool isLayerTransitionPossible(const ELayer dstLayer) const;
 	bool isLayerTransitionPossible() const;
 	bool isMovementToDestPossible() const;
@@ -200,6 +210,7 @@ private:
 	bool isDestinationGuarded(const bool ignoreAccessibility = true) const;
 	bool isDestinationGuardian() const;
 
+	void initializePatrol();
 	void initializeGraph();
 
 	CGPathNode::EAccessibility evaluateAccessibility(const int3 & pos, const TerrainTile * tinfo, const ELayer layer) const;

+ 1 - 1
lib/Connection.h

@@ -27,7 +27,7 @@
 #include "mapping/CCampaignHandler.h" //for CCampaignState
 #include "rmg/CMapGenerator.h" // for CMapGenOptions
 
-const ui32 version = 754;
+const ui32 version = 755;
 const ui32 minSupportedVersion = 753;
 
 class CISer;

+ 8 - 2
lib/IGameCallback.cpp

@@ -47,7 +47,7 @@ void CPrivilagedInfoCallback::getFreeTiles (std::vector<int3> &tiles) const
 	}
 }
 
-void CPrivilagedInfoCallback::getTilesInRange( std::unordered_set<int3, ShashInt3> &tiles, int3 pos, int radious, boost::optional<PlayerColor> player/*=uninit*/, int mode/*=0*/ ) const
+void CPrivilagedInfoCallback::getTilesInRange( std::unordered_set<int3, ShashInt3> &tiles, int3 pos, int radious, boost::optional<PlayerColor> player/*=uninit*/, int mode/*=0*/, bool patrolDistance/*=false*/) const
 {
 	if(!!player && *player >= PlayerColor::PLAYER_LIMIT)
 	{
@@ -63,7 +63,13 @@ void CPrivilagedInfoCallback::getTilesInRange( std::unordered_set<int3, ShashInt
 		{
 			for (int yd = std::max<int>(pos.y - radious, 0); yd <= std::min<int>(pos.y + radious, gs->map->height - 1); yd++)
 			{
-				double distance = pos.dist2d(int3(xd,yd,pos.z)) - 0.5;
+				int3 tilePos(xd,yd,pos.z);
+				double distance;
+				if(patrolDistance)
+					distance = pos.mandist2d(tilePos);
+				else
+					distance = pos.dist2d(tilePos) - 0.5;
+
 				if(distance <= radious)
 				{
 					if(!player

+ 1 - 1
lib/IGameCallback.h

@@ -30,7 +30,7 @@ class DLL_LINKAGE CPrivilagedInfoCallback : public CGameInfoCallback
 public:
 	CGameState * gameState();
 	void getFreeTiles (std::vector<int3> &tiles) const; //used for random spawns
-	void getTilesInRange(std::unordered_set<int3, ShashInt3> &tiles, int3 pos, int radious, boost::optional<PlayerColor> player = boost::optional<PlayerColor>(), int mode=0) const;  //mode 1 - only unrevealed tiles; mode 0 - all, mode -1 -  only unrevealed
+	void getTilesInRange(std::unordered_set<int3, ShashInt3> &tiles, int3 pos, int radious, boost::optional<PlayerColor> player = boost::optional<PlayerColor>(), int mode = 0, bool patrolDistance = false) const;  //mode 1 - only unrevealed tiles; mode 0 - all, mode -1 -  only unrevealed
 	void getAllTiles (std::unordered_set<int3, ShashInt3> &tiles, boost::optional<PlayerColor> player = boost::optional<PlayerColor>(), int level=-1, int surface=0) const; //returns all tiles on given level (-1 - both levels, otherwise number of level); surface: 0 - land and water, 1 - only land, 2 - only water
 	void pickAllowedArtsSet(std::vector<const CArtifact*> &out); //gives 3 treasures, 3 minors, 1 major -> used by Black Market and Artifact Merchant
 	void getAllowedSpells(std::vector<SpellID> &out, ui16 level);

+ 5 - 0
lib/int3.h

@@ -105,6 +105,11 @@ public:
 	{
 		return std::sqrt((double)dist2dSQ(o));
 	}
+	//manhattan distance used for patrol radius (z coord is not used)
+	double mandist2d(const int3 & o) const
+	{
+		return abs(o.x - x) + abs(o.y - y);
+	}
 
 	bool areNeighbours(const int3 & o) const
 	{

+ 13 - 2
lib/mapObjects/CGHeroInstance.h

@@ -72,12 +72,23 @@ public:
 
 	struct DLL_LINKAGE Patrol
 	{
-		Patrol(){patrolling=false;patrolRadious=-1;};
+		Patrol(){patrolling=false;initialPos=int3();patrolRadious=-1;};
 		bool patrolling;
+		int3 initialPos;
 		ui32 patrolRadious;
 		template <typename Handler> void serialize(Handler &h, const int version)
 		{
-			h & patrolling & patrolRadious;
+			h & patrolling;
+			if(version >= 755)
+			{
+				h & initialPos;
+			}
+			else if(!h.saving)
+			{
+				patrolling = false;
+				initialPos = int3();
+			}
+			h & patrolRadious;
 		}
 	} patrol;
 

+ 3 - 2
lib/mapping/MapFormatH3M.cpp

@@ -1060,7 +1060,7 @@ void CMapLoaderH3M::readObjects()
 		case Obj::RANDOM_HERO:
 		case Obj::PRISON:
 			{
-				nobj = readHero(idToBeGiven);
+				nobj = readHero(idToBeGiven, objPos);
 				break;
 			}
 		case Obj::MONSTER:  //Monster
@@ -1549,7 +1549,7 @@ void CMapLoaderH3M::readCreatureSet(CCreatureSet * out, int number)
 	out->validTypes(true);
 }
 
-CGObjectInstance * CMapLoaderH3M::readHero(ObjectInstanceID idToBeGiven)
+CGObjectInstance * CMapLoaderH3M::readHero(ObjectInstanceID idToBeGiven, const int3 & initialPos)
 {
 	auto nhi = new CGHeroInstance();
 
@@ -1658,6 +1658,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(ObjectInstanceID idToBeGiven)
 	else
 	{
 		nhi->patrol.patrolling = true;
+		nhi->patrol.initialPos = CGHeroInstance::convertPosition(initialPos, false);
 	}
 
 	if(map->version > EMapFormat::ROE)

+ 1 - 1
lib/mapping/MapFormatH3M.h

@@ -172,7 +172,7 @@ private:
 	 * @param idToBeGiven the object id which should be set for the hero
 	 * @return a object instance
 	 */
-	CGObjectInstance * readHero(ObjectInstanceID idToBeGiven);
+	CGObjectInstance * readHero(ObjectInstanceID idToBeGiven, const int3 & initialPos);
 
 	/**
 	 * Reads a seer hut.