Browse Source

Remove pointer to objects from TerrainTile

Ivan Savenko 7 months ago
parent
commit
cd7732456a

+ 1 - 2
AI/Nullkiller/AIGateway.cpp

@@ -1332,8 +1332,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
 		{
 			auto tile = cb->getTile(coord, false);
 			assert(tile);
-			return tile->topVisitableObj(ignoreHero);
-			//return cb->getTile(coord,false)->topVisitableObj(ignoreHero);
+			return cb->getObj(tile->topVisitableObj(ignoreHero));
 		};
 
 		auto isTeleportAction = [&](EPathNodeAction action) -> bool

+ 1 - 1
AI/Nullkiller/AIUtility.cpp

@@ -202,7 +202,7 @@ bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater)
 	}
 	else if(!fromWater) // do not try to board when in water sector
 	{
-		if(t->visitableObjects.size() == 1 && t->topVisitableId() == Obj::BOAT)
+		if(t->visitableObjects.size() == 1 && cb->getObjInstance(t->topVisitableObj())->ID == Obj::BOAT)
 			return true;
 	}
 	return false;

+ 5 - 3
AI/VCAI/AIUtility.cpp

@@ -192,7 +192,7 @@ bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater)
 	}
 	else if(!fromWater) // do not try to board when in water sector
 	{
-		if(t->visitableObjects.size() == 1 && t->topVisitableId() == Obj::BOAT)
+		if(t->visitableObjects.size() == 1 && cb->getObjInstance(t->topVisitableObj())->ID == Obj::BOAT)
 			return true;
 	}
 	return false;
@@ -200,9 +200,11 @@ bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater)
 
 bool isBlockedBorderGate(int3 tileToHit) //TODO: is that function needed? should be handled by pathfinder
 {
-	if(cb->getTile(tileToHit)->topVisitableId() != Obj::BORDER_GATE)
+	const auto * object = cb->getTopObj(tileToHit);
+	if( object && object->id != Obj::BORDER_GATE)
 		return false;
-	auto gate = dynamic_cast<const CGKeys *>(cb->getTile(tileToHit)->topVisitableObj());
+
+	auto gate = dynamic_cast<const CGKeys *>(object);
 	return !gate->passableFor(ai->playerID);
 }
 

+ 1 - 1
AI/VCAI/Pathfinding/PathfindingManager.cpp

@@ -192,7 +192,7 @@ Goals::TSubgoal PathfindingManager::clearWayTo(HeroPtr hero, int3 firstTileToGet
 	if(isBlockedBorderGate(firstTileToGet))
 	{
 		//FIXME: this way we'll not visit gate and activate quest :?
-		return sptr(Goals::FindObj(Obj::KEYMASTER, cb->getTile(firstTileToGet)->visitableObjects.back()->getObjTypeIndex()));
+		return sptr(Goals::FindObj(Obj::KEYMASTER, cb->getTopObj(firstTileToGet)->getObjTypeIndex()));
 	}
 
 	auto topObj = cb->getTopObj(firstTileToGet);

+ 1 - 4
AI/VCAI/VCAI.cpp

@@ -1868,10 +1868,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
 
 		auto getObj = [&](int3 coord, bool ignoreHero)
 		{
-			auto tile = cb->getTile(coord, false);
-			assert(tile);
-			return tile->topVisitableObj(ignoreHero);
-			//return cb->getTile(coord,false)->topVisitableObj(ignoreHero);
+			return cb->getObj(cb->getTile(coord)->topVisitableObj(ignoreHero));
 		};
 
 		auto isTeleportAction = [&](EPathNodeAction action) -> bool

+ 11 - 4
client/NetPacksClient.cpp

@@ -595,7 +595,7 @@ void ApplyClientNetPackVisitor::visitSetAvailableCreatures(SetAvailableCreatures
 
 	PlayerColor p;
 	if(dw->ID == Obj::WAR_MACHINE_FACTORY) //War Machines Factory is not flaggable, it's "owned" by visitor
-		p = cl.getTile(dw->visitablePos())->visitableObjects.back()->tempOwner;
+		p = cl.getObjInstance(cl.getTile(dw->visitablePos())->visitableObjects.back())->getOwner();
 	else
 		p = dw->tempOwner;
 
@@ -1016,7 +1016,9 @@ void ApplyClientNetPackVisitor::visitOpenWindow(OpenWindow & pack)
 			const CGObjectInstance *obj = cl.getObj(ObjectInstanceID(pack.object));
 			const CGHeroInstance *hero = cl.getHero(ObjectInstanceID(pack.visitor));
 			const auto market = cl.getMarket(pack.object);
-			callInterfaceIfPresent(cl, cl.getTile(obj->visitablePos())->visitableObjects.back()->tempOwner, &IGameEventsReceiver::showMarketWindow, market, hero, pack.queryID);
+			const auto * tile = cl.getTile(obj->visitablePos());
+			const auto * topObject = cl.getObjInstance(tile->visitableObjects.back());
+			callInterfaceIfPresent(cl, topObject->getOwner(), &IGameEventsReceiver::showMarketWindow, market, hero, pack.queryID);
 		}
 		break;
 	case EOpenWindowMode::HILL_FORT_WINDOW:
@@ -1025,7 +1027,9 @@ void ApplyClientNetPackVisitor::visitOpenWindow(OpenWindow & pack)
 			//displays Hill fort window
 			const CGObjectInstance *obj = cl.getObj(ObjectInstanceID(pack.object));
 			const CGHeroInstance *hero = cl.getHero(ObjectInstanceID(pack.visitor));
-			callInterfaceIfPresent(cl, cl.getTile(obj->visitablePos())->visitableObjects.back()->tempOwner, &IGameEventsReceiver::showHillFortWindow, obj, hero);
+			const auto * tile = cl.getTile(obj->visitablePos());
+			const auto * topObject = cl.getObjInstance(tile->visitableObjects.back());
+			callInterfaceIfPresent(cl, topObject->getOwner(), &IGameEventsReceiver::showHillFortWindow, obj, hero);
 		}
 		break;
 	case EOpenWindowMode::PUZZLE_MAP:
@@ -1076,7 +1080,10 @@ void ApplyClientNetPackVisitor::visitSetAvailableArtifacts(SetAvailableArtifacts
 	{
 		const CGBlackMarket *bm = dynamic_cast<const CGBlackMarket *>(cl.getObj(ObjectInstanceID(pack.id)));
 		assert(bm);
-		callInterfaceIfPresent(cl, cl.getTile(bm->visitablePos())->visitableObjects.back()->tempOwner, &IGameEventsReceiver::availableArtifactsChanged, bm);
+		const auto * tile = cl.getTile(bm->visitablePos());
+		const auto * topObject = cl.getObjInstance(tile->visitableObjects.back());
+
+		callInterfaceIfPresent(cl, topObject->getOwner(), &IGameEventsReceiver::availableArtifactsChanged, bm);
 	}
 }
 

+ 2 - 1
client/adventureMap/CMinimap.cpp

@@ -39,8 +39,9 @@ ColorRGBA CMinimapInstance::getTileColor(const int3 & pos) const
 		return Colors::BLACK;
 
 	// if object at tile is owned - it will be colored as its owner
-	for (const CGObjectInstance *obj : tile->blockingObjects)
+	for (const ObjectInstanceID objectID : tile->blockingObjects)
 	{
+		const auto * obj = GAME->interface()->cb->getObj(objectID);
 		PlayerColor player = obj->getOwner();
 		if(player == PlayerColor::NEUTRAL)
 			return graphics->neutralColor;

+ 5 - 3
client/mapView/MapRendererContext.cpp

@@ -278,10 +278,12 @@ std::string MapRendererAdventureContext::overlayText(const int3 & coordinates) c
 	if (!tile.visitable())
 		return {};
 
-	if ( tile.visitableObjects.back()->ID == Obj::EVENT)
+	const auto * object = getObject(tile.visitableObjects.back());
+
+	if ( object->ID == Obj::EVENT)
 		return {};
 
-	return tile.visitableObjects.back()->getObjectName();
+	return object->getObjectName();
 }
 
 ColorRGBA MapRendererAdventureContext::overlayTextColor(const int3 & coordinates) const
@@ -294,7 +296,7 @@ ColorRGBA MapRendererAdventureContext::overlayTextColor(const int3 & coordinates
 	if (!tile.visitable())
 		return {};
 
-	const auto * object = tile.visitableObjects.back();
+	const auto * object = getObject(tile.visitableObjects.back());
 
 	if (object->getOwner() == GAME->interface()->playerID)
 		return { 0, 192, 0};

+ 3 - 2
client/windows/CMapOverview.cpp

@@ -74,9 +74,10 @@ std::shared_ptr<CanvasImage> CMapOverviewWidget::createMinimapForLayer(std::uniq
 
 			if(drawPlayerElements)
 				// if object at tile is owned - it will be colored as its owner
-				for (const CGObjectInstance *obj : tile.blockingObjects)
+				for (ObjectInstanceID objectID : tile.blockingObjects)
 				{
-					PlayerColor player = obj->getOwner();
+					const auto * object = map->getObject(objectID);
+					PlayerColor player = object->getOwner();
 					if(player == PlayerColor::NEUTRAL)
 					{
 						color = graphics->neutralColor;

+ 1 - 1
lib/CCreatureSet.cpp

@@ -866,7 +866,7 @@ TerrainId CStackInstance::getNativeTerrain() const
 
 TerrainId CStackInstance::getCurrentTerrain() const
 {
-	return armyObj->getCurrentTerrain();
+	return getArmy()->getCurrentTerrain();
 }
 
 void CStackInstance::deserializationFix()

+ 13 - 7
lib/CGameInfoCallback.cpp

@@ -453,8 +453,8 @@ std::vector <const CGObjectInstance *> CGameInfoCallback::getBlockingObjs( int3
 	const TerrainTile *t = getTile(pos);
 	ERROR_RET_VAL_IF(!t, "Not a valid tile requested!", ret);
 
-	for(const CGObjectInstance * obj : t->blockingObjects)
-		ret.push_back(obj);
+	for(const auto & objID : t->blockingObjects)
+		ret.push_back(getObj(objID));
 	return ret;
 }
 
@@ -464,10 +464,12 @@ std::vector <const CGObjectInstance *> CGameInfoCallback::getVisitableObjs(int3
 	const TerrainTile *t = getTile(pos, verbose);
 	ERROR_VERBOSE_OR_NOT_RET_VAL_IF(!t, verbose, pos.toString() + " is not visible!", ret);
 
-	for(const CGObjectInstance * obj : t->visitableObjects)
+	for(const auto & objID : t->visitableObjects)
 	{
-		if(!getPlayerID().has_value() || obj->ID != Obj::EVENT) //hide events from players
-			ret.push_back(obj);
+		const auto & object = getObj(objID);
+
+		if(!getPlayerID().has_value() || object->ID != Obj::EVENT) //hide events from players
+			ret.push_back(object);
 	}
 	return ret;
 }
@@ -492,9 +494,12 @@ std::vector <const CGObjectInstance *> CGameInfoCallback::getFlaggableObjects(in
 	std::vector<const CGObjectInstance *> ret;
 	const TerrainTile *t = getTile(pos);
 	ERROR_RET_VAL_IF(!t, "Not a valid tile requested!", ret);
-	for(const CGObjectInstance *obj : t->blockingObjects)
+	for(const auto & objectID : t->blockingObjects)
+	{
+		const auto * obj = getObj(objectID);
 		if(obj->tempOwner != PlayerColor::UNFLAGGABLE)
 			ret.push_back(obj);
+	}
 	return ret;
 }
 
@@ -724,7 +729,8 @@ bool CGameInfoCallback::isOwnedOrVisited(const CGObjectInstance *obj) const
 		return true;
 
 	const TerrainTile *t = getTile(obj->visitablePos()); //get entrance tile
-	const CGObjectInstance *visitor = t->visitableObjects.back(); //visitong hero if present or the object itself at last
+	const ObjectInstanceID visitorID = t->visitableObjects.back(); //visitong hero if present or the object itself at last
+	const CGObjectInstance * visitor = getObj(visitorID);
 	return visitor->ID == Obj::HERO && canGetFullInfo(visitor); //owned or allied hero is a visitor
 }
 

+ 14 - 10
lib/gameState/CGameState.cpp

@@ -1030,7 +1030,8 @@ BattleField CGameState::battleGetBattlefieldType(int3 tile, vstd::RNG & rand)
 
 	const TerrainTile &t = map->getTile(tile);
 
-	auto * topObject = t.visitableObjects.front();
+	ObjectInstanceID topObjectID = t.visitableObjects.front();
+	const CGObjectInstance * topObject = gs->getObjInstance(topObjectID);
 	if(topObject && topObject->getBattlefield() != BattleField::NONE)
 	{
 		return topObject->getBattlefield();
@@ -1129,9 +1130,9 @@ void CGameState::calculatePaths(const std::shared_ptr<PathfinderConfig> & config
  * @return int3(-1, -1, -1) if the tile is unguarded, or the position of
  * the monster guarding the tile.
  */
-std::vector<CGObjectInstance*> CGameState::guardingCreatures (int3 pos) const
+std::vector<const CGObjectInstance*> CGameState::guardingCreatures (int3 pos) const
 {
-	std::vector<CGObjectInstance*> guards;
+	std::vector<const CGObjectInstance*> guards;
 	const int3 originalPos = pos;
 	if (!map->isInTheMap(pos))
 		return guards;
@@ -1139,12 +1140,13 @@ std::vector<CGObjectInstance*> CGameState::guardingCreatures (int3 pos) const
 	const TerrainTile &posTile = map->getTile(pos);
 	if (posTile.visitable())
 	{
-		for (CGObjectInstance* obj : posTile.visitableObjects)
+		for (ObjectInstanceID objectID : posTile.visitableObjects)
 		{
-			if(obj->isBlockedVisitable())
+			const CGObjectInstance * object = getObjInstance(objectID);
+			if(object->isBlockedVisitable())
 			{
-				if (obj->ID == Obj::MONSTER) // Monster
-					guards.push_back(obj);
+				if (object->ID == Obj::MONSTER) // Monster
+					guards.push_back(object);
 			}
 		}
 	}
@@ -1158,11 +1160,13 @@ std::vector<CGObjectInstance*> CGameState::guardingCreatures (int3 pos) const
 				const auto & tile = map->getTile(pos);
 				if (tile.visitable() && (tile.isWater() == posTile.isWater()))
 				{
-					for (CGObjectInstance* obj : tile.visitableObjects)
+					for (ObjectInstanceID objectID : tile.visitableObjects)
 					{
-						if (obj->ID == Obj::MONSTER  &&  map->checkForVisitableDir(pos, &map->getTile(originalPos), originalPos)) // Monster being able to attack investigated tile
+						const CGObjectInstance * object = getObjInstance(objectID);
+
+						if (object->ID == Obj::MONSTER  &&  map->checkForVisitableDir(pos, &map->getTile(originalPos), originalPos)) // Monster being able to attack investigated tile
 						{
-							guards.push_back(obj);
+							guards.push_back(object);
 						}
 					}
 				}

+ 1 - 1
lib/gameState/CGameState.h

@@ -99,7 +99,7 @@ public:
 	bool checkForVisitableDir(const int3 & src, const int3 & dst) const; //check if src tile is visitable from dst tile
 	void calculatePaths(const std::shared_ptr<PathfinderConfig> & config) const override;
 	int3 guardingCreaturePosition (int3 pos) const override;
-	std::vector<CGObjectInstance*> guardingCreatures (int3 pos) const;
+	std::vector<const CGObjectInstance*> guardingCreatures (int3 pos) const;
 
 	/// Gets a artifact ID randomly and removes the selected artifact from this handler.
 	ArtifactID pickRandomArtifact(vstd::RNG & rand, int flags);

+ 7 - 2
lib/mapObjects/IObjectInterface.cpp

@@ -96,9 +96,12 @@ int3 IBoatGenerator::bestLocation() const
 		if (tile->blocked())
 		{
 			bool hasBoat = false;
-			for (auto const * object : tile->blockingObjects)
+			for (auto const & objectID : tile->blockingObjects)
+			{
+				const auto * object = getObject()->cb->getObj(objectID);
 				if (object->ID == Obj::BOAT || object->ID == Obj::HERO)
 					hasBoat = true;
+			}
 
 			if (!hasBoat)
 				continue; // tile is blocked, but not by boat -> check next potential position
@@ -122,7 +125,9 @@ IBoatGenerator::EGeneratorState IBoatGenerator::shipyardStatus() const
 	if(t->blockingObjects.empty())
 		return GOOD; //OK
 
-	if(t->blockingObjects.front()->ID == Obj::BOAT || t->blockingObjects.front()->ID == Obj::HERO)
+	auto blockerObject = getObject()->cb->getObjInstance(t->blockingObjects.front());
+
+	if(blockerObject->ID == Obj::BOAT || blockerObject->ID == Obj::HERO)
 		return BOAT_ALREADY_BUILT; //blocked with boat
 
 	return TILE_BLOCKED; //blocked

+ 6 - 4
lib/mapObjects/MiscObjects.cpp

@@ -322,14 +322,16 @@ bool CGTeleport::isConnected(const CGObjectInstance * src, const CGObjectInstanc
 
 bool CGTeleport::isExitPassable(CGameState * gs, const CGHeroInstance * h, const CGObjectInstance * obj)
 {
-	auto * objTopVisObj = gs->getMap().getTile(obj->visitablePos()).topVisitableObj();
-	if(objTopVisObj->ID == Obj::HERO)
+	ObjectInstanceID topObjectID = gs->getMap().getTile(obj->visitablePos()).topVisitableObj();
+	const CGObjectInstance * topObject = gs->getObjInstance(topObjectID);
+
+	if(topObject->ID == Obj::HERO)
 	{
-		if(h->id == objTopVisObj->id) // Just to be sure it's won't happen.
+		if(h->id == topObject->id) // Just to be sure it's won't happen.
 			return false;
 
 		// Check if it's friendly hero or not
-		if(gs->getPlayerRelations(h->tempOwner, objTopVisObj->tempOwner) != PlayerRelations::ENEMIES)
+		if(gs->getPlayerRelations(h->tempOwner, topObject->tempOwner) != PlayerRelations::ENEMIES)
 		{
 			// Exchange between heroes only possible via subterranean gates
 			if(!dynamic_cast<const CGSubterraneanGate *>(obj))

+ 17 - 21
lib/mapping/CMap.cpp

@@ -143,15 +143,10 @@ bool TerrainTile::isClear(const TerrainTile * from) const
 	return entrableTerrain(from) && !blocked();
 }
 
-Obj TerrainTile::topVisitableId(bool excludeTop) const
-{
-	return topVisitableObj(excludeTop) ? topVisitableObj(excludeTop)->ID : Obj(Obj::NO_OBJ);
-}
-
-CGObjectInstance * TerrainTile::topVisitableObj(bool excludeTop) const
+ObjectInstanceID TerrainTile::topVisitableObj(bool excludeTop) const
 {
 	if(visitableObjects.empty() || (excludeTop && visitableObjects.size() == 1))
-		return nullptr;
+		return {};
 
 	if(excludeTop)
 		return visitableObjects[visitableObjects.size()-2];
@@ -202,10 +197,10 @@ void CMap::removeBlockVisTiles(CGObjectInstance * obj, bool total)
 			{
 				TerrainTile & curt = terrain[zVal][xVal][yVal];
 				if(total || obj->visitableAt(int3(xVal, yVal, zVal)))
-					curt.visitableObjects -= obj;
+					curt.visitableObjects -= obj->id;
 
 				if(total || obj->blockingAt(int3(xVal, yVal, zVal)))
-					curt.blockingObjects -= obj;
+					curt.blockingObjects -= obj->id;
 			}
 		}
 	}
@@ -224,10 +219,10 @@ void CMap::addBlockVisTiles(CGObjectInstance * obj)
 			{
 				TerrainTile & curt = terrain[zVal][xVal][yVal];
 				if(obj->visitableAt(int3(xVal, yVal, zVal)))
-					curt.visitableObjects.push_back(obj);
+					curt.visitableObjects.push_back(obj->id);
 
 				if(obj->blockingAt(int3(xVal, yVal, zVal)))
-					curt.blockingObjects.push_back(obj);
+					curt.blockingObjects.push_back(obj->id);
 			}
 		}
 	}
@@ -300,12 +295,12 @@ bool CMap::checkForVisitableDir(const int3 & src, const TerrainTile * pom, const
 {
 	if (!pom->entrableTerrain()) //rock is never accessible
 		return false;
-	for(auto * obj : pom->visitableObjects) //checking destination tile
+	for(const auto & objID : pom->visitableObjects) //checking destination tile
 	{
-		if(!vstd::contains(pom->blockingObjects, obj)) //this visitable object is not blocking, ignore
+		if(!vstd::contains(pom->blockingObjects, objID)) //this visitable object is not blocking, ignore
 			continue;
 
-		if (!obj->appearance->isVisitableFrom(src.x - dst.x, src.y - dst.y))
+		if (!getObject(objID)->appearance->isVisitableFrom(src.x - dst.x, src.y - dst.y))
 			return false;
 	}
 	return true;
@@ -320,9 +315,10 @@ int3 CMap::guardingCreaturePosition (int3 pos) const
 	const TerrainTile &posTile = getTile(pos);
 	if (posTile.visitable())
 	{
-		for (CGObjectInstance* obj : posTile.visitableObjects)
+		for (const auto & objID : posTile.visitableObjects)
 		{
-			if (obj->ID == Obj::MONSTER)
+			const auto * object = getObject(objID);
+			if (object->ID == Obj::MONSTER)
 				return pos;
 		}
 	}
@@ -340,12 +336,11 @@ int3 CMap::guardingCreaturePosition (int3 pos) const
 				const auto & tile = getTile(pos);
 				if (tile.visitable() && (tile.isWater() == water))
 				{
-					for (CGObjectInstance* obj : tile.visitableObjects)
+					for (const auto & objID : tile.visitableObjects)
 					{
-						if (obj->ID == Obj::MONSTER  &&  checkForVisitableDir(pos, &posTile, originalPos)) // Monster being able to attack investigated tile
-						{
+						const auto * object = getObject(objID);
+						if (object->ID == Obj::MONSTER  &&  checkForVisitableDir(pos, &posTile, originalPos)) // Monster being able to attack investigated tile
 							return pos;
-						}
 					}
 				}
 			}
@@ -361,8 +356,9 @@ int3 CMap::guardingCreaturePosition (int3 pos) const
 
 const CGObjectInstance * CMap::getObjectiveObjectFrom(const int3 & pos, Obj type)
 {
-	for (CGObjectInstance * object : getTile(pos).visitableObjects)
+	for(const auto & objID : getTile(pos).visitableObjects)
 	{
+		const auto * object = getObject(objID);
 		if (object->ID == type)
 			return object;
 	}

+ 3 - 4
lib/mapping/CMapDefines.h

@@ -96,8 +96,7 @@ struct DLL_LINKAGE TerrainTile
 	/// Checks for blocking objects and terraint type (water / land).
 	bool isClear(const TerrainTile * from = nullptr) const;
 	/// Gets the ID of the top visitable object or -1 if there is none.
-	Obj topVisitableId(bool excludeTop = false) const;
-	CGObjectInstance * topVisitableObj(bool excludeTop = false) const;
+	ObjectInstanceID topVisitableObj(bool excludeTop = false) const;
 	inline bool isWater() const;
 	inline bool isLand() const;
 	EDiggingStatus getDiggingStatus(bool excludeTop = true) const;
@@ -127,8 +126,8 @@ struct DLL_LINKAGE TerrainTile
 	///	7th bit - whether tile is coastal (allows disembarking if land or block movement if water); 8th bit - Favorable Winds effect
 	ui8 extTileFlags;
 
-	std::vector<CGObjectInstance *> visitableObjects;
-	std::vector<CGObjectInstance *> blockingObjects;
+	std::vector<ObjectInstanceID> visitableObjects;
+	std::vector<ObjectInstanceID> blockingObjects;
 
 	template <typename Handler>
 	void serialize(Handler & h)

+ 5 - 3
lib/mapping/MapFormatH3M.cpp

@@ -2741,11 +2741,13 @@ void CMapLoaderH3M::afterRead()
 
 			const CGObjectInstance * mainTown = nullptr;
 
-			for(auto * obj : t.visitableObjects)
+			for(ObjectInstanceID objID : t.visitableObjects)
 			{
-				if(obj->ID == Obj::TOWN || obj->ID == Obj::RANDOM_TOWN)
+				const CGObjectInstance * object = map->getObject(objID);
+
+				if(object->ID == Obj::TOWN || object->ID == Obj::RANDOM_TOWN)
 				{
-					mainTown = obj;
+					mainTown = object;
 					break;
 				}
 			}

+ 5 - 3
lib/networkPacks/NetPacksLib.cpp

@@ -1313,9 +1313,11 @@ void TryMoveHero::applyGs(CGameState *gs)
 
 	if(result == EMBARK) //hero enters boat at destination tile
 	{
-
-		assert(destTile.visitableObjects.size() >= 1  &&  destTile.visitableObjects.back()->ID == Obj::BOAT); //the only visitable object at destination is Boat
-		auto * boat = dynamic_cast<CGBoat *>(destTile.visitableObjects.back());
+		const TerrainTile &tt = gs->getMap().getTile(h->convertToVisitablePos(end));
+		ObjectInstanceID topObjectID = tt.visitableObjects.back();
+		CGObjectInstance * topObject = gs->getObjInstance(topObjectID);
+		assert(tt.visitableObjects.size() >= 1 && topObject->ID == Obj::BOAT); //the only visitable object at destination is Boat
+		auto * boat = dynamic_cast<CGBoat *>(topObject);
 		assert(boat);
 
 		gs->getMap().removeBlockVisTiles(boat); //hero blockvis mask will be used, we don't need to duplicate it with boat

+ 14 - 11
lib/pathfinder/CGPathNode.cpp

@@ -103,6 +103,7 @@ PathNodeInfo::PathNodeInfo()
 void PathNodeInfo::setNode(CGameState * gs, CGPathNode * n)
 {
 	node = n;
+	guarded = false;
 
 	if(coord != node->coord)
 	{
@@ -110,23 +111,25 @@ void PathNodeInfo::setNode(CGameState * gs, CGPathNode * n)
 
 		coord = node->coord;
 		tile = gs->getTile(coord);
-		nodeObject = tile->topVisitableObj();
+		nodeObject = nullptr;
+		nodeHero = nullptr;
 
-		if(nodeObject && nodeObject->ID == Obj::HERO)
+		ObjectInstanceID topObjectID = tile->topVisitableObj();
+		if (topObjectID.hasValue())
 		{
-			nodeHero = dynamic_cast<const CGHeroInstance *>(nodeObject);
-			nodeObject = tile->topVisitableObj(true);
+			nodeObject = gs->getObjInstance(topObjectID);
 
-			if(!nodeObject)
-				nodeObject = nodeHero;
-		}
-		else
-		{
-			nodeHero = nullptr;
+			if (nodeObject->ID == Obj::HERO)
+			{
+				nodeHero = dynamic_cast<const CGHeroInstance *>(nodeObject);
+				ObjectInstanceID bottomObjectID = tile->topVisitableObj(true);
+
+				if (bottomObjectID.hasValue())
+					nodeObject = gs->getObjInstance(bottomObjectID);
+			}
 		}
 	}
 
-	guarded = false;
 }
 
 void PathNodeInfo::updateInfo(CPathfinderHelper * hlp, CGameState * gs)

+ 2 - 1
lib/pathfinder/CPathfinder.cpp

@@ -251,7 +251,8 @@ TeleporterTilesVector CPathfinderHelper::getAllowedTeleportChannelExits(const Te
 			auto pos = obj->getBlockedPos();
 			for(const auto & p : pos)
 			{
-				if(gs->getMap().getTile(p).topVisitableId() == obj->ID)
+				ObjectInstanceID topObject = gs->getMap().getTile(p).topVisitableObj();
+				if(topObject.hasValue() && getObj(topObject)->ID == obj->ID)
 					allowedExits.push_back(p);
 			}
 		}

+ 7 - 2
lib/pathfinder/PathfinderUtil.h

@@ -34,7 +34,10 @@ namespace PathfinderUtil
 		case ELayer::SAIL:
 			if(tinfo.visitable())
 			{
-				if(tinfo.visitableObjects.front()->ID == Obj::SANCTUARY && tinfo.visitableObjects.back()->ID == Obj::HERO && tinfo.visitableObjects.back()->tempOwner != player) //non-owned hero stands on Sanctuary
+				auto frontVisitable = gs->getObjInstance(tinfo.visitableObjects.front());
+				auto backVisitable = gs->getObjInstance(tinfo.visitableObjects.front());
+
+				if(frontVisitable->ID == Obj::SANCTUARY && backVisitable->ID == Obj::HERO && backVisitable->getOwner() != player) //non-owned hero stands on Sanctuary
 				{
 					return EPathAccessibility::BLOCKED;
 				}
@@ -43,8 +46,10 @@ namespace PathfinderUtil
 					bool hasBlockedVisitable = false;
 					bool hasVisitable = false;
 
-					for(const CGObjectInstance * obj : tinfo.visitableObjects)
+					for(const auto objID : tinfo.visitableObjects)
 					{
+						auto obj = gs->getObjInstance(objID);
+
 						if(obj->isBlockedVisitable())
 							hasBlockedVisitable = true;
 						else if(!obj->passableFor(player) && obj->ID != Obj::EVENT)

+ 11 - 4
lib/spells/AdventureSpellMechanics.cpp

@@ -262,7 +262,11 @@ bool ScuttleBoatMechanics::canBeCastAtImpl(spells::Problem & problem, const CGam
 		return false;
 
 	const TerrainTile * t = cb->getTile(pos);
-	if(!t || t->visitableObjects.empty() || t->visitableObjects.back()->ID != Obj::BOAT)
+	if(!t || t->visitableObjects.empty())
+		return false;
+
+	const CGObjectInstance * topObject = cb->getObj(t->visitableObjects.back());
+	if (topObject->ID != Obj::BOAT)
 		return false;
 
 	return true;
@@ -286,7 +290,7 @@ ESpellCastResult ScuttleBoatMechanics::applyAdventureEffects(SpellCastEnvironmen
 
 	RemoveObject ro;
 	ro.initiator = parameters.caster->getCasterOwner();
-	ro.objectID = t.visitableObjects.back()->id;
+	ro.objectID = t.visitableObjects.back();
 	env->apply(ro);
 	return ESpellCastResult::OK;
 }
@@ -476,7 +480,8 @@ ESpellCastResult TownPortalMechanics::applyAdventureEffects(SpellCastEnvironment
 	{
 		const TerrainTile & tile = env->getMap()->getTile(parameters.pos);
 
-		auto * const topObj = tile.topVisitableObj(false);
+		ObjectInstanceID topObjID = tile.topVisitableObj(false);
+		const CGObjectInstance * topObj = env->getMap()->getObject(topObjID);
 
 		if(!topObj)
 		{
@@ -556,7 +561,9 @@ void TownPortalMechanics::endCast(SpellCastEnvironment * env, const AdventureSpe
 	else
 	{
 		const TerrainTile & tile = env->getMap()->getTile(parameters.pos);
-		auto * const topObj = tile.topVisitableObj(false);
+		ObjectInstanceID topObjID = tile.topVisitableObj(false);
+		const CGObjectInstance * topObj = env->getMap()->getObject(topObjID);
+
 		destination = dynamic_cast<const CGTownInstance*>(topObj);
 	}
 

+ 10 - 5
mapeditor/scenelayer.cpp

@@ -128,12 +128,17 @@ void ObjectPickerLayer::highlight(std::function<bool(const CGObjectInstance *)>
 			for(int i = 0; i < map->width; ++i)
 			{
 				auto tl = map->getTile(int3(i, j, scene->level));
-				auto * obj = tl.topVisitableObj();
-				if(!obj && !tl.blockingObjects.empty())
-					obj = tl.blockingObjects.front();
+				ObjectInstanceID objID = tl.topVisitableObj();
+				if(!objID.hasValue() && !tl.blockingObjects.empty())
+					objID = tl.blockingObjects.front();
+
+				if (objID.hasValue())
+				{
+					const CGObjectInstance * obj = map->getObject(objID);
 				
-				if(obj && predicate(obj))
-					possibleObjects.insert(obj);
+					if(obj && predicate(obj))
+						possibleObjects.insert(obj);
+				}
 			}
 		}
 	}

+ 17 - 12
server/CGameHandler.cpp

@@ -826,13 +826,17 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme
 	CGObjectInstance * guardian = nullptr;
 
 	if (!t.visitableObjects.empty())
-		objectToVisit = t.visitableObjects.back();
+		objectToVisit = gameState()->getObjInstance(t.visitableObjects.back());
 
 	if (isInTheMap(guardPos))
 	{
-		for (auto const & object : getTile(guardPos)->visitableObjects)
+		for (auto const & objectID : getTile(guardPos)->visitableObjects)
+		{
+			auto object = gameState()->getObjInstance(objectID);
+
 			if (object->ID == MapObjectID::MONSTER) // exclude other objects, such as hero flying above monster
 				guardian = object;
+		}
 	}
 
 	const bool embarking = !h->boat && objectToVisit && objectToVisit->ID == Obj::BOAT;
@@ -909,10 +913,9 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme
 	// should be called if hero changes tile but before applying TryMoveHero package
 	auto leaveTile = [&]()
 	{
-		for (CGObjectInstance *obj : gs->getMap().getTile(h->visitablePos()).visitableObjects)
-		{
-			obj->onHeroLeave(h);
-		}
+		for(const auto & objID : gs->getMap().getTile(h->visitablePos()).visitableObjects)
+			gameState()->getObjInstance(objID)->onHeroLeave(h);
+
 		this->getTilesInRange(tmh.fowRevealed, h->getSightCenter()+(tmh.end-tmh.start), h->getSightRadius(), ETileVisibility::HIDDEN, h->tempOwner);
 	};
 
@@ -956,12 +959,14 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme
 	//interaction with blocking object (like resources)
 	auto blockingVisit = [&]() -> bool
 	{
-		for (CGObjectInstance *obj : t.visitableObjects)
+		for (ObjectInstanceID objectID : t.visitableObjects)
 		{
-			if(h->boat && !obj->isBlockedVisitable() && !h->boat->onboardVisitAllowed)
+			const CGObjectInstance * object = getObj(objectID);
+
+			if(h->boat && !object->isBlockedVisitable() && !h->boat->onboardVisitAllowed)
 				return doMove(TryMoveHero::SUCCESS, this->IGNORE_GUARDS, DONT_VISIT_DEST, REMAINING_ON_TILE);
 			
-			if (obj != h && obj->isBlockedVisitable() && !obj->passableFor(h->tempOwner))
+			if (object != h && object->isBlockedVisitable() && !object->passableFor(h->tempOwner))
 			{
 				EVisitDest visitDest = VISIT_DEST;
 				if(h->boat && !h->boat->onboardVisitAllowed)
@@ -3657,10 +3662,10 @@ void CGameHandler::visitObjectOnTile(const TerrainTile &t, const CGHeroInstance
 	if (!t.visitableObjects.empty())
 	{
 		//to prevent self-visiting heroes on space press
-		if (t.visitableObjects.back() != h)
-			objectVisited(t.visitableObjects.back(), h);
+		if (t.visitableObjects.back() != h->id)
+			objectVisited(gameState()->getObjInstance(t.visitableObjects.back()), h);
 		else if (t.visitableObjects.size() > 1)
-			objectVisited(*(t.visitableObjects.end()-2),h);
+			objectVisited(gameState()->getObjInstance(*(t.visitableObjects.end()-2)),h);
 	}
 }
 

+ 1 - 1
server/processors/HeroPoolProcessor.cpp

@@ -199,7 +199,7 @@ bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTy
 			return false;
 		}
 
-		if(gameHandler->getTile(mapObject->visitablePos())->visitableObjects.back() != mapObject && gameHandler->complain("Tavern entry must be unoccupied!"))
+		if(gameHandler->getTile(mapObject->visitablePos())->visitableObjects.back() != mapObject->id && gameHandler->complain("Tavern entry must be unoccupied!"))
 			return false;
 	}