Explorar o código

Merge branch 'optimization/swiftAI' into develop

Conflicts:
	lib/CGameState.cpp
Ivan Savenko %!s(int64=11) %!d(string=hai) anos
pai
achega
fc16c8d207

+ 8 - 12
AI/VCAI/AIUtility.cpp

@@ -175,9 +175,11 @@ std::string strFromInt3(int3 pos)
 	return oss.str();
 }
 
-bool isCloser(const CGObjectInstance *lhs, const CGObjectInstance *rhs)
+bool CDistanceSorter::operator ()(const CGObjectInstance *lhs, const CGObjectInstance *rhs)
 {
-	const CGPathNode *ln = cb->getPathInfo(lhs->visitablePos()), *rn = cb->getPathInfo(rhs->visitablePos());
+	const CGPathNode *ln = ai->myCb->getPathsInfo(hero)->getPathInfo(lhs->visitablePos()),
+	                 *rn = ai->myCb->getPathsInfo(hero)->getPathInfo(rhs->visitablePos());
+
 	if(ln->turns != rn->turns)
 		return ln->turns < rn->turns;
 
@@ -340,11 +342,6 @@ bool isSafeToVisit(HeroPtr h, crint3 tile)
 	return true; //there's no danger
 }
 
-bool isReachable(const CGObjectInstance *obj)
-{
-	return cb->getPathInfo(obj->visitablePos())->turns < 255;
-}
-
 bool canBeEmbarkmentPoint(const TerrainTile *t, bool fromWater)
 {
 	//tile must be free of with unoccupied boat
@@ -356,7 +353,6 @@ bool canBeEmbarkmentPoint(const TerrainTile *t, bool fromWater)
 int3 whereToExplore(HeroPtr h)
 {
 	TimeCheck tc ("where to explore");
-	cb->setSelection(*h);
 	int radius = h->getSightRadious();
 	int3 hpos = h->visitablePos();
 
@@ -371,7 +367,7 @@ int3 whereToExplore(HeroPtr h)
 			{
 				int3 op = obj->visitablePos();
 				CGPath p;
-				cb->getPath2(op, p);
+				ai->myCb->getPathsInfo(h.get())->getPath(op, p);
 				if (p.nodes.size() && p.endPos() == op && p.nodes.size() <= DIST_LIMIT)
 					if (ai->isGoodForVisit(obj, h))
 						nearbyVisitableObjs.push_back(obj);
@@ -379,7 +375,7 @@ int3 whereToExplore(HeroPtr h)
 		}
 	}
 	vstd::removeDuplicates (nearbyVisitableObjs); //one object may occupy multiple tiles
-	boost::sort(nearbyVisitableObjs, isCloser);
+	boost::sort(nearbyVisitableObjs, CDistanceSorter(h.get()));
 	if(nearbyVisitableObjs.size())
 		return nearbyVisitableObjs.back()->visitablePos();
 
@@ -396,8 +392,8 @@ int3 whereToExplore(HeroPtr h)
 
 bool isBlockedBorderGate(int3 tileToHit)
 {
-    return cb->getTile(tileToHit)->topVisitableId() == Obj::BORDER_GATE
-		&& cb->getPathInfo(tileToHit)->accessible != CGPathNode::ACCESSIBLE;
+    return cb->getTile(tileToHit)->topVisitableId() == Obj::BORDER_GATE &&
+	       (dynamic_cast <const CGKeys *>(cb->getTile(tileToHit)->visitableObjects.back()))->wasMyColorVisited (ai->playerID);
 }
 
 int howManyTilesWillBeDiscovered(const int3 &pos, int radious, CCallback * cbp)

+ 9 - 2
AI/VCAI/AIUtility.h

@@ -146,8 +146,6 @@ void getVisibleNeighbours(const std::vector<int3> &tiles, std::vector<int3> &out
 
 bool canBeEmbarkmentPoint(const TerrainTile *t, bool fromWater);
 bool isBlockedBorderGate(int3 tileToHit);
-bool isReachable(const CGObjectInstance *obj);
-bool isCloser(const CGObjectInstance *lhs, const CGObjectInstance *rhs);
 
 bool isWeeklyRevisitable (const CGObjectInstance * obj);
 bool shouldVisit (HeroPtr h, const CGObjectInstance * obj);
@@ -162,3 +160,12 @@ bool compareHeroStrength(HeroPtr h1, HeroPtr h2);
 bool compareArmyStrength(const CArmedInstance *a1, const CArmedInstance *a2);
 ui64 howManyReinforcementsCanGet(HeroPtr h, const CGTownInstance *t);
 int3 whereToExplore(HeroPtr h);
+
+class CDistanceSorter
+{
+	const CGHeroInstance * hero;
+public:
+	CDistanceSorter(const CGHeroInstance * hero): hero(hero) {}
+
+	bool operator ()(const CGObjectInstance *lhs, const CGObjectInstance *rhs);
+};

+ 0 - 1
AI/VCAI/Fuzzy.cpp

@@ -405,7 +405,6 @@ float FuzzyHelper::evaluate (Goals::VisitTile & g)
 		return 0;
 
 	//assert(cb->isInTheMap(g.tile));
-	cb->setSelection (g.hero.h);
 	float turns = 0;
 	float distance = cb->getMovementCost(g.hero.h, g.tile);
 	if (!distance) //we stand on that tile

+ 28 - 5
AI/VCAI/Goals.cpp

@@ -256,7 +256,7 @@ TSubgoal Win::whatToDoToAchieve()
 													return vstd::contains(t->forbiddenBuildings, BuildingID::GRAIL);
 												}),
 										towns.end());
-							boost::sort(towns, isCloser);
+							boost::sort(towns, CDistanceSorter(h.get()));
 							if(towns.size())
 							{
 								return sptr (Goals::VisitTile(towns.front()->visitablePos()).sethero(h));
@@ -353,7 +353,7 @@ TSubgoal FindObj::whatToDoToAchieve()
 			}
 		}
 	}
-	if (o && isReachable(o)) //we don't use isAccessibleForHero as we don't know which hero it is
+	if (o && ai->isAccessible(o->pos)) //we don't use isAccessibleForHero as we don't know which hero it is
 		return sptr (Goals::GetObj(o->id.getNum()));
 	else
 		return sptr (Goals::Explore());
@@ -377,7 +377,7 @@ TSubgoal GetObj::whatToDoToAchieve()
 	}
 	else
 	{
-		if (isReachable(obj))
+		if (ai->isAccessible(obj->pos))
 			return sptr (Goals::VisitTile(pos).sethero(hero)); //we must visit object with same hero, if any
 	}
 	return sptr (Goals::ClearWayTo(pos).sethero(hero));
@@ -853,8 +853,31 @@ TSubgoal GatherTroops::whatToDoToAchieve()
 	}
 	if (dwellings.size())
 	{
-		boost::sort(dwellings, isCloser);
-		return sptr (Goals::GetObj(dwellings.front()->id.getNum()));
+		typedef std::map<const CGHeroInstance *, const CGDwelling *> TDwellMap;
+
+		// sorted helper
+		auto comparator = [](const TDwellMap::value_type & a, const TDwellMap::value_type & b) -> bool
+		{
+			const CGPathNode *ln = ai->myCb->getPathsInfo(a.first)->getPathInfo(a.second->visitablePos()),
+			                 *rn = ai->myCb->getPathsInfo(b.first)->getPathInfo(b.second->visitablePos());
+
+			if(ln->turns != rn->turns)
+				return ln->turns < rn->turns;
+
+			return (ln->moveRemains > rn->moveRemains);
+		};
+
+		// for all owned heroes generate map <hero -> nearest dwelling>
+		TDwellMap nearestDwellings;
+		for (const CGHeroInstance * hero : cb->getHeroesInfo(true))
+		{
+			nearestDwellings[hero] = *boost::range::min_element(dwellings, CDistanceSorter(hero));
+		}
+
+		// find hero who is nearest to a dwelling
+		const CGDwelling * nearest = boost::range::min_element(nearestDwellings, comparator)->second;
+
+		return sptr (Goals::GetObj(nearest->id.getNum()));
 	}
 	else
 		return sptr (Goals::Explore());

+ 13 - 26
AI/VCAI/VCAI.cpp

@@ -664,9 +664,6 @@ void VCAI::makeTurn()
 		}
 			break;
 	}
-	if(cb->getSelectedHero())
-		cb->recalculatePaths();
-
 	markHeroAbleToExplore (primaryHero());
 
 	makeTurnInternal();
@@ -697,9 +694,8 @@ void VCAI::makeTurnInternal()
 				continue;
 			}
 
-			cb->setSelection(hero.first.get());
 			std::vector<const CGObjectInstance *> vec(hero.second.begin(), hero.second.end());
-			boost::sort (vec, isCloser);
+			boost::sort (vec, CDistanceSorter(hero.first.get()));
 			for (auto obj : vec)
 			{
 				if(!obj || !cb->getObj(obj->id))
@@ -793,7 +789,7 @@ void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h)
 	switch (obj->ID)
 	{
 		case Obj::CREATURE_GENERATOR1:
-			recruitCreatures (dynamic_cast<const CGDwelling *>(obj));
+			recruitCreatures (dynamic_cast<const CGDwelling *>(obj), h.get());
 			checkHeroArmy (h);
 			break;
 		case Obj::TOWN:
@@ -926,7 +922,7 @@ void VCAI::pickBestCreatures(const CArmedInstance * army, const CArmedInstance *
 	}
 }
 
-void VCAI::recruitCreatures(const CGDwelling * d)
+void VCAI::recruitCreatures(const CGDwelling * d, const CArmedInstance * recruiter)
 {
 	for(int i = 0; i < d->creatures.size(); i++)
 	{
@@ -941,7 +937,7 @@ void VCAI::recruitCreatures(const CGDwelling * d)
 
 		amin(count, freeResources() / VLC->creh->creatures[creID]->cost);
 		if(count > 0)
-			cb->recruitCreatures(d, creID, count, i);
+			cb->recruitCreatures(d, recruiter, creID, count, i);
 	}
 }
 
@@ -1222,7 +1218,7 @@ std::vector<const CGObjectInstance *> VCAI::getPossibleDestinations(HeroPtr h)
 		}
 	}
 
-	boost::sort(possibleDestinations, isCloser);
+	boost::sort(possibleDestinations, CDistanceSorter(h.get()));
 
 	return possibleDestinations;
 }
@@ -1256,7 +1252,6 @@ bool VCAI::canRecruitAnyHero (const CGTownInstance * t) const
 
 void VCAI::wander(HeroPtr h)
 {
-	cb->setSelection(*h);
 	//unclaim objects that are now dangerous for us
 	auto reservedObjsSetCopy = reservedHeroesMap[h];
 	for (auto obj : reservedObjsSetCopy)
@@ -1381,7 +1376,7 @@ void VCAI::wander(HeroPtr h)
 			erase_if(dests, shouldBeErased);
 
 			erase_if_present(dests, dest); //why that fails sometimes when removing monsters?
-			boost::sort(dests, isCloser); //find next closest one
+			boost::sort(dests, CDistanceSorter(h.get())); //find next closest one
 		}
 
 		if (h->visitedTown)
@@ -1618,7 +1613,6 @@ const CGObjectInstance * VCAI::getUnvisitedObj(const std::function<bool(const CG
 
 bool VCAI::isAccessibleForHero(const int3 & pos, HeroPtr h, bool includeAllies /*= false*/) const
 {
-	cb->setSelection(*h);
 	if (!includeAllies)
 	{ //don't visit tile occupied by allied hero
 		for (auto obj : cb->getVisitableObjs(pos))
@@ -1629,12 +1623,11 @@ bool VCAI::isAccessibleForHero(const int3 & pos, HeroPtr h, bool includeAllies /
 				return false;
 		}
 	}
-	return cb->getPathInfo(pos)->reachable();
+	return cb->getPathsInfo(h.get())->getPathInfo(pos)->reachable();
 }
 
 bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
 {
-	cb->setSelection(h.h); //make sure we are using the RIGHT pathfinder
 	logAi->debugStream() << boost::format("Moving hero %s to tile %s") % h->name % dst;
 	int3 startHpos = h->visitablePos();
 	bool ret = false;
@@ -1649,11 +1642,10 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
 	else
 	{
 		CGPath path;
-		cb->getPath2(dst, path);
+		cb->getPathsInfo(h.get())->getPath(dst, path);
 		if(path.nodes.empty())
 		{
             logAi->errorStream() << "Hero " << h->name << " cannot reach " << dst;
-			cb->recalculatePaths();
 			throw goalFulfilledException (sptr(Goals::VisitTile(dst).sethero(h)));
 		}
 
@@ -1702,7 +1694,6 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
 				reserveObject(h, obj);
 		}
 
-		cb->recalculatePaths();
 		if (startHpos == h->visitablePos() && !ret) //we didn't move and didn't reach the target
 		{
 			erase_if_present (lockedHeroes, h); //hero seemingly is confused
@@ -2212,7 +2203,7 @@ void VCAI::buildArmyIn(const CGTownInstance * t)
 {
 	makePossibleUpgrades(t->visitingHero);
 	makePossibleUpgrades(t);
-	recruitCreatures(t);
+	recruitCreatures(t, t);
 	moveCreaturesToHero(t);
 }
 
@@ -2230,7 +2221,7 @@ int3 VCAI::explorationBestNeighbour(int3 hpos, int radius, HeroPtr h)
 	auto best = dstToRevealedTiles.begin();
 	for (auto i = dstToRevealedTiles.begin(); i != dstToRevealedTiles.end(); i++)
 	{
-		const CGPathNode *pn = cb->getPathInfo(i->first);
+		const CGPathNode *pn = cb->getPathsInfo(h.get())->getPathInfo(i->first);
 		//const TerrainTile *t = cb->getTile(i->first);
 		if(best->second < i->second && pn->reachable() && pn->accessible == CGPathNode::ACCESSIBLE)
 			best = i;
@@ -2245,7 +2236,6 @@ int3 VCAI::explorationBestNeighbour(int3 hpos, int radius, HeroPtr h)
 int3 VCAI::explorationNewPoint(HeroPtr h)
 {
     //logAi->debugStream() << "Looking for an another place for exploration...";
-	cb->setSelection(h.h);
 	int radius = h->getSightRadious();
 
 	std::vector<std::vector<int3> > tiles; //tiles[distance_to_fow]
@@ -2269,13 +2259,11 @@ int3 VCAI::explorationNewPoint(HeroPtr h)
 
 		for(const int3 &tile : tiles[i])
 		{
-			if (cbp->getTile(tile)->blocked) //does it shorten the time?
-				continue;
-			if (!cbp->getPathInfo(tile)->reachable()) //this will remove tiles that are guarded by monsters (or removable objects)
+			if (!cb->getPathsInfo(h.get())->getPathInfo(tile)->reachable()) //this will remove tiles that are guarded by monsters (or removable objects)
 				continue;
 
 			CGPath path;
-			cbp->getPath2(tile, path);
+			cb->getPathsInfo(h.get())->getPath(tile, path);
 			float ourValue = (float)howManyTilesWillBeDiscovered(tile, radius, cbp) / (path.nodes.size() + 1); //+1 prevents erratic jumps
 
 			if (ourValue > bestValue) //avoid costly checks of tiles that don't reveal much
@@ -2655,7 +2643,6 @@ SectorMap::SectorMap()
 
 SectorMap::SectorMap(HeroPtr h)
 {
-	cb->setSelection(h.h);
 	update();
 	makeParentBFS(h->visitablePos());
 }
@@ -3104,7 +3091,7 @@ int3 SectorMap::findFirstVisitableTile (HeroPtr h, crint3 dst)
 			logAi->warnStream() << ("Another allied hero stands in our way");
 			return ret;
 		}
-		if(cb->getPathInfo(curtile)->reachable())
+		if(ai->myCb->getPathsInfo(h.get())->getPathInfo(curtile)->reachable())
 		{
 			return curtile;
 		}

+ 1 - 1
AI/VCAI/VCAI.h

@@ -263,7 +263,7 @@ public:
 	std::vector<const CGObjectInstance *> getPossibleDestinations(HeroPtr h);
 	void buildStructure(const CGTownInstance * t);
 	//void recruitCreatures(const CGTownInstance * t);
-	void recruitCreatures(const CGDwelling * d);
+	void recruitCreatures(const CGDwelling * d, const CArmedInstance * recruiter);
 	bool canGetArmy (const CGHeroInstance * h, const CGHeroInstance * source); //can we get any better stacks from other hero?
 	void pickBestCreatures(const CArmedInstance * army, const CArmedInstance * source); //called when we can't find a slot for new stack
 	void moveCreaturesToHero(const CGTownInstance * t);

+ 10 - 85
CCallback.cpp

@@ -69,12 +69,12 @@ int CCallback::selectionMade(int selection, QueryID queryID)
 	return sendRequest(&pack);
 }
 
-void CCallback::recruitCreatures(const CGObjectInstance *obj, CreatureID ID, ui32 amount, si32 level/*=-1*/)
+void CCallback::recruitCreatures(const CGDwelling *obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level/*=-1*/)
 {
 	if(player!=obj->tempOwner  &&  obj->ID != Obj::WAR_MACHINE_FACTORY)
 		return;
 
-	RecruitCreatures pack(obj->id,ID,amount,level);
+	RecruitCreatures pack(obj->id, dst->id, ID, amount, level);
 	sendRequest(&pack);
 }
 
@@ -235,36 +235,6 @@ void CCallback::setFormation(const CGHeroInstance * hero, bool tight)
 	sendRequest(&pack);
 }
 
-void CCallback::setSelection(const CArmedInstance * obj)
-{
-	if(!player || obj->getOwner() != *player)
-	{
-		logGlobal->errorStream() << boost::format("Cannot set selection to the object that is not owned. Object owner is %s, callback player %s") % obj->getOwner() % player;
-		return;
-	}
-
-	SetSelection ss;
-	ss.player = *player;
-	ss.id = obj->id;
-	sendRequest(&(CPackForClient&)ss);
-
-	if(obj->getOwner() != *player)
-	{
-		// Cf. bug #1679 http://bugs.vcmi.eu/view.php?id=1679
-		logGlobal->warnStream() << "The selection request became invalid because of event that occurred after it was made. Object owner is now " << obj->getOwner();
-		throw std::runtime_error("setSelection not allowed");
-	}
-
-	if(obj->ID == Obj::HERO)
-	{
-		if(cl->pathInfo->hero != obj) //calculate new paths only if we selected a different hero
-			cl->calculatePaths(static_cast<const CGHeroInstance *>(obj));
-
-		//nasty workaround. TODO: nice workaround
-		cl->gs->getPlayer(*player)->currentSelection = obj->id;
-	}
-}
-
 void CCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero)
 {
 	assert(townOrTavern);
@@ -288,10 +258,10 @@ void CCallback::save( const std::string &fname )
 }
 
 
-void CCallback::sendMessage(const std::string &mess)
+void CCallback::sendMessage(const std::string &mess, const CGObjectInstance * currentObject)
 {
 	ASSERT_IF_CALLED_WITH_PLAYER
-	PlayerMessage pm(*player, mess);
+	PlayerMessage pm(*player, mess, currentObject? currentObject->id : ObjectInstanceID(-1));
 	sendRequest(&(CPackForClient&)pm);
 }
 
@@ -314,45 +284,20 @@ CCallback::~CCallback()
 //trivial, but required. Don`t remove.
 }
 
-
-const CGPathNode * CCallback::getPathInfo( int3 tile )
-{
-	if (!gs->map->isInTheMap(tile))
-		return nullptr;
-
-	validatePaths();
-	return &cl->pathInfo->nodes[tile.x][tile.y][tile.z];
-}
-
-int CCallback::getDistance( int3 tile )
-{
-	CGPath ret;
-	if (getPath2 (tile, ret))
-		return ret.nodes.size();
-	else
-		return 255;
-}
-
 bool CCallback::canMoveBetween(const int3 &a, const int3 &b)
 {
 	//TODO: merge with Pathfinder::canMoveBetween
 	return gs->checkForVisitableDir(a, b) && gs->checkForVisitableDir(b, a);
 }
 
-bool CCallback::getPath2( int3 dest, CGPath &ret )
+int CCallback::getMovementCost(const CGHeroInstance * hero, int3 dest)
 {
-	if (!gs->map->isInTheMap(dest))
-		return false;
-
-	validatePaths();
-
-	boost::unique_lock<boost::mutex> pathLock(cl->pathMx);
-	return cl->pathInfo->getPath(dest, ret);
+	return gs->getMovementCost(hero, hero->visitablePos(), dest, hero->hasBonusOfType (Bonus::FLYING_MOVEMENT), hero->movement);
 }
 
-int CCallback::getMovementCost(const CGHeroInstance * hero, int3 dest)
+const CPathsInfo * CCallback::getPathsInfo(const CGHeroInstance *h)
 {
-	return gs->getMovementCost(hero, hero->visitablePos(), dest, hero->hasBonusOfType (Bonus::FLYING_MOVEMENT), hero->movement);
+	return cl->getPathsInfo(h);
 }
 
 int3 CCallback::getGuardingCreaturePosition(int3 tile)
@@ -360,20 +305,12 @@ int3 CCallback::getGuardingCreaturePosition(int3 tile)
 	if (!gs->map->isInTheMap(tile))
 		return int3(-1,-1,-1);
 
-	validatePaths();
-
-	boost::unique_lock<boost::mutex> pathLock(cl->pathMx);
 	return gs->map->guardingCreaturePositions[tile.x][tile.y][tile.z];
 }
 
-void CCallback::recalculatePaths()
+void CCallback::calculatePaths( const CGHeroInstance *hero, CPathsInfo &out)
 {
-	cl->calculatePaths(cl->IGameCallback::getSelectedHero(*player));
-}
-
-void CCallback::calculatePaths( const CGHeroInstance *hero, CPathsInfo &out, int3 src /*= int3(-1,-1,-1)*/, int movement /*= -1*/ )
-{
-	gs->calculatePaths(hero, out, src, movement);
+	gs->calculatePaths(hero, out);
 }
 
 void CCallback::dig( const CGObjectInstance *hero )
@@ -398,18 +335,6 @@ void CCallback::unregisterAllInterfaces()
 	cl->battleints.clear();
 }
 
-void CCallback::validatePaths()
-{
-	ASSERT_IF_CALLED_WITH_PLAYER
-	const CGHeroInstance *h = cl->IGameCallback::getSelectedHero(*player);
-	if(h  && ( cl->pathInfo->hero != h							//wrong hero
-		       || cl->pathInfo->hpos != h->getPosition(false)  //wrong hero position
-		       || !cl->pathInfo->isValid)) //paths invalidated by game event
-	{
-		recalculatePaths();
-	}
-}
-
 int CCallback::mergeOrSwapStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2)
 {
 	if(s1->getCreature(p1) == s2->getCreature(p2))

+ 7 - 17
CCallback.h

@@ -54,7 +54,7 @@ public:
 	//town
 	virtual void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero)=0;
 	virtual bool buildBuilding(const CGTownInstance *town, BuildingID buildingID)=0;
-	virtual void recruitCreatures(const CGObjectInstance *obj, CreatureID ID, ui32 amount, si32 level=-1)=0;
+	virtual void recruitCreatures(const CGDwelling *obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1)=0;
 	virtual bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE)=0; //if newID==-1 then best possible upgrade will be made
 	virtual void swapGarrisonHero(const CGTownInstance *town)=0;
 
@@ -72,11 +72,9 @@ public:
 	virtual void endTurn()=0;
 	virtual void buyArtifact(const CGHeroInstance *hero, ArtifactID aid)=0; //used to buy artifacts in towns (including spell book in the guild and war machines in blacksmith)
 	virtual void setFormation(const CGHeroInstance * hero, bool tight)=0;
-	virtual void setSelection(const CArmedInstance * obj)=0;
-
 
 	virtual void save(const std::string &fname) = 0;
-	virtual void sendMessage(const std::string &mess) = 0;
+	virtual void sendMessage(const std::string &mess, const CGObjectInstance * currentObject = nullptr) = 0;
 	virtual void buildBoat(const IShipyard *obj) = 0;
 };
 
@@ -100,24 +98,17 @@ public:
 
 class CCallback : public CPlayerSpecificInfoCallback, public IGameActionCallback, public CBattleCallback
 {
-private:
-
-	void validatePaths(); //recalcualte paths if necessary
-
 public:
 	CCallback(CGameState * GS, boost::optional<PlayerColor> Player, CClient *C);
 	virtual ~CCallback();
 
 	//client-specific functionalities (pathfinding)
-	virtual const CGPathNode *getPathInfo(int3 tile); //uses main, client pathfinder info
-	virtual int getDistance(int3 tile);
-	virtual bool getPath2(int3 dest, CGPath &ret); //uses main, client pathfinder info
 	virtual bool canMoveBetween(const int3 &a, const int3 &b);
 	virtual int getMovementCost(const CGHeroInstance * hero, int3 dest);
-	virtual int3 getGuardingCreaturePosition(int3 tile); //uses main, client pathfinder info
+	virtual int3 getGuardingCreaturePosition(int3 tile);
+	virtual const CPathsInfo * getPathsInfo(const CGHeroInstance *h);
 
-	virtual void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out, int3 src = int3(-1,-1,-1), int movement = -1);
-	virtual void recalculatePaths(); //updates main, client pathfinder info (should be called when moving hero is over)
+	virtual void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out);
 
 	//Set of metrhods that allows adding more interfaces for this player that'll receive game event call-ins.
 	void registerGameInterface(shared_ptr<IGameEventsReceiver> gameEvents);
@@ -142,7 +133,7 @@ public:
 	//bool moveArtifact(const CStackInstance * stack, ui16 src , const CGHeroInstance * hero, ui16 dest); // TODO: unify classes
 	bool assembleArtifacts(const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo);
 	bool buildBuilding(const CGTownInstance *town, BuildingID buildingID) override;
-	void recruitCreatures(const CGObjectInstance *obj, CreatureID ID, ui32 amount, si32 level=-1);
+	void recruitCreatures(const CGDwelling * obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1);
 	bool dismissCreature(const CArmedInstance *obj, SlotID stackPos);
 	bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE) override;
 	void endTurn();
@@ -150,10 +141,9 @@ public:
 	void buyArtifact(const CGHeroInstance *hero, ArtifactID aid) override;
 	void trade(const CGObjectInstance *market, EMarketMode::EMarketMode mode, int id1, int id2, int val1, const CGHeroInstance *hero = nullptr);
 	void setFormation(const CGHeroInstance * hero, bool tight);
-	void setSelection(const CArmedInstance * obj);
 	void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero);
 	void save(const std::string &fname);
-	void sendMessage(const std::string &mess);
+	void sendMessage(const std::string &mess, const CGObjectInstance * currentObject = nullptr);
 	void buildBoat(const IShipyard *obj);
 	void dig(const CGObjectInstance *hero);
 	void castSpell(const CGHeroInstance *hero, SpellID spellID, const int3 &pos = int3(-1, -1, -1));

+ 14 - 5
client/CPlayerInterface.cpp

@@ -105,6 +105,7 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player)
 	curAction = nullptr;
 	playerID=Player;
 	human=true;
+	currentSelection = nullptr;
 	castleInt = nullptr;
 	battleInt = nullptr;
 	//pim = new boost::recursive_mutex;
@@ -1256,12 +1257,10 @@ template <typename Handler> void CPlayerInterface::serializeTempl( Handler &h, c
 	{
 		h & pathsMap;
 
-		CPathsInfo pathsInfo(cb->getMapSize());
 		for(auto &p : pathsMap)
 		{
-			cb->calculatePaths(p.first, pathsInfo);
 			CGPath path;
-			pathsInfo.getPath(p.second, path);
+			cb->getPathsInfo(p.first)->getPath(p.second, path);
 			paths[p.first] = path;
 			logGlobal->traceStream() << boost::format("Restored path for hero %s leading to %s with %d nodes")
 				% p.first->nodeName() % p.second % path.nodes.size();
@@ -1464,7 +1463,7 @@ void CPlayerInterface::showRecruitmentDialog(const CGDwelling *dwelling, const C
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	waitWhileDialog();
-	auto recruitCb = [=](CreatureID id, int count){ LOCPLINT->cb->recruitCreatures(dwelling, id, count, -1); };
+	auto recruitCb = [=](CreatureID id, int count){ LOCPLINT->cb->recruitCreatures(dwelling, dst, id, count, -1); };
 	CRecruitmentWindow *cr = new CRecruitmentWindow(dwelling, level, dst, recruitCb);
 	GH.pushInt(cr);
 }
@@ -1552,6 +1551,16 @@ bool CPlayerInterface::ctrlPressed() const
 	return isCtrlKeyDown();
 }
 
+const CArmedInstance * CPlayerInterface::getSelection()
+{
+	return currentSelection;
+}
+
+void CPlayerInterface::setSelection(const CArmedInstance * obj)
+{
+	currentSelection = obj;
+}
+
 void CPlayerInterface::update()
 {
 	if (!locked)
@@ -2206,7 +2215,7 @@ CGPath * CPlayerInterface::getAndVerifyPath(const CGHeroInstance * h)
 		{
 			assert(h->getPosition(false) == path.startPos());
 			//update the hero path in case of something has changed on map
-			if(LOCPLINT->cb->getPath2(path.endPos(), path))
+			if(LOCPLINT->cb->getPathsInfo(h)->getPath(path.endPos(), path))
 				return &path;
 			else
 				paths.erase(h);

+ 4 - 0
client/CPlayerInterface.h

@@ -86,6 +86,7 @@ enum
 /// Central class for managing user interface logic
 class CPlayerInterface : public CGameInterface, public ILockedUpdatable
 {
+	const CArmedInstance * currentSelection;
 public:
 	bool observerInDuelMode;
 
@@ -117,6 +118,8 @@ public:
 	shared_ptr<CBattleGameInterface> autofightingAI; //AI that makes decisions
 	bool isAutoFightOn; //Flag, switch it to stop quick combat. Don't touch if there is no battle interface.
 
+	const CArmedInstance * getSelection();
+	void setSelection(const CArmedInstance * obj);
 
 	struct SpellbookLastSetting
 	{
@@ -246,6 +249,7 @@ public:
 	void movementPxStep( const TryMoveHero &details, int i, const int3 &hp, const CGHeroInstance * ho );//performing step of movement
 	void finishMovement( const TryMoveHero &details, const int3 &hp, const CGHeroInstance * ho ); //finish movement
 	void eraseCurrentPathOf( const CGHeroInstance * ho, bool checkForExistanceOfPath = true );
+
 	void removeLastNodeFromPath(const CGHeroInstance *ho);
 	CGPath *getAndVerifyPath( const CGHeroInstance * h );
 	void acceptTurn(); //used during hot seat after your turn message is close

+ 15 - 18
client/Client.cpp

@@ -542,14 +542,6 @@ void CClient::handlePack( CPack * pack )
 	delete pack;
 }
 
-void CClient::updatePaths()
-{
-	//TODO? lazy evaluation? paths now can get recalculated multiple times upon various game events
-	const CGHeroInstance *h = getSelectedHero();
-	if (h)//if we have selected hero...
-		calculatePaths(h);
-}
-
 void CClient::finishCampaign( shared_ptr<CCampaignState> camp )
 {
 }
@@ -672,13 +664,6 @@ PlayerColor CClient::getLocalPlayer() const
 	return getCurrentPlayer();
 }
 
-void CClient::calculatePaths(const CGHeroInstance *h)
-{
-	assert(h);
-	boost::unique_lock<boost::mutex> pathLock(pathMx);
-	gs->calculatePaths(h, *pathInfo);
-}
-
 void CClient::commenceTacticPhaseForInt(shared_ptr<CBattleGameInterface> battleInt)
 {
 	setThreadName("CClient::commenceTacticPhaseForInt");
@@ -693,10 +678,22 @@ void CClient::commenceTacticPhaseForInt(shared_ptr<CBattleGameInterface> battleI
 	} HANDLE_EXCEPTION
 }
 
-void CClient::invalidatePaths(const CGHeroInstance *h /*= nullptr*/)
+void CClient::invalidatePaths()
 {
-	if(!h || pathInfo->hero == h)
-		pathInfo->isValid = false;
+	// turn pathfinding info into invalid. It will be regenerated later
+	boost::unique_lock<boost::mutex> pathLock(pathInfo->pathMx);
+	pathInfo->hero = nullptr;
+}
+
+const CPathsInfo * CClient::getPathsInfo(const CGHeroInstance *h)
+{
+	assert(h);
+	boost::unique_lock<boost::mutex> pathLock(pathInfo->pathMx);
+	if (pathInfo->hero != h)
+	{
+		gs->calculatePaths(h, *pathInfo.get());
+	}
+	return pathInfo.get();
 }
 
 int CClient::sendRequest(const CPack *request, PlayerColor player)

+ 4 - 6
client/Client.h

@@ -113,6 +113,7 @@ public:
 /// Class which handles client - server logic
 class CClient : public IGameCallback
 {
+	unique_ptr<CPathsInfo> pathInfo;
 public:
 	std::map<PlayerColor,shared_ptr<CCallback> > callbacks; //callbacks given to player interfaces
 	std::map<PlayerColor,shared_ptr<CBattleCallback> > battleCallbacks; //callbacks given to player interfaces
@@ -129,9 +130,6 @@ public:
 
 	boost::optional<BattleAction> curbaction;
 
-	unique_ptr<CPathsInfo> pathInfo;
-	boost::mutex pathMx; //protects the variable above
-
 	CScriptingModule *erm;
 
 	ThreadSafeVector<int> waitingRequest;
@@ -158,9 +156,9 @@ public:
 	void campaignMapFinished( shared_ptr<CCampaignState> camp );
 	void finishCampaign( shared_ptr<CCampaignState> camp );
 	void proposeNextMission(shared_ptr<CCampaignState> camp);
-	void invalidatePaths(const CGHeroInstance *h = nullptr); //invalidates paths for hero h or for any hero if h is nullptr => they'll got recalculated when the next query comes
-	void calculatePaths(const CGHeroInstance *h);
-	void updatePaths(); //calls calculatePaths for same hero for which we previously calculated paths
+
+	void invalidatePaths();
+	const CPathsInfo * getPathsInfo(const CGHeroInstance *h);
 
 	bool terminate;	// tell to terminate
 	boost::thread *connectionHandler; //thread running run() method

+ 2 - 11
client/NetPacksClient.cpp

@@ -160,7 +160,7 @@ void SetMana::applyCl( CClient *cl )
 void SetMovePoints::applyCl( CClient *cl )
 {
 	const CGHeroInstance *h = cl->getHero(hid);
-	cl->invalidatePaths(h);
+	cl->invalidatePaths();
 	INTERFACE_CALL_IF_PRESENT(h->tempOwner, heroMovePointsChanged, h);
 }
 
@@ -822,15 +822,6 @@ void PlayerMessage::applyCl(CClient *cl)
 		LOCPLINT->cingconsole->print(str.str());
 }
 
-void SetSelection::applyCl(CClient *cl)
-{
-	const CGHeroInstance *h = cl->getHero(id);
-	if(!h)
-		return;
-
-	//CPackForClient::GS(cl)->calculatePaths(h, *cl->pathInfo);
-}
-
 void ShowInInfobox::applyCl(CClient *cl)
 {
 	INTERFACE_CALL_IF_PRESENT(player,showComp, c, text.toString());
@@ -914,7 +905,7 @@ void CenterView::applyCl(CClient *cl)
 
 void NewObject::applyCl(CClient *cl)
 {
-	cl->updatePaths();
+	cl->invalidatePaths();
 
 	const CGObjectInstance *obj = cl->getObj(id);
 	CGI->mh->printObject(obj);

+ 1 - 1
client/widgets/AdventureMapClasses.cpp

@@ -1168,7 +1168,7 @@ void CInGameConsole::endEnteringText(bool printEnteredText)
 	if(printEnteredText)
 	{
 		std::string txt = enteredText.substr(0, enteredText.size()-1);
-		LOCPLINT->cb->sendMessage(txt);
+		LOCPLINT->cb->sendMessage(txt, LOCPLINT->getSelection());
 		previouslyEntered.push_back(txt);
 		//print(txt);
 	}

+ 8 - 9
client/windows/CAdvmapInterface.cpp

@@ -948,7 +948,7 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key)
 
 			CGPath &path = LOCPLINT->paths[h];
 			terrain.currentPath = &path;
-			if(!LOCPLINT->cb->getPath2(h->getPosition(false) + dir, path))
+			if(!LOCPLINT->cb->getPathsInfo(h)->getPath(h->getPosition(false) + dir, path))
 			{
 				terrain.currentPath = nullptr;
 				return;
@@ -997,7 +997,7 @@ int3 CAdvMapInt::verifyPos(int3 ver)
 void CAdvMapInt::select(const CArmedInstance *sel, bool centerView /*= true*/)
 {
 	assert(sel);
-	LOCPLINT->cb->setSelection(sel);
+	LOCPLINT->setSelection(sel);
 	selection = sel;
 	if (LOCPLINT->battleInt == nullptr && LOCPLINT->makingTurn)
 	{
@@ -1184,7 +1184,7 @@ void CAdvMapInt::tileLClicked(const int3 &mapPos)
 	}
 	else if(const CGHeroInstance * currentHero = curHero()) //hero is selected
 	{
-		const CGPathNode *pn = LOCPLINT->cb->getPathInfo(mapPos);
+		const CGPathNode *pn = LOCPLINT->cb->getPathsInfo(currentHero)->getPathInfo(mapPos);
 		if(currentHero == topBlocking) //clicked selected hero
 		{
 			LOCPLINT->openHeroWindow(currentHero);
@@ -1206,7 +1206,7 @@ void CAdvMapInt::tileLClicked(const int3 &mapPos)
 			{
 				CGPath &path = LOCPLINT->paths[currentHero];
 				terrain.currentPath = &path;
-				bool gotPath = LOCPLINT->cb->getPath2(mapPos, path); //try getting path, erase if failed
+				bool gotPath = LOCPLINT->cb->getPathsInfo(currentHero)->getPath(mapPos, path); //try getting path, erase if failed
 				updateMoveHero(currentHero);
 				if (!gotPath)
 					LOCPLINT->eraseCurrentPathOf(currentHero);
@@ -1249,11 +1249,6 @@ void CAdvMapInt::tileHovered(const int3 &mapPos)
 		statusbar.setText(hlp);
 	}
 
-	const CGPathNode *pnode = LOCPLINT->cb->getPathInfo(mapPos);
-
-	int turns = pnode->turns;
-	vstd::amin(turns, 3);
-
 	if(!selection) //may occur just at the start of game (fake move before full intiialization)
 		return;
 
@@ -1298,6 +1293,10 @@ void CAdvMapInt::tileHovered(const int3 &mapPos)
 	}
 	else if(const CGHeroInstance *h = curHero())
 	{
+		const CGPathNode *pnode = LOCPLINT->cb->getPathsInfo(h)->getPathInfo(mapPos);
+
+		int turns = pnode->turns;
+		vstd::amin(turns, 3);
 		bool accessible  =  pnode->turns < 255;
 
 		if(objAtTile)

+ 2 - 2
client/windows/CCastleInterface.cpp

@@ -777,7 +777,7 @@ void CCastleBuildings::enterCastleGate()
 void CCastleBuildings::enterDwelling(int level)
 {
 	assert(level >= 0 && level < town->creatures.size());
-	auto recruitCb = [=](CreatureID id, int count){ LOCPLINT->cb->recruitCreatures(town, id, count, level); };
+	auto recruitCb = [=](CreatureID id, int count){ LOCPLINT->cb->recruitCreatures(town, town, id, count, level); };
 	GH.pushInt(new CRecruitmentWindow(town, level, town, recruitCb, -87));
 }
 
@@ -1066,7 +1066,7 @@ void CCreaInfo::clickLeft(tribool down, bool previousState)
 	if(previousState && (!down))
 	{
 		int offset = LOCPLINT->castleInt? (-87) : 0;
-		auto recruitCb = [=](CreatureID id, int count) { LOCPLINT->cb->recruitCreatures(town, id, count, level); };
+		auto recruitCb = [=](CreatureID id, int count) { LOCPLINT->cb->recruitCreatures(town, town, id, count, level); };
 		GH.pushInt(new CRecruitmentWindow(town, level, town, recruitCb, offset));
 	}
 }

+ 0 - 12
lib/CGameInfoCallback.cpp

@@ -38,18 +38,6 @@ int CGameInfoCallback::getResource(PlayerColor Player, Res::ERes which) const
 	return p->resources[which];
 }
 
-const CGHeroInstance* CGameInfoCallback::getSelectedHero( PlayerColor Player ) const
-{
-	const PlayerState *p = getPlayer(Player);
-	ERROR_RET_VAL_IF(!p, "No player info!", nullptr);
-	return getHero(p->currentSelection);
-}
-
-const CGHeroInstance* CGameInfoCallback::getSelectedHero() const
-{
-	return getSelectedHero(gs->currentPlayer);
-}
-
 const PlayerSettings * CGameInfoCallback::getPlayerSettings(PlayerColor color) const
 {
 	return &gs->scenarioOps->getIthPlayersSettings(color);

+ 0 - 2
lib/CGameInfoCallback.h

@@ -72,8 +72,6 @@ public:
 	bool getHeroInfo(const CGObjectInstance *hero, InfoAboutHero &dest) const;
 	int getSpellCost(const CSpell * sp, const CGHeroInstance * caster) const; //when called during battle, takes into account creatures' spell cost reduction
 	int estimateSpellDamage(const CSpell * sp, const CGHeroInstance * hero) const; //estimates damage of given spell; returns 0 if spell causes no dmg
-	const CGHeroInstance* getSelectedHero(PlayerColor player) const; //nullptr if no hero is selected
-	const CGHeroInstance* getSelectedHero() const; //of current (active) player
 	const CArtifactInstance * getArtInstance(ArtifactInstanceID aid) const;
 	const CGObjectInstance * getObjInstance(ObjectInstanceID oid) const;
 

+ 3 - 1
lib/CGameInterface.h

@@ -3,6 +3,7 @@
 
 #include "BattleAction.h"
 #include "IGameEventsReceiver.h"
+#include "CGameStateFwd.h"
 
 /*
  * CGameInterface.h, part of VCMI engine
@@ -45,6 +46,7 @@ struct StackLocation;
 class CStackInstance;
 class CCommanderInstance;
 class CStack;
+class CPathsInfo;
 class CCreature;
 class CLoadFile;
 class CSaveFile;
@@ -73,7 +75,7 @@ public:
 };
 
 /// Central class for managing human player / AI interface logic
-class CGameInterface : public CBattleGameInterface, public IGameEventsReceiver
+class DLL_LINKAGE CGameInterface : public CBattleGameInterface, public IGameEventsReceiver
 {
 public:
 	virtual void init(shared_ptr<CCallback> CB){};

+ 31 - 16
lib/CGameState.cpp

@@ -2165,10 +2165,10 @@ void CGameState::apply(CPack *pack)
 	applierGs->apps[typ]->applyOnGS(this,pack);
 }
 
-void CGameState::calculatePaths(const CGHeroInstance *hero, CPathsInfo &out, int3 src, int movement)
+void CGameState::calculatePaths(const CGHeroInstance *hero, CPathsInfo &out)
 {
 	CPathfinder pathfinder(out, this, hero);
-	pathfinder.calculatePaths(src, movement);
+	pathfinder.calculatePaths();
 }
 
 /**
@@ -2896,9 +2896,29 @@ bool CGPathNode::reachable() const
 	return turns < 255;
 }
 
-bool CPathsInfo::getPath( const int3 &dst, CGPath &out )
+const CGPathNode * CPathsInfo::getPathInfo( int3 tile ) const
 {
-	assert(isValid);
+	boost::unique_lock<boost::mutex> pathLock(pathMx);
+
+	if (tile.x >= sizes.x || tile.y >= sizes.y || tile.z >= sizes.z)
+		return nullptr;
+	return &nodes[tile.x][tile.y][tile.z];
+}
+
+int CPathsInfo::getDistance( int3 tile ) const
+{
+	boost::unique_lock<boost::mutex> pathLock(pathMx);
+
+	CGPath ret;
+	if (getPath(tile, ret))
+		return ret.nodes.size();
+	else
+		return 255;
+}
+
+bool CPathsInfo::getPath( const int3 &dst, CGPath &out ) const
+{
+	boost::unique_lock<boost::mutex> pathLock(pathMx);
 
 	out.nodes.clear();
 	const CGPathNode *curnode = &nodes[dst.x][dst.y][dst.z];
@@ -2965,7 +2985,7 @@ void CGPath::convert( ui8 mode )
 }
 
 PlayerState::PlayerState()
- : color(-1), currentSelection(0xffffffff), enteredWinningCheatCode(0),
+ : color(-1), enteredWinningCheatCode(0),
    enteredLosingCheatCode(0), status(EPlayerStatus::INGAME)
 {
 	setNodeType(PLAYER);
@@ -3277,18 +3297,15 @@ void CPathfinder::initializeGraph()
 	}
 }
 
-void CPathfinder::calculatePaths(int3 src /*= int3(-1,-1,-1)*/, int movement /*= -1*/)
+void CPathfinder::calculatePaths()
 {
 	assert(hero);
 	assert(hero == getHero(hero->id));
 
-	if(src.x < 0)
-		src = hero->getPosition(false);
-	if(movement < 0)
-		movement = hero->movement;
 	bool flying = hero->hasBonusOfType(Bonus::FLYING_MOVEMENT);
 	int maxMovePointsLand = hero->maxMovePoints(true);
 	int maxMovePointsWater = hero->maxMovePoints(false);
+	int3 src = hero->getPosition(false);
 
 	auto maxMovePoints = [&](CGPathNode *cp) -> int
 	{
@@ -3296,9 +3313,9 @@ void CPathfinder::calculatePaths(int3 src /*= int3(-1,-1,-1)*/, int movement /*=
 	};
 
 	out.hero = hero;
-	out.hpos = src;
+	out.hpos = hero->getPosition(false);
 
-	if(!gs->map->isInTheMap(src)/* || !gs->map->isInTheMap(dest)*/) //check input
+	if(!gs->map->isInTheMap(out.hpos)/* || !gs->map->isInTheMap(dest)*/) //check input
 	{
         logGlobal->errorStream() << "CGameState::calculatePaths: Hero outside the gs->map? How dare you...";
 		return;
@@ -3308,9 +3325,9 @@ void CPathfinder::calculatePaths(int3 src /*= int3(-1,-1,-1)*/, int movement /*=
 	initializeGraph();
 
 	//initial tile - set cost on 0 and add to the queue
-	CGPathNode &initialNode = *getNode(src);
+	CGPathNode &initialNode = *getNode(out.hpos);
 	initialNode.turns = 0;
-	initialNode.moveRemains = movement;
+	initialNode.moveRemains = hero->movement;
 	mq.push_back(&initialNode);
 
 	std::vector<int3> neighbours;
@@ -3426,8 +3443,6 @@ void CPathfinder::calculatePaths(int3 src /*= int3(-1,-1,-1)*/, int movement /*=
 			}
 		} //neighbours loop
 	} //queue loop
-
-	out.isValid = true;
 }
 
 CGPathNode *CPathfinder::getNode(const int3 &coord)

+ 3 - 48
lib/CGameState.h

@@ -164,7 +164,6 @@ struct DLL_LINKAGE PlayerState : public CBonusSystemNode
 public:
 	PlayerColor color;
 	bool human; //true if human controlled player, false for AI
-	ObjectInstanceID currentSelection; //id of hero/town, 0xffffffff if none
 	TeamID team;
 	TResources resources;
 	std::set<ObjectInstanceID> visitedObjects; // as a std::set, since most accesses here will be from visited status checks
@@ -183,7 +182,7 @@ public:
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & color & human & currentSelection & team & resources & status;
+		h & color & human & team & resources & status;
 		h & heroes & towns & availableHeroes & dwellings & visitedObjects;
 		h & getBonusList(); //FIXME FIXME FIXME
 		h & status & daysWithoutCastle;
@@ -217,50 +216,6 @@ struct UpgradeInfo
 	UpgradeInfo(){oldID = CreatureID::NONE;};
 };
 
-struct DLL_LINKAGE CGPathNode
-{
-	enum EAccessibility
-	{
-		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
-		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
-
-	CGPathNode();
-	bool reachable() const;
-};
-
-struct DLL_LINKAGE CGPath
-{
-	std::vector<CGPathNode> nodes; //just get node by node
-
-	int3 startPos() const; // start point
-	int3 endPos() const; //destination point
-	void convert(ui8 mode); //mode=0 -> from 'manifest' to 'object'
-};
-
-struct DLL_LINKAGE CPathsInfo
-{
-	bool isValid;
-	const CGHeroInstance *hero;
-	int3 hpos;
-	int3 sizes;
-	CGPathNode ***nodes; //[w][h][level]
-
-	bool getPath(const int3 &dst, CGPath &out);
-	CPathsInfo(const int3 &Sizes);
-	~CPathsInfo();
-};
-
 struct DLL_EXPORT DuelParameters
 {
 	ETerrainType terType;
@@ -350,7 +305,7 @@ private:
 
 public:
 	CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero);
-	void calculatePaths(int3 src = int3(-1,-1,-1), int movement = -1); //calculates possible paths for hero, by default uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists
+	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
 };
 
 
@@ -398,7 +353,7 @@ public:
 	UpgradeInfo getUpgradeInfo(const CStackInstance &stack);
 	PlayerRelations::PlayerRelations getPlayerRelations(PlayerColor color1, PlayerColor color2);
 	bool checkForVisitableDir(const int3 & src, const int3 & dst) const; //check if src tile is visitable from dst tile
-	void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out, int3 src = int3(-1,-1,-1), int movement = -1); //calculates possible paths for hero, by default uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists
+	void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out); //calculates possible paths for hero, by default uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists
 	int3 guardingCreaturePosition (int3 pos) const;
 	std::vector<CGObjectInstance*> guardingCreatures (int3 pos) const;
 

+ 46 - 0
lib/CGameStateFwd.h

@@ -118,3 +118,49 @@ struct DLL_LINKAGE QuestInfo //universal interface for human and AI
 	}
 };
 
+struct DLL_LINKAGE CGPathNode
+{
+	enum EAccessibility
+	{
+		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
+		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
+
+	CGPathNode();
+	bool reachable() const;
+};
+
+struct DLL_LINKAGE CGPath
+{
+	std::vector<CGPathNode> nodes; //just get node by node
+
+	int3 startPos() const; // start point
+	int3 endPos() const; //destination point
+	void convert(ui8 mode); //mode=0 -> from 'manifest' to 'object'
+};
+
+struct DLL_LINKAGE CPathsInfo
+{
+	mutable boost::mutex pathMx;
+
+	const CGHeroInstance *hero;
+	int3 hpos;
+	int3 sizes;
+	CGPathNode ***nodes; //[w][h][level]
+
+	const CGPathNode * getPathInfo( int3 tile ) const;
+	bool getPath(const int3 &dst, CGPath &out) const;
+	int getDistance( int3 tile ) const;
+	CPathsInfo(const int3 &Sizes);
+	~CPathsInfo();
+};

+ 3 - 0
lib/Connection.cpp

@@ -45,6 +45,9 @@ CTypeList typeList;
 
 void CConnection::init()
 {
+	boost::asio::ip::tcp::no_delay option(true);
+	socket->set_option(option);
+
 	enableSmartPointerSerializatoin();
 	disableStackSendingByID();
 	registerTypes(static_cast<CISer<CConnection>&>(*this));

+ 9 - 23
lib/NetPacks.h

@@ -1803,15 +1803,17 @@ struct RazeStructure : public BuildStructure
 struct RecruitCreatures : public CPackForServer
 {
 	RecruitCreatures(){};
-	RecruitCreatures(ObjectInstanceID TID, CreatureID CRID, si32 Amount, si32 Level):tid(TID),crid(CRID),amount(Amount),level(Level){};
-	ObjectInstanceID tid; //town id
+	RecruitCreatures(ObjectInstanceID TID, ObjectInstanceID DST, CreatureID CRID, si32 Amount, si32 Level):
+	    tid(TID), dst(DST), crid(CRID), amount(Amount), level(Level){};
+	ObjectInstanceID tid; //dwelling id, or town
+	ObjectInstanceID dst; //destination ID, e.g. hero
 	CreatureID crid;
 	ui32 amount;//creature amount
 	si32 level;//dwelling level to buy from, -1 if any
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & tid & crid & amount & level;
+		h & tid & dst & crid & amount & level;
 	}
 };
 
@@ -2033,8 +2035,8 @@ struct SaveGame : public CPackForClient, public CPackForServer
 struct PlayerMessage : public CPackForClient, public CPackForServer //513
 {
 	PlayerMessage(){CPackForClient::type = 513;};
-	PlayerMessage(PlayerColor Player, const std::string &Text)
-		:player(Player),text(Text)
+	PlayerMessage(PlayerColor Player, const std::string &Text, ObjectInstanceID obj)
+		:player(Player),text(Text), currObj(obj)
 	{CPackForClient::type = 513;};
 	void applyCl(CClient *cl);
 	void applyGs(CGameState *gs){};
@@ -2042,27 +2044,11 @@ struct PlayerMessage : public CPackForClient, public CPackForServer //513
 
 	PlayerColor player;
 	std::string text;
+	ObjectInstanceID currObj; // optional parameter that specifies current object. For cheats :)
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & text & player;
-	}
-};
-
-
-struct SetSelection : public CPackForClient, public CPackForServer //514
-{
-	SetSelection(){CPackForClient::type = 514;};
-	DLL_LINKAGE void applyGs(CGameState *gs);
-	bool applyGh(CGameHandler *gh);
-	void applyCl(CClient *cl);
-
-	PlayerColor player;
-	ObjectInstanceID id;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & id & player;
+		h & text & player & currObj;
 	}
 };
 

+ 0 - 5
lib/NetPacksLib.cpp

@@ -1658,11 +1658,6 @@ DLL_LINKAGE void YourTurn::applyGs( CGameState *gs )
 	}
 }
 
-DLL_LINKAGE void SetSelection::applyGs( CGameState *gs )
-{
-	gs->getPlayer(player)->currentSelection = id;
-}
-
 DLL_LINKAGE Component::Component(const CStackBasicDescriptor &stack)
 	: id(CREATURE), subtype(stack.type->idNumber), val(stack.count), when(0)
 {

+ 0 - 2
lib/registerTypes/RegisterTypes.h

@@ -298,7 +298,6 @@ void registerTypesClientPacks2(Serializer &s)
 	s.template registerType<CArtifactOperationPack, DisassembledArtifact>();
 
 	s.template registerType<CPackForClient, SaveGame>();
-	s.template registerType<CPackForClient, SetSelection>();
 	s.template registerType<CPackForClient, PlayerMessage>();
 }
 
@@ -332,7 +331,6 @@ void registerTypesServerPacks(Serializer &s)
 	s.template registerType<CPackForServer, CommitPackage>();
 
 	s.template registerType<CPackForServer, SaveGame>();
-	s.template registerType<CPackForServer, SetSelection>();
 	s.template registerType<CPackForServer, PlayerMessage>();
 }
 

+ 24 - 30
server/CGameHandler.cpp

@@ -2562,7 +2562,7 @@ void CGameHandler::sendMessageToAll( const std::string &message )
 	sendToAllClients(&sm);
 }
 
-bool CGameHandler::recruitCreatures( ObjectInstanceID objid, CreatureID crid, ui32 cram, si32 fromLvl )
+bool CGameHandler::recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dstid, CreatureID crid, ui32 cram, si32 fromLvl )
 {
 	const CGDwelling *dw = static_cast<const CGDwelling*>(gs->getObj(objid));
 	const CArmedInstance *dst = nullptr;
@@ -2570,14 +2570,8 @@ bool CGameHandler::recruitCreatures( ObjectInstanceID objid, CreatureID crid, ui
 	bool warMachine = c->hasBonusOfType(Bonus::SIEGE_WEAPON);
 
 	//TODO: test for owning
-
-	if(dw->ID == Obj::TOWN)
-		dst = (static_cast<const CGTownInstance *>(dw))->getUpperArmy();
-	else if(dw->ID == Obj::CREATURE_GENERATOR1  ||  dw->ID == Obj::CREATURE_GENERATOR4
-		||  dw->ID == Obj::REFUGEE_CAMP) //advmap dwelling
-		dst = getHero(gs->getPlayer(dw->tempOwner)->currentSelection); //TODO: check if current hero is really visiting dwelling
-	else if(dw->ID == Obj::WAR_MACHINE_FACTORY)
-		dst = dynamic_cast<const CGHeroInstance *>(getTile(dw->visitablePos())->visitableObjects.back());
+	//TODO: check if dst can recruit objects (e.g. hero is actually visiting object, town and source are same, etc)
+	dst = dynamic_cast<const CArmedInstance*>(getObj(dstid));
 
 	assert(dw && dst);
 
@@ -3784,10 +3778,10 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 	return ok;
 }
 
-void CGameHandler::playerMessage( PlayerColor player, const std::string &message )
+void CGameHandler::playerMessage( PlayerColor player, const std::string &message, ObjectInstanceID currObj )
 {
 	bool cheated=true;
-	PlayerMessage temp_message(player, message);
+	PlayerMessage temp_message(player, message, ObjectInstanceID(-1)); // don't inform other client on selected object
 
 	sendAndApply(&temp_message);
 	if(message == "vcmiistari") //give all spells and 999 mana
@@ -3795,7 +3789,7 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message
 		SetMana sm;
 		ChangeSpells cs;
 
-		CGHeroInstance *h = gs->getHero(gs->getPlayer(player)->currentSelection);
+		CGHeroInstance *h = gs->getHero(currObj);
 		if(!h && complain("Cannot realize cheat, no hero selected!")) return;
 
 		sm.hid = cs.hid = h->id;
@@ -3819,13 +3813,13 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message
 	}
 	else if (message == "vcmiarmenelos") //build all buildings in selected town
 	{
-		CGHeroInstance *hero = gs->getHero(gs->getPlayer(player)->currentSelection);
+		CGHeroInstance *hero = gs->getHero(currObj);
 		CGTownInstance *town;
 
 		if (hero)
 			town = hero->visitedTown;
 		else
-			town = gs->getTown(gs->getPlayer(player)->currentSelection);
+			town = gs->getTown(currObj);
 
 		if (town)
 		{
@@ -3842,7 +3836,7 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message
 	}
 	else if(message == "vcmiainur") //gives 5 archangels into each slot
 	{
-		CGHeroInstance *hero = gs->getHero(gs->getPlayer(player)->currentSelection);
+		CGHeroInstance *hero = gs->getHero(currObj);
 		const CCreature *archangel = VLC->creh->creatures.at(13);
 		if(!hero) return;
 
@@ -3852,7 +3846,7 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message
 	}
 	else if(message == "vcmiangband") //gives 10 black knight into each slot
 	{
-		CGHeroInstance *hero = gs->getHero(gs->getPlayer(player)->currentSelection);
+		CGHeroInstance *hero = gs->getHero(currObj);
 		const CCreature *blackKnight = VLC->creh->creatures.at(66);
 		if(!hero) return;
 
@@ -3862,7 +3856,7 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message
 	}
 	else if(message == "vcminoldor") //all war machines
 	{
-		CGHeroInstance *hero = gs->getHero(gs->getPlayer(player)->currentSelection);
+		CGHeroInstance *hero = gs->getHero(currObj);
 		if(!hero) return;
 
 		if(!hero->getArt(ArtifactPosition::MACH1))
@@ -3872,9 +3866,21 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message
 		if(!hero->getArt(ArtifactPosition::MACH3))
 			giveHeroNewArtifact(hero, VLC->arth->artifacts.at(6), ArtifactPosition::MACH3);
 	}
+	else if (message == "vcmiforgeofnoldorking") //hero gets all artifacts except war machines, spell scrolls and spell book
+	{
+		CGHeroInstance *hero = gs->getHero(currObj);
+		if(!hero) return;
+		for (int g = 7; g < VLC->arth->artifacts.size(); ++g) //including artifacts from mods
+			giveHeroNewArtifact(hero, VLC->arth->artifacts.at(g), ArtifactPosition::PRE_FIRST);
+	}
+	else if(message == "vcmiglorfindel") //selected hero gains a new level
+	{
+		CGHeroInstance *hero = gs->getHero(currObj);
+		changePrimSkill(hero, PrimarySkill::EXPERIENCE, VLC->heroh->reqExp(hero->level+1) - VLC->heroh->reqExp(hero->level));
+	}
 	else if(message == "vcminahar") //1000000 movement points
 	{
-		CGHeroInstance *hero = gs->getHero(gs->getPlayer(player)->currentSelection);
+		CGHeroInstance *hero = gs->getHero(currObj);
 		if(!hero) return;
 		SetMovePoints smp;
 		smp.hid = hero->id;
@@ -3907,11 +3913,6 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message
 		delete [] hlp_tab;
 		sendAndApply(&fc);
 	}
-	else if(message == "vcmiglorfindel") //selected hero gains a new level
-	{
-		CGHeroInstance *hero = gs->getHero(gs->getPlayer(player)->currentSelection);
-		changePrimSkill(hero, PrimarySkill::EXPERIENCE, VLC->heroh->reqExp(hero->level+1) - VLC->heroh->reqExp(hero->level));
-	}
 	else if(message == "vcmisilmaril") //player wins
 	{
 		gs->getPlayer(player)->enteredWinningCheatCode = 1;
@@ -3920,13 +3921,6 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message
 	{
 		gs->getPlayer(player)->enteredLosingCheatCode = 1;
 	}
-	else if (message == "vcmiforgeofnoldorking") //hero gets all artifacts except war machines, spell scrolls and spell book
-	{
-		CGHeroInstance *hero = gs->getHero(gs->getPlayer(player)->currentSelection);
-		if(!hero) return;
-		for (int g = 7; g < VLC->arth->artifacts.size(); ++g) //including artifacts from mods
-			giveHeroNewArtifact(hero, VLC->arth->artifacts.at(g), ArtifactPosition::PRE_FIRST);
-	}
 	else
 		cheated = false;
 	if(cheated)

+ 2 - 2
server/CGameHandler.h

@@ -198,7 +198,7 @@ public:
 	void handleConnection(std::set<PlayerColor> players, CConnection &c);
 	PlayerColor getPlayerAt(CConnection *c) const;
 
-	void playerMessage( PlayerColor player, const std::string &message);
+	void playerMessage( PlayerColor player, const std::string &message, ObjectInstanceID currObj);
 	bool makeBattleAction(BattleAction &ba);
 	bool makeAutomaticAction(const CStack *stack, BattleAction &ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack)
 	void handleSpellCasting(SpellID spellID, int spellLvl, BattleHex destination, ui8 casterSide, PlayerColor casterColor, const CGHeroInstance * caster, const CGHeroInstance * secHero,
@@ -224,7 +224,7 @@ public:
 	bool buySecSkill( const IMarket *m, const CGHeroInstance *h, SecondarySkill skill);
 	bool garrisonSwap(ObjectInstanceID tid);
 	bool upgradeCreature( ObjectInstanceID objid, SlotID pos, CreatureID upgID );
-	bool recruitCreatures(ObjectInstanceID objid, CreatureID crid, ui32 cram, si32 level);
+	bool recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dst, CreatureID crid, ui32 cram, si32 level);
 	bool buildStructure(ObjectInstanceID tid, BuildingID bid, bool force=false);//force - for events: no cost, no checkings
 	bool razeStructure(ObjectInstanceID tid, BuildingID bid);
 	bool disbandCreature( ObjectInstanceID id, SlotID pos );

+ 2 - 14
server/NetPacksServer.cpp

@@ -114,7 +114,7 @@ bool BuildStructure::applyGh( CGameHandler *gh )
 
 bool RecruitCreatures::applyGh( CGameHandler *gh )
 {
-	return gh->recruitCreatures(tid,crid,amount,level);
+	return gh->recruitCreatures(tid,dst,crid,amount,level);
 }
 
 bool UpgradeCreature::applyGh( CGameHandler *gh )
@@ -285,18 +285,6 @@ bool PlayerMessage::applyGh( CGameHandler *gh )
 {
 	ERROR_IF_NOT(player);
 	if(gh->getPlayerAt(c) != player) ERROR_AND_RETURN;
-	gh->playerMessage(player,text);
-	return true;
-}
-
-bool SetSelection::applyGh( CGameHandler *gh )
-{
-	ERROR_IF_NOT(player);
-	if(!gh->getObj(id))
-	{
-        logNetwork->errorStream() << "No such object...";
-		ERROR_AND_RETURN;
-	}
-	gh->sendAndApply(this);
+	gh->playerMessage(player,text, currObj);
 	return true;
 }