diff --git a/AI/EmptyAI/CEmptyAI.cpp b/AI/EmptyAI/CEmptyAI.cpp index f46bc1e6e..58d8fa946 100644 --- a/AI/EmptyAI/CEmptyAI.cpp +++ b/AI/EmptyAI/CEmptyAI.cpp @@ -30,7 +30,7 @@ void CEmptyAI::showBlockingDialog(const std::string &text, const std::vectorselectionMade(0, askID); } -void CEmptyAI::showTeleportDialog(TeleportChannelID channel, std::vector exits, bool impassable, QueryID askID) +void CEmptyAI::showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) { cb->selectionMade(0, askID); } diff --git a/AI/EmptyAI/CEmptyAI.h b/AI/EmptyAI/CEmptyAI.h index eb5e43a32..262511a5d 100644 --- a/AI/EmptyAI/CEmptyAI.h +++ b/AI/EmptyAI/CEmptyAI.h @@ -15,7 +15,7 @@ public: void heroGotLevel(const CGHeroInstance *hero, PrimarySkill::PrimarySkill pskill, std::vector &skills, QueryID queryID) override; void commanderGotLevel (const CCommanderInstance * commander, std::vector skills, QueryID queryID) override; void showBlockingDialog(const std::string &text, const std::vector &components, QueryID askID, const int soundID, bool selection, bool cancel) override; - void showTeleportDialog(TeleportChannelID channel, std::vector exits, bool impassable, QueryID askID) override; + void showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override; void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) override; }; diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index d45a3e3c2..d9cf52f5c 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -98,6 +98,7 @@ VCAI::VCAI(void) LOG_TRACE(logAi); makingTurn = nullptr; destinationTeleport = ObjectInstanceID(); + destinationTeleportPos = int3(-1); } VCAI::~VCAI(void) @@ -616,33 +617,46 @@ void VCAI::showBlockingDialog(const std::string &text, const std::vector exits, bool impassable, QueryID askID) +void VCAI::showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) { - LOG_TRACE_PARAMS(logAi, "askID '%i', exits '%s'", askID % exits); +// LOG_TRACE_PARAMS(logAi, "askID '%i', exits '%s'", askID % exits); NET_EVENT_HANDLER; status.addQuery(askID, boost::str(boost::format("Teleport dialog query with %d exits") % exits.size())); - ObjectInstanceID choosenExit; + int choosenExit = -1; if(impassable) knownTeleportChannels[channel]->passability = TeleportChannel::IMPASSABLE; - else + else if(destinationTeleport != ObjectInstanceID() && destinationTeleportPos.valid()) { - if(destinationTeleport != ObjectInstanceID() && vstd::contains(exits, destinationTeleport)) - choosenExit = destinationTeleport; + auto neededExit = std::make_pair(destinationTeleport, destinationTeleportPos); + if(destinationTeleport != ObjectInstanceID() && vstd::contains(exits, neededExit)) + choosenExit = vstd::find_pos(exits, neededExit); + } - if(!status.channelProbing()) + for(auto exit : exits) + { + if(status.channelProbing() && exit.first == destinationTeleport) { - vstd::copy_if(exits, vstd::set_inserter(teleportChannelProbingList), [&](ObjectInstanceID id) -> bool + choosenExit = vstd::find_pos(exits, exit); + break; + } + else + { + // TODO: Implement checking if visiting that teleport will uncovert any FoW + // So far this is the best option to handle decision about probing + auto obj = cb->getObj(exit.first, false); + if(obj == nullptr && !vstd::contains(teleportChannelProbingList, exit.first) && + exit.first != destinationTeleport) { - return !(vstd::contains(visitableObjs, cb->getObj(id)) || id == choosenExit); - }); + teleportChannelProbingList.push_back(exit.first); + } } } requestActionASAP([=]() { - answerQuery(askID, choosenExit.getNum()); + answerQuery(askID, choosenExit); }); } @@ -1876,25 +1890,29 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) cb->moveHero(*h, CGHeroInstance::convertPosition(dst, true), transit); }; - auto doTeleportMovement = [&](int3 dst, ObjectInstanceID exitId) + auto doTeleportMovement = [&](ObjectInstanceID exitId, int3 exitPos) { destinationTeleport = exitId; - cb->moveHero(*h, CGHeroInstance::convertPosition(dst, true)); + if(exitPos.valid()) + destinationTeleportPos = CGHeroInstance::convertPosition(exitPos, true); + cb->moveHero(*h, h->pos); destinationTeleport = ObjectInstanceID(); + destinationTeleportPos = int3(-1); afterMovementCheck(); }; auto doChannelProbing = [&]() -> void { - auto currentExit = getObj(CGHeroInstance::convertPosition(h->pos,false), false); - assert(currentExit); + auto currentPos = CGHeroInstance::convertPosition(h->pos,false); + auto currentExit = getObj(currentPos, true)->id; status.setChannelProbing(true); for(auto exit : teleportChannelProbingList) - doTeleportMovement(CGHeroInstance::convertPosition(h->pos,false), exit); + doTeleportMovement(exit, int3(-1)); teleportChannelProbingList.clear(); - doTeleportMovement(CGHeroInstance::convertPosition(h->pos,false), currentExit->id); status.setChannelProbing(false); + + doTeleportMovement(currentExit, currentPos); }; int i=path.nodes.size()-1; @@ -1907,7 +1925,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) auto nextObject = getObj(nextCoord, false); if(CGTeleport::isConnected(currentObject, nextObject)) { //we use special login if hero standing on teleporter it's mean we need - doTeleportMovement(currentCoord, nextObject->id); + doTeleportMovement(nextObject->id, nextCoord); if(teleportChannelProbingList.size()) doChannelProbing(); @@ -2934,7 +2952,7 @@ void AIStatus::setMove(bool ongoing) void AIStatus::setChannelProbing(bool ongoing) { boost::unique_lock lock(mx); - ongoingHeroMovement = ongoing; + ongoingChannelProbing = ongoing; cv.notify_all(); } diff --git a/AI/VCAI/VCAI.h b/AI/VCAI/VCAI.h index bb3e5b7d5..a208d0785 100644 --- a/AI/VCAI/VCAI.h +++ b/AI/VCAI/VCAI.h @@ -132,6 +132,7 @@ public: std::map > knownTeleportChannels; std::map knownSubterraneanGates; ObjectInstanceID destinationTeleport; + int3 destinationTeleportPos; std::vector teleportChannelProbingList; //list of teleport channel exits that not visible and need to be (re-)explored //std::vector visitedThisWeek; //only OPWs std::map > townVisitsThisWeek; @@ -186,7 +187,7 @@ public: virtual void commanderGotLevel (const CCommanderInstance * commander, std::vector skills, QueryID queryID) override; //TODO virtual void showBlockingDialog(const std::string &text, const std::vector &components, QueryID askID, const int soundID, bool selection, bool cancel) override; //Show a dialog, player must take decision. If selection then he has to choose between one of given components, if cancel he is allowed to not choose. After making choice, CCallback::selectionMade should be called with number of selected component (1 - n) or 0 for cancel (if allowed) and askID. virtual void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) override; //all stacks operations between these objects become allowed, interface has to call onEnd when done - virtual void showTeleportDialog(TeleportChannelID channel, std::vector exits, bool impassable, QueryID askID) override; + virtual void showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override; virtual void saveGame(COSer & h, const int version) override; //saving virtual void loadGame(CISer & h, const int version) override; //loading virtual void finish() override; @@ -344,6 +345,14 @@ public: template void serializeInternal(Handler &h, const int version) { h & knownTeleportChannels & knownSubterraneanGates & destinationTeleport; + if(version >= 755) + { + h & destinationTeleportPos; + } + else if(!h.saving) + { + destinationTeleportPos = int3(-1); + } h & townVisitsThisWeek & lockedHeroes & reservedHeroesMap; //FIXME: cannot instantiate abstract class h & visitableObjs & alreadyVisited & reservedObjs; h & saving & status & battlename; diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index f263423f4..7eedb4431 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -98,6 +98,7 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player) { logGlobal->traceStream() << "\tHuman player interface for player " << Player << " being constructed"; destinationTeleport = ObjectInstanceID(); + destinationTeleportPos = int3(-1); observerInDuelMode = false; howManyPeople++; GH.defActionsDef = 0; @@ -1148,14 +1149,15 @@ void CPlayerInterface::showBlockingDialog( const std::string &text, const std::v } -void CPlayerInterface::showTeleportDialog(TeleportChannelID channel, std::vector exits, bool impassable, QueryID askID) +void CPlayerInterface::showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) { EVENT_HANDLER_CALLED_BY_CLIENT; - ObjectInstanceID choosenExit; - if(destinationTeleport != ObjectInstanceID() && vstd::contains(exits, destinationTeleport)) - choosenExit = destinationTeleport; + int choosenExit = -1; + auto neededExit = std::make_pair(destinationTeleport, destinationTeleportPos); + if(destinationTeleport != ObjectInstanceID() && vstd::contains(exits, neededExit)) + choosenExit = vstd::find_pos(exits, neededExit); - cb->selectionMade(choosenExit.getNum(), askID); + cb->selectionMade(choosenExit, askID); } void CPlayerInterface::tileRevealed(const std::unordered_set &pos) @@ -1415,6 +1417,7 @@ void CPlayerInterface::requestRealized( PackageApplied *pa ) && stillMoveHero.get() == DURING_MOVE) { // After teleportation via CGTeleport object is finished destinationTeleport = ObjectInstanceID(); + destinationTeleportPos = int3(-1); stillMoveHero.setn(CONTINUE_MOVE); } } @@ -2663,6 +2666,7 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path) { CCS->soundh->stopSound(sh); destinationTeleport = nextObject->id; + destinationTeleportPos = nextCoord; doMovement(h->pos, false); sh = CCS->soundh->playSound(CCS->soundh->horseSounds[currentTerrain], -1); continue; diff --git a/client/CPlayerInterface.h b/client/CPlayerInterface.h index 80891f205..1d0204d0c 100644 --- a/client/CPlayerInterface.h +++ b/client/CPlayerInterface.h @@ -89,6 +89,7 @@ class CPlayerInterface : public CGameInterface, public IUpdateable public: bool observerInDuelMode; ObjectInstanceID destinationTeleport; //contain -1 or object id if teleportation + int3 destinationTeleportPos; //minor interfaces CondSh *showingDialog; //indicates if dialog box is displayed @@ -167,7 +168,7 @@ public: void showRecruitmentDialog(const CGDwelling *dwelling, const CArmedInstance *dst, int level) override; void showShipyardDialog(const IShipyard *obj) override; //obj may be town or shipyard; void showBlockingDialog(const std::string &text, const std::vector &components, QueryID askID, int soundID, bool selection, bool cancel) override; //Show a dialog, player must take decision. If selection then he has to choose between one of given components, if cancel he is allowed to not choose. After making choice, CCallback::selectionMade should be called with number of selected component (1 - n) or 0 for cancel (if allowed) and askID. - void showTeleportDialog(TeleportChannelID channel, std::vector exits, bool impassable, QueryID askID) override; + void showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override; void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) override; void showPuzzleMap() override; void viewWorldMap() override; diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index e7000ad46..0fb897458 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -855,7 +855,7 @@ std::vector CGameInfoCallback::getVisibleTeleportObjects(std:: { vstd::erase_if(ids, [&](ObjectInstanceID id) -> bool { - auto obj = getObj(id); + auto obj = getObj(id, false); return player != PlayerColor::UNFLAGGABLE && (!obj || !isVisible(obj->pos, player)); }); return ids; diff --git a/lib/CGameInterface.h b/lib/CGameInterface.h index c35d52985..a49b5894f 100644 --- a/lib/CGameInterface.h +++ b/lib/CGameInterface.h @@ -7,6 +7,8 @@ #include "spells/ViewSpellInt.h" +#include "mapObjects/CObjectHandler.h" + /* * CGameInterface.h, part of VCMI engine * @@ -94,7 +96,7 @@ public: // all stacks operations between these objects become allowed, interface has to call onEnd when done virtual void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) = 0; - virtual void showTeleportDialog(TeleportChannelID channel, std::vector exits, bool impassable, QueryID askID) = 0; + virtual void showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) = 0; virtual void finish(){}; //if for some reason we want to end virtual void showWorldViewEx(const std::vector & objectPositions){}; diff --git a/lib/NetPacks.h b/lib/NetPacks.h index f4a2163ae..5c564fb4e 100644 --- a/lib/NetPacks.h +++ b/lib/NetPacks.h @@ -1222,7 +1222,7 @@ struct TeleportDialog : public Query//2006 const CGHeroInstance *hero; TeleportChannelID channel; - std::vector exits; + TTeleportExitsList exits; bool impassable; template void serialize(Handler &h, const int version) diff --git a/lib/mapObjects/CObjectHandler.h b/lib/mapObjects/CObjectHandler.h index adc71b3d1..b159482c8 100644 --- a/lib/mapObjects/CObjectHandler.h +++ b/lib/mapObjects/CObjectHandler.h @@ -22,6 +22,10 @@ class CGObjectInstance; struct MetaString; struct BattleResult; +// This one teleport-specific, but has to be available everywhere in callbacks and netpacks +// For now it's will be there till teleports code refactored and moved into own file +typedef std::vector> TTeleportExitsList; + class DLL_LINKAGE IObjectInterface { public: diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 025920192..a4a925c5e 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -836,7 +836,7 @@ bool CGTeleport::isTeleport(const CGObjectInstance * obj) bool CGTeleport::isConnected(const CGTeleport * src, const CGTeleport * dst) { - return src && dst && src != dst && src->isChannelExit(dst->id); + return src && dst && src->isChannelExit(dst->id); } bool CGTeleport::isConnected(const CGObjectInstance * src, const CGObjectInstance * dst) @@ -915,7 +915,13 @@ void CGMonolith::onHeroVisit( const CGHeroInstance * h ) const if(isEntrance()) { if(cb->isTeleportChannelBidirectional(channel) && 1 < cb->getTeleportChannelExits(channel).size()) - td.exits = cb->getTeleportChannelExits(channel); + { + auto exits = cb->getTeleportChannelExits(channel); + for(auto exit : exits) + { + td.exits.push_back(std::make_pair(exit, CGHeroInstance::convertPosition(cb->getObj(exit)->visitablePos(), true))); + } + } if(cb->isTeleportChannelImpassable(channel)) { @@ -931,9 +937,9 @@ void CGMonolith::onHeroVisit( const CGHeroInstance * h ) const cb->showTeleportDialog(&td); } -void CGMonolith::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector exits) const +void CGMonolith::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, TTeleportExitsList exits) const { - ObjectInstanceID objId(answer); + int3 dPos; 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 @@ -941,14 +947,12 @@ void CGMonolith::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, { return; } - else if(objId == ObjectInstanceID()) - objId = getRandomExit(hero); + else if(vstd::isValidIndex(exits, answer)) + dPos = exits[answer].second; else - assert(vstd::contains(exits, objId)); // Likely cheating attempt: not random teleporter choosen, but it's not from provided list + dPos = CGHeroInstance::convertPosition(cb->getObj(getRandomExit(hero))->visitablePos(), true); - auto obj = cb->getObj(objId); - if(obj) - cb->moveHero(hero->id,CGHeroInstance::convertPosition(obj->pos,true) - getVisitableOffset(), true); + cb->moveHero(hero->id, dPos, true); } void CGMonolith::initObj() @@ -988,7 +992,10 @@ void CGSubterraneanGate::onHeroVisit( const CGHeroInstance * h ) const td.impassable = true; } else - td.exits.push_back(getRandomExit(h)); + { + auto exit = getRandomExit(h); + td.exits.push_back(std::make_pair(exit, CGHeroInstance::convertPosition(cb->getObj(exit)->visitablePos(), true))); + } cb->showTeleportDialog(&td); } @@ -1087,29 +1094,35 @@ void CGWhirlpool::onHeroVisit( const CGHeroInstance * h ) const cb->changeStackCount(StackLocation(h, targetstack), -countToTake); } else - td.exits = getAllExits(true); + { + auto exits = getAllExits(); + for(auto exit : exits) + { + auto blockedPosList = cb->getObj(exit)->getBlockedPos(); + for(auto bPos : blockedPosList) + td.exits.push_back(std::make_pair(exit, CGHeroInstance::convertPosition(bPos, true))); + } + } cb->showTeleportDialog(&td); } -void CGWhirlpool::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector exits) const +void CGWhirlpool::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, TTeleportExitsList exits) const { - ObjectInstanceID objId(answer); + int3 dPos; auto realExits = getAllExits(); if(!exits.size() && !realExits.size()) return; - else if(objId == ObjectInstanceID()) - objId = getRandomExit(hero); + else if(vstd::isValidIndex(exits, answer)) + dPos = exits[answer].second; 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) { + auto obj = cb->getObj(getRandomExit(hero)); std::set tiles = obj->getBlockedPos(); - auto & tile = *RandomGeneratorUtil::nextItem(tiles, cb->gameState()->getRandomGenerator()); - cb->moveHero(hero->id, CGHeroInstance::convertPosition(tile, true), true); + dPos = CGHeroInstance::convertPosition(*RandomGeneratorUtil::nextItem(tiles, cb->gameState()->getRandomGenerator()), true); } + + cb->moveHero(hero->id, dPos, true); } bool CGWhirlpool::isProtected( const CGHeroInstance * h ) diff --git a/lib/mapObjects/MiscObjects.h b/lib/mapObjects/MiscObjects.h index bff8630c4..6abf94bc7 100644 --- a/lib/mapObjects/MiscObjects.h +++ b/lib/mapObjects/MiscObjects.h @@ -286,7 +286,7 @@ public: bool isEntrance() const; bool isExit() const; - virtual void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector exits) const = 0; + virtual void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, TTeleportExitsList exits) const = 0; static bool isTeleport(const CGObjectInstance * dst); static bool isConnected(const CGTeleport * src, const CGTeleport * dst); @@ -307,7 +307,7 @@ class DLL_LINKAGE CGMonolith : public CGTeleport protected: void onHeroVisit(const CGHeroInstance * h) const override; - void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector exits) const override; + void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, TTeleportExitsList exits) const override; void initObj() override; public: @@ -334,7 +334,7 @@ public: class DLL_LINKAGE CGWhirlpool : public CGMonolith { void onHeroVisit(const CGHeroInstance * h) const override; - void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector exits) const override; + void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, TTeleportExitsList exits) const override; static bool isProtected( const CGHeroInstance * h ); public: diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index ffedf5af3..85c1aec3e 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1755,7 +1755,7 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo } logGlobal->traceStream() << "Player " << asker << " wants to move hero "<< hid.getNum() << " from "<< h->pos << " to " << dst; - const int3 hmpos = dst + int3(-1,0,0); + const int3 hmpos = CGHeroInstance::convertPosition(dst, false); if(!gs->map->isInTheMap(hmpos)) {