Pārlūkot izejas kodu

Updated CGTeleport and new CGMonolith / CGSubterraneanGate / CGWhirlpool

Now CGTeleport is not publicly available handler, but generic class for teleport channels usage.
Teleport channels are stored as part of information about the map.
ArseniyShestakov 10 gadi atpakaļ
vecāks
revīzija
c9eba40fe6

+ 1 - 1
lib/CGameState.cpp

@@ -1867,7 +1867,7 @@ void CGameState::initMapObjects()
 			}
 		}
 	}
-	CGTeleport::postInit(); //pairing subterranean gates
+	CGSubterraneanGate::postInit(gs); //pairing subterranean gates
 
 	map->calculateGuardingGreaturePositions(); //calculate once again when all the guards are placed and initialized
 }

+ 3 - 1
lib/mapObjects/CObjectClassesHandler.cpp

@@ -83,7 +83,9 @@ CObjectClassesHandler::CObjectClassesHandler()
 	SET_HANDLER("shrine", CGShrine);
 	SET_HANDLER("sign", CGSignBottle);
 	SET_HANDLER("siren", CGSirens);
-	SET_HANDLER("teleport", CGTeleport);
+	SET_HANDLER("monolith", CGMonolith);
+	SET_HANDLER("subterraneanGate", CGSubterraneanGate);
+	SET_HANDLER("whirlpool", CGWhirlpool);
 	SET_HANDLER("university", CGUniversity);
 	SET_HANDLER("oncePerHero", CGVisitableOPH);
 	SET_HANDLER("oncePerWeek", CGVisitableOPW);

+ 294 - 101
lib/mapObjects/MiscObjects.cpp

@@ -21,8 +21,6 @@
 #include "../IGameCallback.h"
 #include "../CGameState.h"
 
-std::map<Obj, std::map<int, std::vector<ObjectInstanceID> > > CGTeleport::objs;
-std::vector<std::pair<ObjectInstanceID, ObjectInstanceID> > CGTeleport::gates;
 std::map <si32, std::vector<ObjectInstanceID> > CGMagi::eyelist;
 ui8 CGObelisk::obeliskCount; //how many obelisks are on map
 std::map<TeamID, ui8> CGObelisk::visited; //map: team_id => how many obelisks has been visited
@@ -743,135 +741,272 @@ void CGResource::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer)
 		cb->startBattleI(hero, this);
 }
 
-void CGTeleport::onHeroVisit( const CGHeroInstance * h ) const
+CGTeleport::CGTeleport() :
+	type(UNKNOWN), channel(TeleportChannelID())
 {
-	ObjectInstanceID destinationid;
-	switch(ID)
-	{
-	case Obj::MONOLITH_ONE_WAY_ENTRANCE: //one way - find corresponding exit monolith
+}
+
+bool CGTeleport::isEntrance() const
+{
+	return type == BOTH || type == ENTRANCE;
+}
+
+bool CGTeleport::isExit() const
+{
+	return type == BOTH || type == EXIT;
+}
+
+bool CGTeleport::isChannelEntrance(ObjectInstanceID id) const
+{
+	if(vstd::contains(getAllEntrances(), id))
+		return true;
+	else
+		return false;
+}
+
+bool CGTeleport::isChannelExit(ObjectInstanceID id) const
+{
+	if(vstd::contains(getAllExits(), id))
+		return true;
+	else
+		return false;
+}
+
+std::vector<ObjectInstanceID> CGTeleport::getAllEntrances(bool excludeCurrent) const
+{
+	std::vector<ObjectInstanceID> ret = cb->getTeleportChannelEntraces(channel);
+	if(excludeCurrent)
+		ret.erase(std::remove(ret.begin(), ret.end(), id), ret.end());
+
+	return ret;
+}
+
+std::vector<ObjectInstanceID> CGTeleport::getAllExits(bool excludeCurrent) const
+{
+	std::vector<ObjectInstanceID> ret = cb->getTeleportChannelExits(channel);
+	if(excludeCurrent)
+		ret.erase(std::remove(ret.begin(), ret.end(), id), ret.end());
+
+	return ret;
+}
+
+ObjectInstanceID CGTeleport::getRandomExit(const CGHeroInstance * h) const
+{
+	auto passableExits = getPassableExits(cb->gameState(), h, getAllExits(true));
+	if(passableExits.size())
+		return *RandomGeneratorUtil::nextItem(passableExits, cb->gameState()->getRandomGenerator());
+
+	return ObjectInstanceID();
+}
+
+bool CGTeleport::isTeleport(const CGObjectInstance * obj)
+{
+	auto teleportObj = dynamic_cast<const CGTeleport *>(obj);
+	if(teleportObj)
+		return true;
+	else
+		return false;
+}
+
+bool CGTeleport::isConnected(const CGTeleport * src, const CGTeleport * dst)
+{
+	if(src && dst && src->isChannelExit(dst->id) && src != dst)
+		return true;
+	else
+		return false;
+}
+
+bool CGTeleport::isConnected(const CGObjectInstance * src, const CGObjectInstance * dst)
+{
+	auto srcObj = dynamic_cast<const CGTeleport *>(src);
+	auto dstObj = dynamic_cast<const CGTeleport *>(dst);
+	return isConnected(srcObj, dstObj);
+}
+
+bool CGTeleport::isExitPassable(CGameState * gs, const CGHeroInstance * h, const CGObjectInstance * obj)
+{
+	auto objTopVisObj = gs->map->getTile(obj->visitablePos()).topVisitableObj();
+	if(objTopVisObj->ID == Obj::HERO)
 	{
-		if(vstd::contains(objs,Obj::MONOLITH_ONE_WAY_EXIT) && vstd::contains(objs[Obj::MONOLITH_ONE_WAY_EXIT],subID) && objs[Obj::MONOLITH_ONE_WAY_EXIT][subID].size())
-		{
-			destinationid = *RandomGeneratorUtil::nextItem(objs[Obj::MONOLITH_ONE_WAY_EXIT][subID], cb->gameState()->getRandomGenerator());
-		}
-		else
+		if(h->id == objTopVisObj->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))
 		{
-			logGlobal->warnStream() << "Cannot find corresponding exit monolith for "<< id;
+			// Exchange between heroes only possible via subterranean gates
+			if(!dynamic_cast<const CGSubterraneanGate *>(obj))
+				return false;
 		}
-		break;
 	}
-	case Obj::MONOLITH_TWO_WAY://two way monolith - pick any other one
-	case Obj::WHIRLPOOL: //Whirlpool
-		if(vstd::contains(objs,ID) && vstd::contains(objs[ID],subID) && objs[ID][subID].size()>1)
-		{
-			//choose another exit
-			do
-			{
-				destinationid = *RandomGeneratorUtil::nextItem(objs[ID][subID], cb->gameState()->getRandomGenerator());
-			} while(destinationid == id);
+	return true;
+}
 
-			if (ID == Obj::WHIRLPOOL)
-			{
-				if (!h->hasBonusOfType(Bonus::WHIRLPOOL_PROTECTION))
-				{
-					if (h->Slots().size() > 1 || h->Slots().begin()->second->count > 1)
-					{ //we can't remove last unit
-						SlotID targetstack = h->Slots().begin()->first; //slot numbers may vary
-						for(auto i = h->Slots().rbegin(); i != h->Slots().rend(); i++)
-						{
-							if (h->getPower(targetstack) > h->getPower(i->first))
-							{
-								targetstack = (i->first);
-							}
-						}
-
-						TQuantity countToTake = h->getStackCount(targetstack) * 0.5;
-						vstd::amax(countToTake, 1);
-
-
-						InfoWindow iw;
-						iw.player = h->tempOwner;
-						iw.text.addTxt (MetaString::ADVOB_TXT, 168);
-						iw.components.push_back (Component(CStackBasicDescriptor(h->getCreature(targetstack), countToTake)));
-						cb->showInfoDialog(&iw);
-						cb->changeStackCount(StackLocation(h, targetstack), -countToTake);
-					}
-				}
-			}
+std::vector<ObjectInstanceID> CGTeleport::getPassableExits(CGameState * gs, const CGHeroInstance * h, std::vector<ObjectInstanceID> exits)
+{
+	vstd::erase_if(exits, [&](ObjectInstanceID exit) -> bool
+	{
+		return !isExitPassable(gs, h, gs->getObj(exit));
+	});
+	return exits;
+}
+
+void CGTeleport::addToChannel(std::map<TeleportChannelID, shared_ptr<TeleportChannel> > &channelsList, const CGTeleport * obj)
+{
+	shared_ptr<TeleportChannel> tc;
+	if(channelsList.find(obj->channel) == channelsList.end())
+	{
+		tc = make_shared<TeleportChannel>();
+		channelsList.insert(std::make_pair(obj->channel, tc));
+	}
+	else
+		tc = channelsList[obj->channel];
+
+	if(obj->isEntrance() && !vstd::contains(tc->entrances, obj->id))
+		tc->entrances.push_back(obj->id);
+
+	if(obj->isExit() && !vstd::contains(tc->exits, obj->id))
+		tc->exits.push_back(obj->id);
+
+	if(tc->entrances.size() && tc->exits.size()
+		&& (tc->entrances.size() != 1 || tc->entrances != tc->exits))
+	{
+		tc->passability = TeleportChannel::PASSABLE;
+	}
+}
+
+TeleportChannelID CGMonolith::findMeChannel(std::vector<Obj> IDs, int SubID) const
+{
+	for(auto obj : cb->gameState()->map->objects)
+	{
+		auto teleportObj = dynamic_cast<const CGTeleport *>(cb->getObj(obj->id));
+		if(teleportObj && vstd::contains(IDs, teleportObj->ID) && teleportObj->subID == SubID)
+			return teleportObj->channel;
+	}
+	return TeleportChannelID();
+}
+
+void CGMonolith::onHeroVisit( const CGHeroInstance * h ) const
+{
+	TeleportDialog td(h, channel);
+	if(isEntrance())
+	{
+		if(ETeleportChannelType::BIDIRECTIONAL == cb->getTeleportChannelType(channel)
+			&& cb->getTeleportChannelExits(channel).size() > 1)
+		{
+			td.exits = cb->getTeleportChannelExits(channel);
 		}
 		else
-			logGlobal->warnStream() << "Cannot find corresponding exit monolith for "<< id;
-		break;
-	case Obj::SUBTERRANEAN_GATE: //find nearest subterranean gate on the other level
+			td.exits.push_back(getRandomExit(h));
+
+		if(ETeleportChannelType::IMPASSABLE == cb->getTeleportChannelType(channel))
 		{
-			destinationid = getMatchingGate(id);
-			if(destinationid == ObjectInstanceID()) //no exit
-			{
-				showInfoDialog(h,153,0);//Just inside the entrance you find a large pile of rubble blocking the tunnel. You leave discouraged.
-			}
-			break;
+			logGlobal->warnStream() << "Cannot find corresponding exit monolith for "<< id << " (obj at " << pos << ") :(";
+			td.impassable = true;
 		}
+		else if(getRandomExit(h) == ObjectInstanceID())
+			logGlobal->warnStream() << "All exits blocked for monolith "<< id << " (obj at " << pos << ") :(";
 	}
-	if(destinationid == ObjectInstanceID())
+
+	cb->showTeleportDialog(&td);
+}
+
+void CGMonolith::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector<ObjectInstanceID> exits) const
+{
+	ObjectInstanceID objId = ObjectInstanceID(answer);
+	auto realExits = getAllExits(true);
+	if(!isEntrance() // Do nothing if hero visited exit only object
+		|| (!exits.size() && !realExits.size()) // Do nothing if there no exits on this channel
+		|| (!exits.size() && ObjectInstanceID() == getRandomExit(hero))) // Do nothing if all exits are blocked by friendly hero and it's not subterranean gate
 	{
-		logGlobal->warnStream() << "Cannot find exit... (obj at " << pos << ") :( ";
 		return;
 	}
-	if (ID == Obj::WHIRLPOOL)
-	{
-		std::set<int3> tiles = cb->getObj(destinationid)->getBlockedPos();
-		auto & tile = *RandomGeneratorUtil::nextItem(tiles, cb->gameState()->getRandomGenerator());
-		cb->moveHero(h->id, tile + int3(1,0,0), true);
-	}
+	else if(objId == ObjectInstanceID())
+		objId = getRandomExit(hero);
 	else
-		cb->moveHero (h->id,CGHeroInstance::convertPosition(cb->getObj(destinationid)->pos,true) - getVisitableOffset(), true);
+		assert(vstd::contains(exits, objId)); // Likely cheating attempt: not random teleporter choosen, but it's not from provided list
+
+	auto obj = cb->getObj(objId);
+	if(obj)
+		cb->moveHero(hero->id,CGHeroInstance::convertPosition(obj->pos,true) - getVisitableOffset(), true);
 }
 
-void CGTeleport::initObj()
+void CGMonolith::initObj()
 {
-	int si = subID;
-	switch (ID)
+	std::vector<Obj> IDs;
+	IDs.push_back(ID);
+	switch(ID)
 	{
-	case Obj::SUBTERRANEAN_GATE://ignore subterranean gates subid
-	case Obj::WHIRLPOOL:
-		{
-			si = 0;
+		case Obj::MONOLITH_ONE_WAY_ENTRANCE:
+			type = ENTRANCE;
+			IDs.push_back(Obj::MONOLITH_ONE_WAY_EXIT);
+			break;
+		case Obj::MONOLITH_ONE_WAY_EXIT:
+			type = EXIT;
+			IDs.push_back(Obj::MONOLITH_ONE_WAY_ENTRANCE);
+			break;
+		case Obj::MONOLITH_TWO_WAY:
+		default:
+			type = BOTH;
 			break;
-		}
-	default:
-		break;
 	}
-	objs[ID][si].push_back(id);
+
+	channel = findMeChannel(IDs, subID);
+	if(channel == TeleportChannelID())
+		channel = TeleportChannelID(cb->gameState()->map->teleportChannels.size());
+
+	addToChannel(cb->gameState()->map->teleportChannels, this);
 }
 
-void CGTeleport::postInit() //matches subterranean gates into pairs
+void CGSubterraneanGate::onHeroVisit( const CGHeroInstance * h ) const
+{
+	TeleportDialog td(h, channel);
+	if(ETeleportChannelType::IMPASSABLE == cb->getTeleportChannelType(channel)) //no exit
+	{
+		showInfoDialog(h,153,0);//Just inside the entrance you find a large pile of rubble blocking the tunnel. You leave discouraged.
+		logGlobal->debugStream() << "Cannot find exit subterranean gate for "<< id << " (obj at " << pos << ") :(";
+		td.impassable = true;
+	}
+	else
+		td.exits.push_back(getRandomExit(h));
+
+	cb->showTeleportDialog(&td);
+}
+
+void CGSubterraneanGate::initObj()
+{
+	type = BOTH;
+}
+
+void CGSubterraneanGate::postInit( CGameState * gs ) //matches subterranean gates into pairs
 {
 	//split on underground and surface gates
-	std::vector<const CGObjectInstance *> gatesSplit[2]; //surface and underground gates
-	for(auto & elem : objs[Obj::SUBTERRANEAN_GATE][0])
+	std::vector<CGSubterraneanGate *> gatesSplit[2]; //surface and underground gates
+	for(auto & obj : cb->gameState()->map->objects)
 	{
-		const CGObjectInstance *hlp = cb->getObj(elem);
-		gatesSplit[hlp->pos.z].push_back(hlp);
+		auto hlp = dynamic_cast<CGSubterraneanGate *>(gs->getObjInstance(obj->id));
+		if(hlp)
+			gatesSplit[hlp->pos.z].push_back(hlp);
 	}
 
 	//sort by position
-	std::sort(gatesSplit[0].begin(), gatesSplit[0].end(), [](const CGObjectInstance * a, const CGObjectInstance * b)
+	std::sort(gatesSplit[0].begin(), gatesSplit[0].end(), [](CGSubterraneanGate * a, CGSubterraneanGate * b)
 	{
 		return a->pos < b->pos;
 	});
 
 	for(size_t i = 0; i < gatesSplit[0].size(); i++)
 	{
-		const CGObjectInstance *cur = gatesSplit[0][i];
+		CGSubterraneanGate * objCurrent = gatesSplit[0][i];
 
 		//find nearest underground exit
 		std::pair<int, si32> best(-1, std::numeric_limits<si32>::max()); //pair<pos_in_vector, distance^2>
 		for(int j = 0; j < gatesSplit[1].size(); j++)
 		{
-			const CGObjectInstance *checked = gatesSplit[1][j];
+			CGSubterraneanGate *checked = gatesSplit[1][j];
 			if(!checked)
 				continue;
-			si32 hlp = checked->pos.dist2dSQ(cur->pos);
+			si32 hlp = checked->pos.dist2dSQ(objCurrent->pos);
 			if(hlp < best.second)
 			{
 				best.first = j;
@@ -879,28 +1014,86 @@ void CGTeleport::postInit() //matches subterranean gates into pairs
 			}
 		}
 
+		if(objCurrent->channel == TeleportChannelID())
+		{ // if object not linked to channel then create new channel
+			objCurrent->channel = TeleportChannelID(gs->map->teleportChannels.size());
+			addToChannel(cb->gameState()->map->teleportChannels, objCurrent);
+		}
+
 		if(best.first >= 0) //found pair
 		{
-			gates.push_back(std::make_pair(cur->id, gatesSplit[1][best.first]->id));
-			gatesSplit[1][best.first] = nullptr;
+			gatesSplit[1][best.first]->channel = objCurrent->channel;
+			addToChannel(cb->gameState()->map->teleportChannels, gatesSplit[1][best.first]);
 		}
-		else
-			gates.push_back(std::make_pair(cur->id, ObjectInstanceID()));
 	}
-	objs.erase(Obj::SUBTERRANEAN_GATE);
 }
 
-ObjectInstanceID CGTeleport::getMatchingGate(ObjectInstanceID id)
+void CGWhirlpool::onHeroVisit( const CGHeroInstance * h ) const
 {
-	for(auto & gate : gates)
+	TeleportDialog td(h, channel);
+	if(ETeleportChannelType::IMPASSABLE == cb->getTeleportChannelType(channel))
 	{
-		if(gate.first == id)
-			return gate.second;
-		if(gate.second == id)
-			return gate.first;
+		logGlobal->warnStream() << "Cannot find exit whirlpool for "<< id << " (obj at " << pos << ") :(";
+		td.impassable = true;
 	}
+	else if(getRandomExit(h) == ObjectInstanceID())
+		logGlobal->warnStream() << "All exits are blocked for whirlpool "<< id << " (obj at " << pos << ") :(";
 
-	return ObjectInstanceID();
+	if(!isProtected(h))
+	{
+		SlotID targetstack = h->Slots().begin()->first; //slot numbers may vary
+		for(auto i = h->Slots().rbegin(); i != h->Slots().rend(); i++)
+		{
+			if(h->getPower(targetstack) > h->getPower(i->first))
+				targetstack = (i->first);
+		}
+
+		TQuantity countToTake = h->getStackCount(targetstack) * 0.5;
+		vstd::amax(countToTake, 1);
+
+		InfoWindow iw;
+		iw.player = h->tempOwner;
+		iw.text.addTxt(MetaString::ADVOB_TXT, 168);
+		iw.components.push_back(Component(CStackBasicDescriptor(h->getCreature(targetstack), countToTake)));
+		cb->showInfoDialog(&iw);
+		cb->changeStackCount(StackLocation(h, targetstack), -countToTake);
+	}
+	else
+		 td.exits = getAllExits(true);
+
+	cb->showTeleportDialog(&td);
+}
+
+void CGWhirlpool::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector<ObjectInstanceID> exits) const
+{
+	ObjectInstanceID objId = ObjectInstanceID(answer);
+	auto realExits = getAllExits();
+	if(!exits.size() && !realExits.size())
+		return;
+	else if(objId == ObjectInstanceID())
+		objId = getRandomExit(hero);
+	else
+		assert(vstd::contains(exits, objId)); // Likely cheating attempt: not random teleporter choosen, but it's not from provided list
+
+	auto obj = cb->getObj(objId);
+	if(obj)
+	{
+		std::set<int3> tiles = obj->getBlockedPos();
+		auto & tile = *RandomGeneratorUtil::nextItem(tiles, cb->gameState()->getRandomGenerator());
+		cb->moveHero(hero->id, tile + int3(1,0,0), true);
+
+		cb->moveHero(hero->id,CGHeroInstance::convertPosition(obj->pos,true) - getVisitableOffset(), true);
+	}
+}
+
+bool CGWhirlpool::isProtected( const CGHeroInstance * h )
+{
+	if(h->hasBonusOfType(Bonus::WHIRLPOOL_PROTECTION)
+		|| (h->Slots().size() == 1 && h->Slots().begin()->second->count == 1)) //we can't remove last unit
+	{
+		return true;
+	}
+	return false;
 }
 
 void CGArtifact::initObj()

+ 63 - 6
lib/mapObjects/MiscObjects.h

@@ -263,19 +263,76 @@ struct DLL_LINKAGE TeleportChannel
 	}
 };
 
-class DLL_LINKAGE CGTeleport : public CGObjectInstance //teleports and subterranean gates
+class DLL_LINKAGE CGTeleport : public CGObjectInstance
 {
 public:
-	static std::map<Obj, std::map<int, std::vector<ObjectInstanceID> > > objs; //teleports: map[ID][subID] => vector of ids
-	static std::vector<std::pair<ObjectInstanceID, ObjectInstanceID> > gates; //subterranean gates: pairs of ids
+	enum EType {UNKNOWN, ENTRANCE, EXIT, BOTH};
+
+	EType type;
+	TeleportChannelID channel;
+
+	CGTeleport();
+	bool isEntrance() const;
+	bool isExit() const;
+	bool isChannelEntrance(ObjectInstanceID id) const;
+	bool isChannelExit(ObjectInstanceID id) const;
+	std::vector<ObjectInstanceID> getAllEntrances(bool excludeCurrent = false) const;
+	std::vector<ObjectInstanceID> getAllExits(bool excludeCurrent = false) const;
+	ObjectInstanceID getRandomExit(const CGHeroInstance * h) const;
+
+	virtual void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector<ObjectInstanceID> exits) const = 0;
+
+	static bool isTeleport(const CGObjectInstance * dst);
+	static bool isConnected(const CGTeleport * src, const CGTeleport * dst);
+	static bool isConnected(const CGObjectInstance * src, const CGObjectInstance * dst);
+	static bool isExitPassable(CGameState * gs, const CGHeroInstance * h, const CGObjectInstance * obj);
+	static std::vector<ObjectInstanceID> getPassableExits(CGameState * gs, const CGHeroInstance * h, std::vector<ObjectInstanceID> exits);
+	static void addToChannel(std::map<TeleportChannelID, shared_ptr<TeleportChannel> > &channelsList, const CGTeleport * obj);
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & type & channel & static_cast<CGObjectInstance&>(*this);
+	}
+};
+
+class DLL_LINKAGE CGMonolith : public CGTeleport
+{
+	TeleportChannelID findMeChannel(std::vector<Obj> IDs, int SubID) const;
+
+public:
 	void onHeroVisit(const CGHeroInstance * h) const override;
+	void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector<ObjectInstanceID> exits) const override;
 	void initObj() override;
-	static void postInit();
-	static ObjectInstanceID getMatchingGate(ObjectInstanceID id); //receives id of one subterranean gate and returns id of the paired one, -1 if none
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & static_cast<CGObjectInstance&>(*this);
+		h & static_cast<CGTeleport&>(*this);
+	}
+};
+
+class DLL_LINKAGE CGSubterraneanGate : public CGMonolith
+{
+public:
+	void onHeroVisit(const CGHeroInstance * h) const override;
+	void initObj() override;
+	static void postInit(CGameState * gs);
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CGMonolith&>(*this);
+	}
+};
+
+class DLL_LINKAGE CGWhirlpool : public CGMonolith
+{
+public:
+	void onHeroVisit(const CGHeroInstance * h) const override;
+	void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector<ObjectInstanceID> exits) const override;
+	static bool isProtected( const CGHeroInstance * h );
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CGMonolith&>(*this);
 	}
 };
 

+ 2 - 3
lib/mapping/CMap.h

@@ -432,6 +432,7 @@ public:
 
 	//Helper lists
 	std::vector< ConstTransitivePtr<CGHeroInstance> > heroesOnMap;
+	std::map<TeleportChannelID, shared_ptr<TeleportChannel> > teleportChannels;
 
 	/// associative list to identify which hero/creature id belongs to which object id(index for objects)
 	std::map<si32, ObjectInstanceID> questIdentifierToId;
@@ -499,11 +500,9 @@ public:
 		}
 
 		h & objects;
-		h & heroesOnMap & towns & artInstances;
+		h & heroesOnMap & teleportChannels & towns & artInstances;
 
 		// static members
-		h & CGTeleport::objs;
-		h & CGTeleport::gates;
 		h & CGKeys::playerKeyMap;
 		h & CGMagi::eyelist;
 		h & CGObelisk::obeliskCount & CGObelisk::visited;

+ 6 - 1
lib/registerTypes/RegisterTypes.h

@@ -36,6 +36,9 @@ void registerTypesMapObjects1(Serializer &s)
 
 	// Non-armed objects
 	s.template registerType<CGObjectInstance, CGTeleport>();
+		s.template registerType<CGTeleport, CGMonolith>();
+			s.template registerType<CGMonolith, CGSubterraneanGate>();
+			s.template registerType<CGMonolith, CGWhirlpool>();
 	s.template registerType<CGObjectInstance, CGSignBottle>();
 	s.template registerType<CGObjectInstance, CGScholar>();
 	s.template registerType<CGObjectInstance, CGMagicWell>();
@@ -121,7 +124,9 @@ void registerTypesMapObjectTypes(Serializer &s)
 	REGISTER_GENERIC_HANDLER(CGShrine);
 	REGISTER_GENERIC_HANDLER(CGSignBottle);
 	REGISTER_GENERIC_HANDLER(CGSirens);
-	REGISTER_GENERIC_HANDLER(CGTeleport);
+	REGISTER_GENERIC_HANDLER(CGMonolith);
+	REGISTER_GENERIC_HANDLER(CGSubterraneanGate);
+	REGISTER_GENERIC_HANDLER(CGWhirlpool);
 	REGISTER_GENERIC_HANDLER(CGTownInstance);
 	REGISTER_GENERIC_HANDLER(CGUniversity);
 	REGISTER_GENERIC_HANDLER(CGVisitableOPH);

+ 4 - 4
lib/rmg/CMapGenerator.cpp

@@ -381,11 +381,11 @@ void CMapGenerator::createConnections()
 
 						if (withinZone)
 						{
-							auto gate1 = new CGTeleport;
+							auto gate1 = new CGSubterraneanGate;
 							gate1->ID = Obj::SUBTERRANEAN_GATE;
 							gate1->subID = 0;
 							zoneA->placeAndGuardObject(this, gate1, tile, connection.getGuardStrength());
-							auto gate2 = new CGTeleport(*gate1);
+							auto gate2 = new CGSubterraneanGate(*gate1);
 							zoneB->placeAndGuardObject(this, gate2, otherTile, connection.getGuardStrength());
 
 							stop = true; //we are done, go to next connection
@@ -398,11 +398,11 @@ void CMapGenerator::createConnections()
 		}
 		if (!guardPos.valid())
 		{
-			auto teleport1 = new CGTeleport;
+			auto teleport1 = new CGMonolith;
 			teleport1->ID = Obj::MONOLITH_TWO_WAY;
 			teleport1->subID = getNextMonlithIndex();
 
-			auto teleport2 = new CGTeleport(*teleport1);
+			auto teleport2 = new CGMonolith(*teleport1);
 
 			zoneA->addRequiredObject (teleport1, connection.getGuardStrength());
 			zoneB->addRequiredObject (teleport2, connection.getGuardStrength());