|
@@ -95,6 +95,7 @@ VCAI::VCAI(void)
|
|
|
{
|
|
|
LOG_TRACE(logAi);
|
|
|
makingTurn = nullptr;
|
|
|
+ destinationTeleport = ObjectInstanceID();
|
|
|
}
|
|
|
|
|
|
VCAI::~VCAI(void)
|
|
@@ -122,11 +123,19 @@ void VCAI::heroMoved(const TryMoveHero & details)
|
|
|
const CGObjectInstance *o1 = frontOrNull(cb->getVisitableObjs(from)),
|
|
|
*o2 = frontOrNull(cb->getVisitableObjs(to));
|
|
|
|
|
|
- if(o1 && o2 && o1->ID == Obj::SUBTERRANEAN_GATE && o2->ID == Obj::SUBTERRANEAN_GATE)
|
|
|
+ auto t1 = dynamic_cast<const CGTeleport *>(o1);
|
|
|
+ auto t2 = dynamic_cast<const CGTeleport *>(o2);
|
|
|
+ if(t1 && t2)
|
|
|
{
|
|
|
- knownSubterraneanGates[o1] = o2;
|
|
|
- knownSubterraneanGates[o2] = o1;
|
|
|
- logAi->debugStream() << boost::format("Found a pair of subterranean gates between %s and %s!") % from % to;
|
|
|
+ if(ETeleportChannelType::BIDIRECTIONAL == cb->getTeleportChannelType(t1->channel))
|
|
|
+ {
|
|
|
+ if(o1->ID == Obj::SUBTERRANEAN_GATE)
|
|
|
+ {
|
|
|
+ knownSubterraneanGates[o1] = o2;
|
|
|
+ knownSubterraneanGates[o2] = o1;
|
|
|
+ logAi->debugStream() << boost::format("Found a pair of subterranean gates between %s and %s!") % from % to;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -590,6 +599,37 @@ void VCAI::showBlockingDialog(const std::string &text, const std::vector<Compone
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+void VCAI::showTeleportDialog(TeleportChannelID channel, std::vector<ObjectInstanceID> exits, bool impassable, QueryID askID)
|
|
|
+{
|
|
|
+ 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;
|
|
|
+ if(impassable)
|
|
|
+ knownTeleportChannels[channel]->passability = TeleportChannel::IMPASSABLE;
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if(destinationTeleport != ObjectInstanceID() && vstd::contains(exits, destinationTeleport))
|
|
|
+ choosenExit = destinationTeleport;
|
|
|
+
|
|
|
+ if(!status.channelProbing())
|
|
|
+ {
|
|
|
+ vstd::erase_if(exits, [&](ObjectInstanceID id) -> bool
|
|
|
+ {
|
|
|
+ return vstd::contains(visitableObjs, cb->getObj(id)) || id == choosenExit;
|
|
|
+ });
|
|
|
+ teleportChannelProbingList = exits;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ requestActionASAP([=]()
|
|
|
+ {
|
|
|
+ answerQuery(askID, choosenExit.getNum());
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
void VCAI::showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID)
|
|
|
{
|
|
|
LOG_TRACE_PARAMS(logAi, "removableUnits '%i', queryID '%i'", removableUnits % queryID);
|
|
@@ -1649,6 +1689,18 @@ bool VCAI::isAccessibleForHero(const int3 & pos, HeroPtr h, bool includeAllies /
|
|
|
|
|
|
bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
|
|
|
{
|
|
|
+ auto afterMovementCheck = [&]() -> void
|
|
|
+ {
|
|
|
+ waitTillFree(); //movement may cause battle or blocking dialog
|
|
|
+ if(!h)
|
|
|
+ {
|
|
|
+ lostHero(h);
|
|
|
+ if (status.channelProbing()) // if hero lost during channel probing we need to switch this mode off
|
|
|
+ status.setChannelProbing(false);
|
|
|
+ throw std::runtime_error("Hero was lost!");
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
logAi->debugStream() << boost::format("Moving hero %s to tile %s") % h->name % dst;
|
|
|
int3 startHpos = h->visitablePos();
|
|
|
bool ret = false;
|
|
@@ -1657,12 +1709,8 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
|
|
|
//FIXME: this assertion fails also if AI moves onto defeated guarded object
|
|
|
assert(cb->getVisitableObjs(dst).size() > 1); //there's no point in revisiting tile where there is no visitable object
|
|
|
cb->moveHero(*h, CGHeroInstance::convertPosition(dst, true));
|
|
|
- waitTillFree(); //movement may cause battle or blocking dialog
|
|
|
- if(!h) // TODO is it feasible to hero get killed there if game work properly?
|
|
|
- { // not sure if AI can currently reconsider to attack bank while staying on it. Check issue 2084 on mantis for more information.
|
|
|
- lostHero(h);
|
|
|
- throw std::runtime_error("Hero was lost!");
|
|
|
- }
|
|
|
+ afterMovementCheck();// TODO: is it feasible to hero get killed there if game work properly?
|
|
|
+ // not sure if AI can currently reconsider to attack bank while staying on it. Check issue 2084 on mantis for more information.
|
|
|
ret = true;
|
|
|
}
|
|
|
else
|
|
@@ -1675,9 +1723,54 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
|
|
|
throw goalFulfilledException (sptr(Goals::VisitTile(dst).sethero(h)));
|
|
|
}
|
|
|
|
|
|
+ auto getObj = [&](int3 coord, bool ignoreHero = false)
|
|
|
+ {
|
|
|
+ return cb->getTile(coord,false)->topVisitableObj(ignoreHero);
|
|
|
+ };
|
|
|
+
|
|
|
+ auto doMovement = [&](int3 dst, bool transit = false)
|
|
|
+ {
|
|
|
+ cb->moveHero(*h, CGHeroInstance::convertPosition(dst, true), transit);
|
|
|
+ };
|
|
|
+
|
|
|
+ auto doTeleportMovement = [&](int3 dst, ObjectInstanceID exitId)
|
|
|
+ {
|
|
|
+ destinationTeleport = exitId;
|
|
|
+ cb->moveHero(*h, CGHeroInstance::convertPosition(dst, true));
|
|
|
+ destinationTeleport = ObjectInstanceID();
|
|
|
+ afterMovementCheck();
|
|
|
+ };
|
|
|
+
|
|
|
+ auto doChannelProbing = [&]() -> void
|
|
|
+ {
|
|
|
+ auto currentExit = getObj(CGHeroInstance::convertPosition(h->pos,false));
|
|
|
+ assert(currentExit);
|
|
|
+
|
|
|
+ status.setChannelProbing(true);
|
|
|
+ for(auto exit : teleportChannelProbingList)
|
|
|
+ doTeleportMovement(CGHeroInstance::convertPosition(h->pos,false), exit);
|
|
|
+ teleportChannelProbingList.clear();
|
|
|
+ doTeleportMovement(CGHeroInstance::convertPosition(h->pos,false), currentExit->id);
|
|
|
+ status.setChannelProbing(false);
|
|
|
+ };
|
|
|
+
|
|
|
int i=path.nodes.size()-1;
|
|
|
for(; i>0; i--)
|
|
|
{
|
|
|
+ int3 currentCoord = path.nodes[i].coord;
|
|
|
+ int3 nextCoord = path.nodes[i-1].coord;
|
|
|
+
|
|
|
+ auto currentObject = getObj(currentCoord, currentCoord == CGHeroInstance::convertPosition(h->pos,false));
|
|
|
+ auto nextObject = getObj(nextCoord);
|
|
|
+ if(CGTeleport::isConnected(currentObject, nextObject))
|
|
|
+ { //we use special login if hero standing on teleporter it's mean we need
|
|
|
+ doTeleportMovement(currentCoord, nextObject->id);
|
|
|
+ if(teleportChannelProbingList.size())
|
|
|
+ doChannelProbing();
|
|
|
+
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
//stop sending move requests if the next node can't be reached at the current turn (hero exhausted his move points)
|
|
|
if(path.nodes[i-1].turns)
|
|
|
{
|
|
@@ -1689,16 +1782,19 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
|
|
|
if(endpos == h->visitablePos())
|
|
|
continue;
|
|
|
|
|
|
- cb->moveHero(*h, CGHeroInstance::convertPosition(endpos, true));
|
|
|
- waitTillFree(); //movement may cause battle or blocking dialog
|
|
|
- boost::this_thread::interruption_point();
|
|
|
- if(!h) //we lost hero - remove all tasks assigned to him/her
|
|
|
- {
|
|
|
- lostHero(h);
|
|
|
- //we need to throw, otherwise hero will be assigned to sth again
|
|
|
- throw std::runtime_error("Hero was lost!");
|
|
|
+ if((i-2 >= 0) // Check there is node after next one; otherwise transit is pointless
|
|
|
+ && (CGTeleport::isConnected(nextObject, getObj(path.nodes[i-2].coord))
|
|
|
+ || CGTeleport::isTeleport(nextObject)))
|
|
|
+ { // Hero should be able to go through object if it's allow transit
|
|
|
+ doMovement(endpos, true);
|
|
|
}
|
|
|
+ else
|
|
|
+ doMovement(endpos);
|
|
|
+
|
|
|
+ afterMovementCheck();
|
|
|
|
|
|
+ if(teleportChannelProbingList.size())
|
|
|
+ doChannelProbing();
|
|
|
}
|
|
|
ret = !i;
|
|
|
}
|