diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 666b8477f..e73aef167 100644 --- a/lib/CGameState.cpp +++ b/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 } diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index f47c20a3c..acfec624d 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/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); diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 477d7978a..0b7932fd7 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -21,8 +21,6 @@ #include "../IGameCallback.h" #include "../CGameState.h" -std::map > > CGTeleport::objs; -std::vector > CGTeleport::gates; std::map > CGMagi::eyelist; ui8 CGObelisk::obeliskCount; //how many obelisks are on map std::map 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) +} + +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 CGTeleport::getAllEntrances(bool excludeCurrent) const +{ + std::vector ret = cb->getTeleportChannelEntraces(channel); + if(excludeCurrent) + ret.erase(std::remove(ret.begin(), ret.end(), id), ret.end()); + + return ret; +} + +std::vector CGTeleport::getAllExits(bool excludeCurrent) const +{ + std::vector 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(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(src); + auto dstObj = dynamic_cast(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) { - case Obj::MONOLITH_ONE_WAY_ENTRANCE: //one way - find corresponding exit monolith - { - 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 - { - logGlobal->warnStream() << "Cannot find corresponding exit monolith for "<< id; - } - 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); + if(h->id == objTopVisObj->id) // Just to be sure it's won't happen. + return false; - 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); - } - } - } - } - else - logGlobal->warnStream() << "Cannot find corresponding exit monolith for "<< id; - break; - case Obj::SUBTERRANEAN_GATE: //find nearest subterranean gate on the other level + // Check if it's friendly hero or not + if(gs->getPlayerRelations(h->tempOwner, objTopVisObj->tempOwner)) { - 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; + // Exchange between heroes only possible via subterranean gates + if(!dynamic_cast(obj)) + return false; } } - if(destinationid == ObjectInstanceID()) + return true; +} + +std::vector CGTeleport::getPassableExits(CGameState * gs, const CGHeroInstance * h, std::vector exits) +{ + vstd::erase_if(exits, [&](ObjectInstanceID exit) -> bool { - logGlobal->warnStream() << "Cannot find exit... (obj at " << pos << ") :( "; - return; - } - if (ID == Obj::WHIRLPOOL) + return !isExitPassable(gs, h, gs->getObj(exit)); + }); + return exits; +} + +void CGTeleport::addToChannel(std::map > &channelsList, const CGTeleport * obj) +{ + shared_ptr tc; + if(channelsList.find(obj->channel) == channelsList.end()) { - std::set tiles = cb->getObj(destinationid)->getBlockedPos(); - auto & tile = *RandomGeneratorUtil::nextItem(tiles, cb->gameState()->getRandomGenerator()); - cb->moveHero(h->id, tile + int3(1,0,0), true); + tc = make_shared(); + channelsList.insert(std::make_pair(obj->channel, tc)); } else - cb->moveHero (h->id,CGHeroInstance::convertPosition(cb->getObj(destinationid)->pos,true) - getVisitableOffset(), true); -} + tc = channelsList[obj->channel]; -void CGTeleport::initObj() -{ - int si = subID; - switch (ID) + 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)) { - case Obj::SUBTERRANEAN_GATE://ignore subterranean gates subid - case Obj::WHIRLPOOL: - { - si = 0; - break; - } - default: - break; + tc->passability = TeleportChannel::PASSABLE; } - objs[ID][si].push_back(id); } -void CGTeleport::postInit() //matches subterranean gates into pairs +TeleportChannelID CGMonolith::findMeChannel(std::vector IDs, int SubID) const +{ + for(auto obj : cb->gameState()->map->objects) + { + auto teleportObj = dynamic_cast(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 + td.exits.push_back(getRandomExit(h)); + + if(ETeleportChannelType::IMPASSABLE == cb->getTeleportChannelType(channel)) + { + 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 << ") :("; + } + + cb->showTeleportDialog(&td); +} + +void CGMonolith::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector 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 + { + 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) + cb->moveHero(hero->id,CGHeroInstance::convertPosition(obj->pos,true) - getVisitableOffset(), true); +} + +void CGMonolith::initObj() +{ + std::vector IDs; + IDs.push_back(ID); + switch(ID) + { + 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; + } + + channel = findMeChannel(IDs, subID); + if(channel == TeleportChannelID()) + channel = TeleportChannelID(cb->gameState()->map->teleportChannels.size()); + + addToChannel(cb->gameState()->map->teleportChannels, this); +} + +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 gatesSplit[2]; //surface and underground gates - for(auto & elem : objs[Obj::SUBTERRANEAN_GATE][0]) + std::vector 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(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 best(-1, std::numeric_limits::max()); //pair 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 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 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() diff --git a/lib/mapObjects/MiscObjects.h b/lib/mapObjects/MiscObjects.h index ebd96c8e1..390bf8a7a 100644 --- a/lib/mapObjects/MiscObjects.h +++ b/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 > > objs; //teleports: map[ID][subID] => vector of ids - static std::vector > gates; //subterranean gates: pairs of ids - void onHeroVisit(const CGHeroInstance * h) 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 + 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 getAllEntrances(bool excludeCurrent = false) const; + std::vector getAllExits(bool excludeCurrent = false) const; + ObjectInstanceID getRandomExit(const CGHeroInstance * h) const; + + virtual void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector 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 getPassableExits(CGameState * gs, const CGHeroInstance * h, std::vector exits); + static void addToChannel(std::map > &channelsList, const CGTeleport * obj); template void serialize(Handler &h, const int version) { - h & static_cast(*this); + h & type & channel & static_cast(*this); + } +}; + +class DLL_LINKAGE CGMonolith : public CGTeleport +{ + TeleportChannelID findMeChannel(std::vector IDs, int SubID) const; + +public: + void onHeroVisit(const CGHeroInstance * h) const override; + void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector exits) const override; + void initObj() override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGSubterraneanGate : public CGMonolith +{ +public: + void onHeroVisit(const CGHeroInstance * h) const override; + void initObj() override; + static void postInit(CGameState * gs); + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGWhirlpool : public CGMonolith +{ +public: + void onHeroVisit(const CGHeroInstance * h) const override; + void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector exits) const override; + static bool isProtected( const CGHeroInstance * h ); + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); } }; diff --git a/lib/mapping/CMap.h b/lib/mapping/CMap.h index d856bf748..906867152 100644 --- a/lib/mapping/CMap.h +++ b/lib/mapping/CMap.h @@ -432,6 +432,7 @@ public: //Helper lists std::vector< ConstTransitivePtr > heroesOnMap; + std::map > teleportChannels; /// associative list to identify which hero/creature id belongs to which object id(index for objects) std::map 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; diff --git a/lib/registerTypes/RegisterTypes.h b/lib/registerTypes/RegisterTypes.h index 3634d2b9c..69761172f 100644 --- a/lib/registerTypes/RegisterTypes.h +++ b/lib/registerTypes/RegisterTypes.h @@ -36,6 +36,9 @@ void registerTypesMapObjects1(Serializer &s) // Non-armed objects s.template registerType(); + s.template registerType(); + s.template registerType(); + s.template registerType(); s.template registerType(); s.template registerType(); s.template registerType(); @@ -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); diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index 13022102a..13c479e9e 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/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());