Parcourir la source

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 il y a 10 ans
Parent
commit
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());