1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Merge pull request #93 from ArseniyShestakov/feature/pathfindingTeleports

Okay let's do this!
This commit is contained in:
DjWarmonger 2015-03-10 09:04:25 +01:00
commit ec879046ca
36 changed files with 936 additions and 243 deletions

View File

@ -30,6 +30,11 @@ void CEmptyAI::showBlockingDialog(const std::string &text, const std::vector<Com
cb->selectionMade(0, askID);
}
void CEmptyAI::showTeleportDialog(TeleportChannelID channel, std::vector<ObjectInstanceID> exits, bool impassable, QueryID askID)
{
cb->selectionMade(0, askID);
}
void CEmptyAI::showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID)
{
cb->selectionMade(0, queryID);

View File

@ -15,6 +15,7 @@ public:
void heroGotLevel(const CGHeroInstance *hero, PrimarySkill::PrimarySkill pskill, std::vector<SecondarySkill> &skills, QueryID queryID) override;
void commanderGotLevel (const CCommanderInstance * commander, std::vector<ui32> skills, QueryID queryID) override;
void showBlockingDialog(const std::string &text, const std::vector<Component> &components, QueryID askID, const int soundID, bool selection, bool cancel) override;
void showTeleportDialog(TeleportChannelID channel, std::vector<ObjectInstanceID> exits, bool impassable, QueryID askID) override;
void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) override;
};

View File

@ -588,11 +588,39 @@ TGoalVec Explore::getAllPossibleSubgoals()
{
switch (obj->ID.num)
{
case Obj::REDWOOD_OBSERVATORY:
case Obj::PILLAR_OF_FIRE:
case Obj::CARTOGRAPHER:
case Obj::SUBTERRANEAN_GATE: //TODO: check ai->knownSubterraneanGates
case Obj::REDWOOD_OBSERVATORY:
case Obj::PILLAR_OF_FIRE:
case Obj::CARTOGRAPHER:
objs.push_back (obj);
break;
case Obj::MONOLITH_ONE_WAY_ENTRANCE:
case Obj::MONOLITH_TWO_WAY:
case Obj::SUBTERRANEAN_GATE:
auto tObj = dynamic_cast<const CGTeleport *>(obj);
assert(ai->knownTeleportChannels.find(tObj->channel) != ai->knownTeleportChannels.end());
if(TeleportChannel::IMPASSABLE != ai->knownTeleportChannels[tObj->channel]->passability)
objs.push_back (obj);
break;
}
}
else
{
switch (obj->ID.num)
{
case Obj::MONOLITH_TWO_WAY:
case Obj::SUBTERRANEAN_GATE:
auto tObj = dynamic_cast<const CGTeleport *>(obj);
if(TeleportChannel::IMPASSABLE == ai->knownTeleportChannels[tObj->channel]->passability)
break;
for(auto exit : ai->knownTeleportChannels[tObj->channel]->exits)
{
if(!cb->getObj(exit))
{ // Always attempt to visit two-way teleports if one of channel exits is not visible
objs.push_back(obj);
break;
}
}
break;
}
}
}

View File

@ -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(cb->isTeleportChannelBidirectional(t1->channel))
{
if(o1->ID == Obj::SUBTERRANEAN_GATE && o1->ID == o2->ID) // We need to only add subterranean gates in knownSubterraneanGates. Used for features not yet ported to use teleport channels
{
knownSubterraneanGates[o1] = o2;
knownSubterraneanGates[o2] = o1;
logAi->debugStream() << boost::format("Found a pair of subterranean gates between %s and %s!") % from % to;
}
}
}
}
}
@ -505,7 +514,7 @@ void VCAI::objectPropertyChanged(const SetObjectProperty * sop)
auto obj = myCb->getObj(sop->id, false);
if (obj)
{
visitableObjs.insert(obj);
addVisitableObj(obj);
erase_if_present(alreadyVisited, obj);
}
}
@ -550,7 +559,7 @@ void VCAI::init(shared_ptr<CCallback> CB)
if(!fh)
fh = new FuzzyHelper();
retreiveVisitableObjs(visitableObjs);
retreiveVisitableObjs();
}
void VCAI::yourTurn()
@ -597,6 +606,36 @@ 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::copy_if(exits, vstd::set_inserter(teleportChannelProbingList), [&](ObjectInstanceID id) -> bool
{
return !(vstd::contains(visitableObjs, cb->getObj(id)) || id == choosenExit);
});
}
}
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);
@ -674,7 +713,7 @@ void VCAI::makeTurn()
{
if (isWeeklyRevisitable(obj))
{
visitableObjs.insert(obj); //set doesn't need duplicate check
addVisitableObj(obj);
erase_if_present (alreadyVisited, obj);
}
}
@ -1559,14 +1598,15 @@ void VCAI::retreiveVisitableObjs(std::vector<const CGObjectInstance *> &out, boo
}
});
}
void VCAI::retreiveVisitableObjs(std::set<const CGObjectInstance *> &out, bool includeOwned /*= false*/) const
void VCAI::retreiveVisitableObjs()
{
foreach_tile_pos([&](const int3 &pos)
{
for(const CGObjectInstance *obj : myCb->getVisitableObjs(pos, false))
{
if(includeOwned || obj->tempOwner != playerID)
out.insert(obj);
if(obj->tempOwner != playerID)
addVisitableObj(obj);
}
});
}
@ -1586,6 +1626,11 @@ void VCAI::addVisitableObj(const CGObjectInstance *obj)
{
visitableObjs.insert(obj);
helperObjInfo[obj] = ObjInfo(obj);
// All teleport objects seen automatically assigned to appropriate channels
auto teleportObj = dynamic_cast<const CGTeleport *>(obj);
if(teleportObj)
CGTeleport::addToChannel(knownTeleportChannels, teleportObj);
}
const CGObjectInstance * VCAI::lookForArt(int aid) const
@ -1650,6 +1695,19 @@ 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);
teleportChannelProbingList.clear();
if (status.channelProbing()) // if hero lost during channel probing we need to switch this mode off
status.setChannelProbing(false);
throw cannotFulfillGoalException("Hero was lost!");
}
};
logAi->debugStream() << boost::format("Moving hero %s to tile %s") % h->name % dst;
int3 startHpos = h->visitablePos();
bool ret = false;
@ -1658,12 +1716,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
@ -1676,9 +1730,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)
{
@ -1690,16 +1789,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;
}
@ -2531,6 +2633,7 @@ AIStatus::AIStatus()
battle = NO_BATTLE;
havingTurn = false;
ongoingHeroMovement = false;
ongoingChannelProbing = false;
}
AIStatus::~AIStatus()
@ -2663,6 +2766,18 @@ void AIStatus::setMove(bool ongoing)
cv.notify_all();
}
void AIStatus::setChannelProbing(bool ongoing)
{
boost::unique_lock<boost::mutex> lock(mx);
ongoingHeroMovement = ongoing;
cv.notify_all();
}
bool AIStatus::channelProbing()
{
return ongoingChannelProbing;
}
SectorMap::SectorMap()
{
update();

View File

@ -41,6 +41,7 @@ class AIStatus
std::map<int, QueryID> requestToQueryID; //IDs of answer-requests sent to server => query ids (so we can match answer confirmation from server to the query)
std::vector<const CGObjectInstance*> objectsBeingVisited;
bool ongoingHeroMovement;
bool ongoingChannelProbing; // true if AI currently explore bidirectional teleport channel exits
bool havingTurn;
@ -49,6 +50,8 @@ public:
~AIStatus();
void setBattle(BattleState BS);
void setMove(bool ongoing);
void setChannelProbing(bool ongoing);
bool channelProbing();
BattleState getBattle();
void addQuery(QueryID ID, std::string description);
void removeQuery(QueryID ID);
@ -138,7 +141,10 @@ public:
friend class FuzzyHelper;
std::map<TeleportChannelID, shared_ptr<TeleportChannel> > knownTeleportChannels;
std::map<const CGObjectInstance *, const CGObjectInstance *> knownSubterraneanGates;
ObjectInstanceID destinationTeleport;
std::vector<ObjectInstanceID> teleportChannelProbingList; //list of teleport channel exits that not visible and need to be (re-)explored
//std::vector<const CGObjectInstance *> visitedThisWeek; //only OPWs
std::map<HeroPtr, std::set<const CGTownInstance *> > townVisitsThisWeek;
@ -190,6 +196,7 @@ public:
virtual void commanderGotLevel (const CCommanderInstance * commander, std::vector<ui32> skills, QueryID queryID) override; //TODO
virtual void showBlockingDialog(const std::string &text, const std::vector<Component> &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<ObjectInstanceID> 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;
@ -290,7 +297,7 @@ public:
void validateObject(ObjectIdRef obj); //checks if object is still visible and if not, removes references to it
void validateVisitableObjs();
void retreiveVisitableObjs(std::vector<const CGObjectInstance *> &out, bool includeOwned = false) const;
void retreiveVisitableObjs(std::set<const CGObjectInstance *> &out, bool includeOwned = false) const;
void retreiveVisitableObjs();
std::vector<const CGObjectInstance *> getFlaggedObjects() const;
const CGObjectInstance *lookForArt(int aid) const;
@ -343,7 +350,8 @@ public:
template <typename Handler> void serializeInternal(Handler &h, const int version)
{
h & knownSubterraneanGates & townVisitsThisWeek & lockedHeroes & reservedHeroesMap; //FIXME: cannot instantiate abstract class
h & knownTeleportChannels & knownSubterraneanGates & destinationTeleport;
h & townVisitsThisWeek & lockedHeroes & reservedHeroesMap; //FIXME: cannot instantiate abstract class
h & visitableObjs & alreadyVisited & reservedObjs;
h & saving & status & battlename;
h & heroesUnableToExplore;

View File

@ -51,3 +51,6 @@ Alexey aka Macron1Robot, <>
Alexander Shishkin aka alexvins,
* MinGW platform support, modding related programming
Arseniy Shestakov aka SXX,
* pathfinding improvements, programming

View File

@ -48,9 +48,9 @@ bool CCallback::teleportHero(const CGHeroInstance *who, const CGTownInstance *wh
return true;
}
bool CCallback::moveHero(const CGHeroInstance *h, int3 dst)
bool CCallback::moveHero(const CGHeroInstance *h, int3 dst, bool transit)
{
MoveHero pack(dst,h->id);
MoveHero pack(dst,h->id,transit);
sendRequest(&pack);
return true;
}

View File

@ -46,7 +46,7 @@ class IGameActionCallback
{
public:
//hero
virtual bool moveHero(const CGHeroInstance *h, int3 dst) =0; //dst must be free, neighbouring tile (this function can move hero only by one tile)
virtual bool moveHero(const CGHeroInstance *h, int3 dst, bool transit) =0; //dst must be free, neighbouring tile (this function can move hero only by one tile)
virtual bool dismissHero(const CGHeroInstance * hero)=0; //dismisses given hero; true - successfuly, false - not successfuly
virtual void dig(const CGObjectInstance *hero)=0;
virtual void castSpell(const CGHeroInstance *hero, SpellID spellID, const int3 &pos = int3(-1, -1, -1))=0; //cast adventure map spell
@ -119,7 +119,7 @@ public:
void unregisterAllInterfaces(); //stops delivering information about game events to player interfaces -> can be called ONLY after victory/loss
//commands
bool moveHero(const CGHeroInstance *h, int3 dst); //dst must be free, neighbouring tile (this function can move hero only by one tile)
bool moveHero(const CGHeroInstance *h, int3 dst, bool transit = false); //dst must be free, neighbouring tile (this function can move hero only by one tile)
bool teleportHero(const CGHeroInstance *who, const CGTownInstance *where);
int selectionMade(int selection, QueryID queryID);
int swapCreatures(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2);

View File

@ -1,4 +1,11 @@
0.97 -> 0.98
GENERAL:
* Pathfinder can now find way using monoliths and whirlpools (only used if hero have protection)
ADVENTURE AI:
* AI will try to use Monolith entrances for exploration
* AI will now always revisit each exit of two way monolith if exit no longer visible
ADVENTURE MAP:
* Implemented world veiw

View File

@ -664,6 +664,16 @@ namespace vstd
dest.insert(dest.end(), src.begin(), src.end());
}
template <typename T>
std::vector<T> intersection(std::vector<T> &v1, std::vector<T> &v2)
{
std::vector<T> v3;
std::sort(v1.begin(), v1.end());
std::sort(v2.begin(), v2.end());
std::set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), std::back_inserter(v3));
return v3;
}
using boost::math::round;
}
using vstd::operator-=;

View File

@ -97,6 +97,7 @@ static bool objectBlitOrderSorter(const TerrainTileObject & a, const TerrainTil
CPlayerInterface::CPlayerInterface(PlayerColor Player)
{
logGlobal->traceStream() << "\tHuman player interface for player " << Player << " being constructed";
destinationTeleport = ObjectInstanceID();
observerInDuelMode = false;
howManyPeople++;
GH.defActionsDef = 0;
@ -1141,6 +1142,16 @@ void CPlayerInterface::showBlockingDialog( const std::string &text, const std::v
}
void CPlayerInterface::showTeleportDialog(TeleportChannelID channel, std::vector<ObjectInstanceID> exits, bool impassable, QueryID askID)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
ObjectInstanceID choosenExit;
if(destinationTeleport != ObjectInstanceID() && vstd::contains(exits, destinationTeleport))
choosenExit = destinationTeleport;
cb->selectionMade(choosenExit.getNum(), askID);
}
void CPlayerInterface::tileRevealed(const std::unordered_set<int3, ShashInt3> &pos)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
@ -1389,8 +1400,17 @@ void CPlayerInterface::showArtifactAssemblyDialog (ui32 artifactID, ui32 assembl
void CPlayerInterface::requestRealized( PackageApplied *pa )
{
EVENT_HANDLER_CALLED_BY_CLIENT;
if(pa->packType == typeList.getTypeID<MoveHero>() && stillMoveHero.get() == DURING_MOVE)
if(pa->packType == typeList.getTypeID<MoveHero>() && stillMoveHero.get() == DURING_MOVE
&& destinationTeleport == ObjectInstanceID())
stillMoveHero.setn(CONTINUE_MOVE);
if(destinationTeleport != ObjectInstanceID()
&& pa->packType == typeList.getTypeID<QueryReply>()
&& stillMoveHero.get() == DURING_MOVE)
{ // After teleportation via CGTeleport object is finished
destinationTeleport = ObjectInstanceID();
stillMoveHero.setn(CONTINUE_MOVE);
}
}
void CPlayerInterface::heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID query)
@ -2631,30 +2651,47 @@ bool CPlayerInterface::capturedAllEvents()
return false;
}
void CPlayerInterface::doMoveHero(const CGHeroInstance* h, CGPath path)
void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
{
int i = 1;
auto getObj = [&](int3 coord, bool ignoreHero = false)
{
return cb->getTile(CGHeroInstance::convertPosition(coord,false))->topVisitableObj(ignoreHero);
};
boost::unique_lock<boost::mutex> un(stillMoveHero.mx);
stillMoveHero.data = CONTINUE_MOVE;
auto doMovement = [&](int3 dst, bool transit = false)
{
stillMoveHero.data = WAITING_MOVE;
cb->moveHero(h, dst, transit);
while(stillMoveHero.data != STOP_MOVE && stillMoveHero.data != CONTINUE_MOVE)
stillMoveHero.cond.wait(un);
};
{
path.convert(0);
boost::unique_lock<boost::mutex> un(stillMoveHero.mx);
stillMoveHero.data = CONTINUE_MOVE;
ETerrainType currentTerrain = ETerrainType::BORDER; // not init yet
ETerrainType newTerrain;
int sh = -1;
const TerrainTile * curTile = cb->getTile(CGHeroInstance::convertPosition(h->pos, false));
for(i=path.nodes.size()-1; i>0 && (stillMoveHero.data == CONTINUE_MOVE || curTile->blocked); i--)
for(i=path.nodes.size()-1; i>0 && (stillMoveHero.data == CONTINUE_MOVE); i--)
{
//changing z coordinate means we're moving through subterranean gate -> it's done automatically upon the visit, so we don't have to request that move here
if(path.nodes[i-1].coord.z != path.nodes[i].coord.z)
continue;
int3 currentCoord = path.nodes[i].coord;
int3 nextCoord = path.nodes[i-1].coord;
//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)
auto nextObject = getObj(nextCoord, nextCoord == h->pos);
if(CGTeleport::isConnected(getObj(currentCoord, currentCoord == h->pos), nextObject))
{
CCS->soundh->stopSound(sh);
destinationTeleport = nextObject->id;
doMovement(h->pos);
sh = CCS->soundh->playSound(CCS->soundh->horseSounds[currentTerrain], -1);
continue;
}
if(path.nodes[i-1].turns)
{ //stop sending move requests if the next node can't be reached at the current turn (hero exhausted his move points)
stillMoveHero.data = STOP_MOVE;
break;
}
@ -2662,13 +2699,12 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance* h, CGPath path)
// Start a new sound for the hero movement or let the existing one carry on.
#if 0
// TODO
if (hero is flying && sh == -1)
if(hero is flying && sh == -1)
sh = CCS->soundh->playSound(soundBase::horseFlying, -1);
#endif
{
newTerrain = cb->getTile(CGHeroInstance::convertPosition(path.nodes[i].coord, false))->terType;
if (newTerrain != currentTerrain)
newTerrain = cb->getTile(CGHeroInstance::convertPosition(currentCoord, false))->terType;
if(newTerrain != currentTerrain)
{
CCS->soundh->stopSound(sh);
sh = CCS->soundh->playSound(CCS->soundh->horseSounds[newTerrain], -1);
@ -2676,19 +2712,22 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance* h, CGPath path)
}
}
stillMoveHero.data = WAITING_MOVE;
int3 endpos(path.nodes[i-1].coord.x, path.nodes[i-1].coord.y, h->pos.z);
bool guarded = CGI->mh->map->isInTheMap(cb->getGuardingCreaturePosition(endpos - int3(1, 0, 0)));
assert(h->pos.z == nextCoord.z); // Z should change only if it's movement via teleporter and in this case this code shouldn't be executed at all
int3 endpos(nextCoord.x, nextCoord.y, h->pos.z);
logGlobal->traceStream() << "Requesting hero movement to " << endpos;
cb->moveHero(h,endpos);
while(stillMoveHero.data != STOP_MOVE && stillMoveHero.data != CONTINUE_MOVE)
stillMoveHero.cond.wait(un);
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);
logGlobal->traceStream() << "Resuming " << __FUNCTION__;
if (guarded || showingDialog->get() == true) // Abort movement if a guard was fought or there is a dialog to display (Mantis #1136)
bool guarded = cb->isInTheMap(cb->getGuardingCreaturePosition(endpos - int3(1, 0, 0)));
if(guarded || showingDialog->get() == true) // Abort movement if a guard was fought or there is a dialog to display (Mantis #1136)
break;
}
@ -2701,7 +2740,7 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance* h, CGPath path)
//todo: this should be in main thread
if (adventureInt)
if(adventureInt)
{
// (i == 0) means hero went through all the path
adventureInt->updateMoveHero(h, (i != 0));

View File

@ -89,6 +89,7 @@ class CPlayerInterface : public CGameInterface, public ILockedUpdatable
const CArmedInstance * currentSelection;
public:
bool observerInDuelMode;
ObjectInstanceID destinationTeleport; //contain -1 or object id if teleportation
//minor interfaces
CondSh<bool> *showingDialog; //indicates if dialog box is displayed
@ -168,6 +169,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<Component> &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<ObjectInstanceID> 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;

View File

@ -179,6 +179,7 @@ public:
void showBlockingDialog(BlockingDialog *iw) override {};
void showGarrisonDialog(ObjectInstanceID upobj, ObjectInstanceID hid, bool removableUnits) override {};
void showTeleportDialog(TeleportDialog *iw) override {};
void showThievesGuildWindow(PlayerColor player, ObjectInstanceID requestingObjId) override {};
void giveResource(PlayerColor player, Res::ERes which, int val) override {};
virtual void giveResources(PlayerColor player, TResources resources) override {};
@ -212,7 +213,7 @@ public:
void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank = false) override {}; //if any of armies is hero, hero will be used
void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false) override {}; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle
void setAmount(ObjectInstanceID objid, ui32 val) override {};
bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, PlayerColor asker = PlayerColor::NEUTRAL) override {return false;};
bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override {return false;};
void giveHeroBonus(GiveBonus * bonus) override {};
void setMovePoints(SetMovePoints * smp) override {};
void setManaPoints(ObjectInstanceID hid, int val) override {};

View File

@ -595,6 +595,11 @@ void ExchangeDialog::applyCl(CClient *cl)
INTERFACE_CALL_IF_PRESENT(heroes[0]->tempOwner, heroExchangeStarted, heroes[0]->id, heroes[1]->id, queryID);
}
void TeleportDialog::applyCl( CClient *cl )
{
CALL_ONLY_THAT_INTERFACE(hero->tempOwner,showTeleportDialog,channel,exits,impassable,queryID);
}
void BattleStart::applyFirstCl( CClient *cl )
{
//Cannot use the usual macro because curB is not set yet

View File

@ -153,7 +153,7 @@
"whirlpool" : {
"index" :111,
"handler" : "teleport",
"handler" : "whirlpool",
"types" : {
"object" : {
"index" : 0,
@ -164,7 +164,7 @@
},
"subterraneanGate" : {
"index" :103,
"handler" : "teleport",
"handler" : "subterraneanGate",
"types" : {
"object" : {
"index" : 0 },

View File

@ -200,7 +200,7 @@
// Subtype: paired monoliths
"monolithOneWayEntrance" : {
"index" :43,
"handler": "teleport",
"handler": "monolith",
"types" : {
"monolith1" : { "index" : 0 },
"monolith2" : { "index" : 1 },
@ -214,7 +214,7 @@
},
"monolithOneWayExit" : {
"index" :44,
"handler": "teleport",
"handler": "monolith",
"types" : {
"monolith1" : { "index" : 0 },
"monolith2" : { "index" : 1 },
@ -228,7 +228,7 @@
},
"monolithTwoWay" : {
"index" :45,
"handler": "teleport",
"handler": "monolith",
"types" : {
"monolith1" : { "index" : 0 },
"monolith2" : { "index" : 1 },

View File

@ -790,6 +790,65 @@ const CGObjectInstance * CGameInfoCallback::getObjInstance( ObjectInstanceID oid
return gs->map->objects[oid.num];
}
std::vector<ObjectInstanceID> CGameInfoCallback::getVisibleTeleportObjects(std::vector<ObjectInstanceID> ids, PlayerColor player) const
{
vstd::erase_if(ids, [&](ObjectInstanceID id) -> bool
{
auto obj = getObj(id);
return player != PlayerColor::UNFLAGGABLE && (!obj || !isVisible(obj->pos, player));
});
return ids;
}
std::vector<ObjectInstanceID> CGameInfoCallback::getTeleportChannelEntraces(TeleportChannelID id, PlayerColor player) const
{
return getVisibleTeleportObjects(gs->map->teleportChannels[id]->entrances, player);
}
std::vector<ObjectInstanceID> CGameInfoCallback::getTeleportChannelExits(TeleportChannelID id, PlayerColor player) const
{
return getVisibleTeleportObjects(gs->map->teleportChannels[id]->exits, player);
}
ETeleportChannelType CGameInfoCallback::getTeleportChannelType(TeleportChannelID id, PlayerColor player) const
{
std::vector<ObjectInstanceID> entrances = getTeleportChannelEntraces(id, player);
std::vector<ObjectInstanceID> exits = getTeleportChannelExits(id, player);
if((!entrances.size() || !exits.size()) // impassable if exits or entrances list are empty
|| (entrances.size() == 1 && entrances == exits)) // impassable if only entrance and only exit is same object. e.g bidirectional monolith
{
return ETeleportChannelType::IMPASSABLE;
}
auto intersection = vstd::intersection(entrances, exits);
if(intersection.size() == entrances.size() && intersection.size() == exits.size())
return ETeleportChannelType::BIDIRECTIONAL;
else if(!intersection.size())
return ETeleportChannelType::UNIDIRECTIONAL;
else
return ETeleportChannelType::MIXED;
}
bool CGameInfoCallback::isTeleportChannelImpassable(TeleportChannelID id, PlayerColor player) const
{
return ETeleportChannelType::IMPASSABLE == getTeleportChannelType(id, player);
}
bool CGameInfoCallback::isTeleportChannelBidirectional(TeleportChannelID id, PlayerColor player) const
{
return ETeleportChannelType::BIDIRECTIONAL == getTeleportChannelType(id, player);
}
bool CGameInfoCallback::isTeleportChannelUnidirectional(TeleportChannelID id, PlayerColor player) const
{
return ETeleportChannelType::UNIDIRECTIONAL == getTeleportChannelType(id, player);
}
bool CGameInfoCallback::isTeleportEntrancePassable(const CGTeleport * obj, PlayerColor player) const
{
return obj && obj->isEntrance() && !isTeleportChannelImpassable(obj->channel, player);
}
void IGameEventRealizer::showInfoDialog( InfoWindow *iw )
{
commitPackage(iw);

View File

@ -25,6 +25,7 @@ struct InfoAboutTown;
struct UpgradeInfo;
struct SThievesGuildInfo;
class CGDwelling;
class CGTeleport;
class CMapHeader;
struct TeamState;
struct QuestInfo;
@ -106,6 +107,16 @@ public:
const TeamState *getTeam(TeamID teamID) const;
const TeamState *getPlayerTeam(PlayerColor color) const;
EBuildingState::EBuildingState canBuildStructure(const CGTownInstance *t, BuildingID ID) const;// 0 - no more than one capitol, 1 - lack of water, 2 - forbidden, 3 - Add another level to Mage Guild, 4 - already built, 5 - cannot build, 6 - cannot afford, 7 - build, 8 - lack of requirements
//teleport
std::vector<ObjectInstanceID> getVisibleTeleportObjects(std::vector<ObjectInstanceID> ids, PlayerColor player) const;
std::vector<ObjectInstanceID> getTeleportChannelEntraces(TeleportChannelID id, PlayerColor Player = PlayerColor::UNFLAGGABLE) const;
std::vector<ObjectInstanceID> getTeleportChannelExits(TeleportChannelID id, PlayerColor Player = PlayerColor::UNFLAGGABLE) const;
ETeleportChannelType getTeleportChannelType(TeleportChannelID id, PlayerColor player = PlayerColor::UNFLAGGABLE) const;
bool isTeleportChannelImpassable(TeleportChannelID id, PlayerColor player = PlayerColor::UNFLAGGABLE) const;
bool isTeleportChannelBidirectional(TeleportChannelID id, PlayerColor player = PlayerColor::UNFLAGGABLE) const;
bool isTeleportChannelUnidirectional(TeleportChannelID id, PlayerColor player = PlayerColor::UNFLAGGABLE) const;
bool isTeleportEntrancePassable(const CGTeleport * obj, PlayerColor player) const;
};
class DLL_LINKAGE CPlayerSpecificInfoCallback : public CGameInfoCallback

View File

@ -94,6 +94,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<ObjectInstanceID> exits, bool impassable, QueryID askID) = 0;
virtual void finish(){}; //if for some reason we want to end
virtual void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions){};

View File

@ -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
}
@ -3300,9 +3300,6 @@ void CPathfinder::initializeGraph()
void CPathfinder::calculatePaths()
{
assert(hero);
assert(hero == getHero(hero->id));
bool flying = hero->hasBonusOfType(Bonus::FLYING_MOVEMENT);
int maxMovePointsLand = hero->maxMovePoints(true);
int maxMovePointsWater = hero->maxMovePoints(false);
@ -3318,7 +3315,7 @@ void CPathfinder::calculatePaths()
if(!gs->map->isInTheMap(out.hpos)/* || !gs->map->isInTheMap(dest)*/) //check input
{
logGlobal->errorStream() << "CGameState::calculatePaths: Hero outside the gs->map? How dare you...";
logGlobal->errorStream() << "CGameState::calculatePaths: Hero outside the gs->map? How dare you...";
return;
}
@ -3349,68 +3346,73 @@ void CPathfinder::calculatePaths()
turn++;
}
//add accessible neighbouring nodes to the queue
neighbours.clear();
//handling subterranean gate => it's exit is the only neighbour
bool subterraneanEntry = (ct->topVisitableId() == Obj::SUBTERRANEAN_GATE && useSubterraneanGates);
if(subterraneanEntry)
auto sObj = ct->topVisitableObj(cp->coord == CGHeroInstance::convertPosition(hero->pos, false));
auto cObj = dynamic_cast<const CGTeleport *>(sObj);
if(gs->isTeleportEntrancePassable(cObj, hero->tempOwner)
&& (addTeleportWhirlpool(dynamic_cast<const CGWhirlpool *>(cObj))
|| addTeleportTwoWay(cObj)
|| addTeleportOneWay(cObj)
|| addTeleportOneWayRandom(cObj)))
{
//try finding the exit gate
if(const CGObjectInstance *outGate = getObj(CGTeleport::getMatchingGate(ct->visitableObjects.back()->id), false))
for(auto objId : gs->getTeleportChannelExits(cObj->channel, hero->tempOwner))
{
const int3 outPos = outGate->visitablePos();
//gs->getNeighbours(*getTile(outPos), outPos, neighbours, boost::logic::indeterminate, !cp->land);
neighbours.push_back(outPos);
}
else
{
//gate with no exit (blocked) -> do nothing with this node
continue;
auto obj = getObj(objId);
if(CGTeleport::isExitPassable(gs, hero, obj))
neighbours.push_back(obj->visitablePos());
}
}
gs->getNeighbours(*ct, cp->coord, neighbours, boost::logic::indeterminate, !cp->land);
std::vector<int3> neighbour_tiles;
gs->getNeighbours(*ct, cp->coord, neighbour_tiles, boost::logic::indeterminate, !cp->land);
if(sObj)
{
for(int3 neighbour_tile: neighbour_tiles)
{
if(canMoveBetween(neighbour_tile, sObj->visitablePos()))
neighbours.push_back(neighbour_tile);
}
}
else
vstd::concatenate(neighbours, neighbour_tiles);
for(auto & neighbour : neighbours)
{
const int3 &n = neighbour; //current neighbor
dp = getNode(n);
dt = &gs->map->getTile(n);
destTopVisObjID = dt->topVisitableId();
destTopVisObjID = dt->topVisitableId();
useEmbarkCost = 0; //0 - usual movement; 1 - embark; 2 - disembark
int turnAtNextTile = turn;
const bool destIsGuardian = sourceGuardPosition == n;
if(!goodForLandSeaTransition())
continue;
if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible == CGPathNode::BLOCKED )
auto dObj = dynamic_cast<const CGTeleport*>(dt->topVisitableObj());
if(!goodForLandSeaTransition()
|| (!canMoveBetween(cp->coord, dp->coord) && !CGTeleport::isConnected(cObj, dObj))
|| dp->accessible == CGPathNode::BLOCKED)
{
continue;
}
//special case -> hero embarked a boat standing on a guarded tile -> we must allow to move away from that tile
if(cp->accessible == CGPathNode::VISITABLE && guardedSource && cp->theNodeBefore->land && ct->topVisitableId() == Obj::BOAT)
if(cp->accessible == CGPathNode::VISITABLE && guardedSource && cp->theNodeBefore->land && ct->topVisitableId() == Obj::BOAT)
guardedSource = false;
int cost = gs->getMovementCost(hero, cp->coord, dp->coord, flying, movement);
//special case -> moving from src Subterranean gate to dest gate -> it's free
if(subterraneanEntry && destTopVisObjID == Obj::SUBTERRANEAN_GATE && cp->coord.z != dp->coord.z)
if(CGTeleport::isConnected(cObj, dObj))
cost = 0;
int remains = movement - cost;
if(useEmbarkCost)
{
remains = hero->movementPointsAfterEmbark(movement, cost, useEmbarkCost - 1);
cost = movement - remains;
}
int turnAtNextTile = turn;
if(remains < 0)
{
//occurs rarely, when hero with low movepoints tries to leave the road
@ -3425,7 +3427,6 @@ void CPathfinder::calculatePaths()
|| (dp->turns >= turnAtNextTile && dp->moveRemains < remains)) //this route is faster
&& (!guardedSource || destIsGuardian)) // Can step into tile of guard
{
assert(dp != cp->theNodeBefore); //two tiles can't point to each other
dp->moveRemains = remains;
dp->turns = turnAtNextTile;
@ -3434,13 +3435,28 @@ void CPathfinder::calculatePaths()
const bool guardedDst = gs->map->guardingCreaturePositions[dp->coord.x][dp->coord.y][dp->coord.z].valid()
&& dp->accessible == CGPathNode::BLOCKVIS;
if (dp->accessible == CGPathNode::ACCESSIBLE
|| (useEmbarkCost && allowEmbarkAndDisembark)
|| destTopVisObjID == Obj::SUBTERRANEAN_GATE
|| (guardedDst && !guardedSource)) // Can step into a hostile tile once.
auto checkDestinationTile = [&]() -> bool
{
if(dp->accessible == CGPathNode::ACCESSIBLE)
return true;
if(dp->coord == CGHeroInstance::convertPosition(hero->pos, false))
return true; // This one is tricky, we can ignore fact that tile is not ACCESSIBLE in case if it's our hero block it. Though this need investigation
if(dp->accessible == CGPathNode::VISITABLE && CGTeleport::isTeleport(dt->topVisitableObj()))
return true; // For now we'll walways allos transit for teleports
if(useEmbarkCost && allowEmbarkAndDisembark)
return true;
if(gs->isTeleportEntrancePassable(dObj, hero->tempOwner))
return true; // Always add entry teleport with non-dummy channel
if(CGTeleport::isConnected(cObj, dObj))
return true; // Always add exit points of teleport
if(guardedDst && !guardedSource)
return true; // Can step into a hostile tile once
return false;
};
if(checkDestinationTile())
mq.push_back(dp);
}
}
} //neighbours loop
} //queue loop
@ -3453,7 +3469,7 @@ CGPathNode *CPathfinder::getNode(const int3 &coord)
bool CPathfinder::canMoveBetween(const int3 &a, const int3 &b) const
{
return gs->checkForVisitableDir(a, b) && gs->checkForVisitableDir(b, a);
return gs->checkForVisitableDir(a, b);
}
CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const TerrainTile *tinfo) const
@ -3532,8 +3548,16 @@ bool CPathfinder::goodForLandSeaTransition()
CPathfinder::CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero) : CGameInfoCallback(_gs, boost::optional<PlayerColor>()), out(_out), hero(_hero), FoW(getPlayerTeam(hero->tempOwner)->fogOfWarMap)
{
useSubterraneanGates = true;
assert(hero);
assert(hero == getHero(hero->id));
allowEmbarkAndDisembark = true;
allowTeleportTwoWay = true;
allowTeleportOneWay = true;
allowTeleportOneWayRandom = false;
allowTeleportWhirlpool = false;
if (CGWhirlpool::isProtected(hero))
allowTeleportWhirlpool = true;
}
CRandomGenerator & CGameState::getRandomGenerator()
@ -3541,3 +3565,35 @@ CRandomGenerator & CGameState::getRandomGenerator()
//logGlobal->traceStream() << "Fetching CGameState::rand with seed " << rand.nextInt();
return rand;
}
bool CPathfinder::addTeleportTwoWay(const CGTeleport * obj) const
{
return allowTeleportTwoWay && gs->isTeleportChannelBidirectional(obj->channel, hero->tempOwner);
}
bool CPathfinder::addTeleportOneWay(const CGTeleport * obj) const
{
if(allowTeleportOneWay && isTeleportChannelUnidirectional(obj->channel, hero->tempOwner))
{
auto passableExits = CGTeleport::getPassableExits(gs, hero, gs->getTeleportChannelExits(obj->channel, hero->tempOwner));
if(passableExits.size() == 1)
return true;
}
return false;
}
bool CPathfinder::addTeleportOneWayRandom(const CGTeleport * obj) const
{
if(allowTeleportOneWayRandom && isTeleportChannelUnidirectional(obj->channel, hero->tempOwner))
{
auto passableExits = CGTeleport::getPassableExits(gs, hero, gs->getTeleportChannelExits(obj->channel, hero->tempOwner));
if(passableExits.size() > 1)
return true;
}
return false;
}
bool CPathfinder::addTeleportWhirlpool(const CGWhirlpool * obj) const
{
return allowTeleportWhirlpool && obj && !gs->isTeleportChannelImpassable(obj->channel, hero->tempOwner);
}

View File

@ -279,8 +279,11 @@ struct DLL_EXPORT DuelParameters
class CPathfinder : private CGameInfoCallback
{
private:
bool useSubterraneanGates;
bool allowEmbarkAndDisembark;
bool allowTeleportTwoWay; // Two-way monoliths and Subterranean Gate
bool allowTeleportOneWay; // One-way monoliths with one known exit only
bool allowTeleportOneWayRandom; // One-way monoliths with more than one known exit
bool allowTeleportWhirlpool; // Force enabled if hero protected or unaffected (have one stack of one creature)
CPathsInfo &out;
const CGHeroInstance *hero;
const std::vector<std::vector<std::vector<ui8> > > &FoW;
@ -293,7 +296,7 @@ private:
CGPathNode *dp; //destination node -> it's a neighbour of cp that we consider
const TerrainTile *ct, *dt; //tile info for both nodes
ui8 useEmbarkCost; //0 - usual movement; 1 - embark; 2 - disembark
int destTopVisObjID;
Obj destTopVisObjID;
CGPathNode *getNode(const int3 &coord);
@ -303,6 +306,11 @@ private:
CGPathNode::EAccessibility evaluateAccessibility(const TerrainTile *tinfo) const;
bool canMoveBetween(const int3 &a, const int3 &b) const; //checks only for visitable objects that may make moving between tiles impossible, not other conditions (like tiles itself accessibility)
bool addTeleportTwoWay(const CGTeleport * obj) const;
bool addTeleportOneWay(const CGTeleport * obj) const;
bool addTeleportOneWayRandom(const CGTeleport * obj) const;
bool addTeleportWhirlpool(const CGWhirlpool * obj) const;
public:
CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero);
void calculatePaths(); //calculates possible paths for hero, uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists

View File

@ -252,6 +252,14 @@ class TeamID : public BaseForID<TeamID, ui8>
friend class CNonConstInfoCallback;
};
class TeleportChannelID : public BaseForID<TeleportChannelID, si32>
{
INSTID_LIKE_CLASS_COMMON(TeleportChannelID, si32)
friend class CGameInfoCallback;
friend class CNonConstInfoCallback;
};
// #ifndef INSTANTIATE_BASE_FOR_ID_HERE
// extern template std::ostream & operator << <ArtifactInstanceID>(std::ostream & os, BaseForID<ArtifactInstanceID> id);
// extern template std::ostream & operator << <ObjectInstanceID>(std::ostream & os, BaseForID<ObjectInstanceID> id);
@ -466,6 +474,14 @@ namespace ETileType
};
}
enum class ETeleportChannelType
{
IMPASSABLE,
BIDIRECTIONAL,
UNIDIRECTIONAL,
MIXED
};
class Obj
{
public:

View File

@ -15,6 +15,7 @@
struct SetMovePoints;
struct GiveBonus;
struct BlockingDialog;
struct TeleportDialog;
struct MetaString;
struct ShowInInfobox;
struct StackLocation;
@ -52,6 +53,7 @@ public:
virtual void changeSecSkill(const CGHeroInstance * hero, SecondarySkill which, int val, bool abs=false)=0;
virtual void showBlockingDialog(BlockingDialog *iw) =0;
virtual void showGarrisonDialog(ObjectInstanceID upobj, ObjectInstanceID hid, bool removableUnits) =0; //cb will be called when player closes garrison window
virtual void showTeleportDialog(TeleportDialog *iw) =0;
virtual void showThievesGuildWindow(PlayerColor player, ObjectInstanceID requestingObjId) =0;
virtual void giveResource(PlayerColor player, Res::ERes which, int val)=0;
virtual void giveResources(PlayerColor player, TResources resources)=0;
@ -83,7 +85,7 @@ public:
virtual void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank = false)=0; //if any of armies is hero, hero will be used
virtual void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false)=0; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle
virtual void setAmount(ObjectInstanceID objid, ui32 val)=0;
virtual bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, PlayerColor asker = PlayerColor::NEUTRAL)=0;
virtual bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL)=0;
virtual void giveHeroBonus(GiveBonus * bonus)=0;
virtual void setMovePoints(SetMovePoints * smp)=0;
virtual void setManaPoints(ObjectInstanceID hid, int val)=0;

View File

@ -1208,6 +1208,28 @@ struct ExchangeDialog : public Query//2005
}
};
struct TeleportDialog : public Query//2006
{
TeleportDialog() {type = 2006;}
TeleportDialog(const CGHeroInstance *Hero, TeleportChannelID Channel)
: hero(Hero), channel(Channel), impassable(false)
{
type = 2006;
}
void applyCl(CClient *cl);
const CGHeroInstance *hero;
TeleportChannelID channel;
std::vector<ObjectInstanceID> exits;
bool impassable;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & queryID & hero & channel & exits & impassable;
}
};
struct BattleInfo;
struct BattleStart : public CPackForClient//3000
{
@ -1758,14 +1780,15 @@ struct DismissHero : public CPackForServer
struct MoveHero : public CPackForServer
{
MoveHero(){};
MoveHero(const int3 &Dest, ObjectInstanceID HID) : dest(Dest), hid(HID){};
MoveHero(const int3 &Dest, ObjectInstanceID HID, bool Transit) : dest(Dest), hid(HID), transit(Transit) {};
int3 dest;
ObjectInstanceID hid;
bool transit;
bool applyGh(CGameHandler *gh);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & dest & hid;
h & dest & hid & transit;
}
};

View File

@ -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);

View File

@ -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
@ -777,115 +775,236 @@ 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
{
return vstd::contains(getAllEntrances(), id);
}
bool CGTeleport::isChannelExit(ObjectInstanceID id) const
{
return vstd::contains(getAllExits(), id);
}
std::vector<ObjectInstanceID> CGTeleport::getAllEntrances(bool excludeCurrent) const
{
auto ret = cb->getTeleportChannelEntraces(channel);
if(excludeCurrent)
vstd::erase_if_present(ret, id);
return ret;
}
std::vector<ObjectInstanceID> CGTeleport::getAllExits(bool excludeCurrent) const
{
auto ret = cb->getTeleportChannelExits(channel);
if(excludeCurrent)
vstd::erase_if_present(ret, id);
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)
{
return ((dynamic_cast<const CGTeleport *>(obj)));
}
bool CGTeleport::isConnected(const CGTeleport * src, const CGTeleport * dst)
{
return src && dst && src != dst && src->isChannelExit(dst->id);
}
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)
{
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<const CGSubterraneanGate *>(obj))
return false;
}
}
if(destinationid == ObjectInstanceID())
return true;
}
std::vector<ObjectInstanceID> CGTeleport::getPassableExits(CGameState * gs, const CGHeroInstance * h, std::vector<ObjectInstanceID> 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<TeleportChannelID, shared_ptr<TeleportChannel> > &channelsList, const CGTeleport * obj)
{
shared_ptr<TeleportChannel> tc;
if(channelsList.find(obj->channel) == channelsList.end())
{
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);
tc = make_shared<TeleportChannel>();
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];
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;
}
}
void CGTeleport::initObj()
TeleportChannelID CGMonolith::findMeChannel(std::vector<Obj> IDs, int SubID) const
{
int si = subID;
switch (ID)
for(auto obj : cb->gameState()->map->objects)
{
case Obj::SUBTERRANEAN_GATE://ignore subterranean gates subid
case Obj::WHIRLPOOL:
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(cb->isTeleportChannelBidirectional(channel) && 1 < cb->getTeleportChannelExits(channel).size())
td.exits = cb->getTeleportChannelExits(channel);
if(cb->isTeleportChannelImpassable(channel))
{
si = 0;
break;
logGlobal->debugStream() << "Cannot find corresponding exit monolith for "<< id << " (obj at " << pos << ") :(";
td.impassable = true;
}
else if(getRandomExit(h) == ObjectInstanceID())
logGlobal->debugStream() << "All exits blocked for monolith "<< id << " (obj at " << pos << ") :(";
}
else
showInfoDialog(h, 70, 0);
cb->showTeleportDialog(&td);
}
void CGMonolith::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector<ObjectInstanceID> exits) const
{
ObjectInstanceID objId(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<Obj> 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;
}
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(cb->isTeleportChannelImpassable(channel))
{
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
@ -896,16 +1015,16 @@ void CGTeleport::postInit() //matches subterranean gates into pairs
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;
@ -913,28 +1032,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(cb->isTeleportChannelImpassable(channel))
{
if(gate.first == id)
return gate.second;
if(gate.second == id)
return gate.first;
logGlobal->debugStream() << "Cannot find exit whirlpool for "<< id << " (obj at " << pos << ") :(";
td.impassable = true;
}
else if(getRandomExit(h) == ObjectInstanceID())
logGlobal->debugStream() << "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(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()

View File

@ -247,19 +247,92 @@ public:
ui32 defaultResProduction();
};
class DLL_LINKAGE CGTeleport : public CGObjectInstance //teleports and subterranean gates
struct DLL_LINKAGE TeleportChannel
{
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
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 EPassability {UNKNOWN, IMPASSABLE, PASSABLE};
TeleportChannel() : passability(UNKNOWN) {}
std::vector<ObjectInstanceID> entrances;
std::vector<ObjectInstanceID> exits;
EPassability passability;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CGObjectInstance&>(*this);
h & entrances & exits & passability;
}
};
class DLL_LINKAGE CGTeleport : public CGObjectInstance
{
public:
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;
template <typename Handler> void serialize(Handler &h, const int version)
{
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);
}
};

View File

@ -125,14 +125,18 @@ bool TerrainTile::isClear(const TerrainTile *from /*= nullptr*/) const
return entrableTerrain(from) && !blocked;
}
int TerrainTile::topVisitableId() const
Obj TerrainTile::topVisitableId(bool excludeTop) const
{
return visitableObjects.size() ? visitableObjects.back()->ID : -1;
return topVisitableObj(excludeTop) ? topVisitableObj(excludeTop)->ID : Obj(Obj::NO_OBJ);
}
CGObjectInstance * TerrainTile::topVisitableObj() const
CGObjectInstance * TerrainTile::topVisitableObj(bool excludeTop) const
{
return visitableObjects.size() ? visitableObjects.back() : nullptr;
auto visitableObj = visitableObjects;
if(excludeTop && visitableObj.size())
visitableObj.pop_back();
return visitableObj.size() ? visitableObj.back() : nullptr;
}
bool TerrainTile::isCoastal() const

View File

@ -289,8 +289,8 @@ struct DLL_LINKAGE TerrainTile
/// Checks for blocking objects and terraint type (water / land).
bool isClear(const TerrainTile * from = nullptr) const;
/// Gets the ID of the top visitable object or -1 if there is none.
int topVisitableId() const;
CGObjectInstance * topVisitableObj() const;
Obj topVisitableId(bool excludeTop = false) const;
CGObjectInstance * topVisitableObj(bool excludeTop = false) const;
bool isWater() const;
bool isCoastal() const;
bool hasFavourableWinds() const;
@ -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;

View File

@ -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);
@ -282,6 +287,7 @@ void registerTypesClientPacks2(Serializer &s)
s.template registerType<Query, BlockingDialog>();
s.template registerType<Query, GarrisonDialog>();
s.template registerType<Query, ExchangeDialog>();
s.template registerType<Query, TeleportDialog>();
s.template registerType<CPackForClient, CGarrisonOperationPack>();
s.template registerType<CGarrisonOperationPack, ChangeStackCount>();

View File

@ -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());

View File

@ -1665,7 +1665,7 @@ void CGameHandler::setAmount(ObjectInstanceID objid, ui32 val)
sendAndApply(&sop);
}
bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, PlayerColor asker /*= 255*/ )
bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bool transit, PlayerColor asker /*= 255*/ )
{
const CGHeroInstance *h = getHero(hid);
@ -1765,7 +1765,8 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, Pl
}
else if(visitDest == VISIT_DEST)
{
visitObjectOnTile(t, h);
if(!transit || !CGTeleport::isTeleport(t.topVisitableObj()))
visitObjectOnTile(t, h);
}
queries.popIfTop(moveQuery);
@ -1900,6 +1901,14 @@ void CGameHandler::showBlockingDialog( BlockingDialog *iw )
sendToAllClients(iw);
}
void CGameHandler::showTeleportDialog( TeleportDialog *iw )
{
auto dialogQuery = make_shared<CTeleportDialogQuery>(*iw);
queries.addQuery(dialogQuery);
iw->queryID = dialogQuery->queryID;
sendToAllClients(iw);
}
void CGameHandler::giveResource(PlayerColor player, Res::ERes which, int val) //TODO: cap according to Bersy's suggestion
{
if(!val) return; //don't waste time on empty call

View File

@ -134,6 +134,7 @@ public:
//void showInfoDialog(InfoWindow *iw) override;
void showBlockingDialog(BlockingDialog *iw) override;
void showTeleportDialog(TeleportDialog *iw) override;
void showGarrisonDialog(ObjectInstanceID upobj, ObjectInstanceID hid, bool removableUnits) override;
void showThievesGuildWindow(PlayerColor player, ObjectInstanceID requestingObjId) override;
void giveResource(PlayerColor player, Res::ERes which, int val) override;
@ -167,7 +168,7 @@ public:
void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank = false) override; //if any of armies is hero, hero will be used
void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false) override; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle//void startBattleI(int heroID, CCreatureSet army, int3 tile, std::function<void(BattleResult*)> cb) override; //for hero<=>neutral army
void setAmount(ObjectInstanceID objid, ui32 val) override;
bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, PlayerColor asker = PlayerColor::NEUTRAL) override;
bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override;
void giveHeroBonus(GiveBonus * bonus) override;
void setMovePoints(SetMovePoints * smp) override;
void setManaPoints(ObjectInstanceID hid, int val) override;

View File

@ -313,6 +313,18 @@ CBlockingDialogQuery::CBlockingDialogQuery(const BlockingDialog &bd)
addPlayer(bd.player);
}
void CTeleportDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const
{
auto obj = dynamic_cast<const CGTeleport *>(objectVisit.visitedObject);
obj->teleportDialogAnswered(objectVisit.visitingHero, *answer, td.exits);
}
CTeleportDialogQuery::CTeleportDialogQuery(const TeleportDialog &td)
{
this->td = td;
addPlayer(td.hero->tempOwner);
}
CHeroLevelUpDialogQuery::CHeroLevelUpDialogQuery(const HeroLevelUp &Hlu)
{
hlu = Hlu;

View File

@ -130,6 +130,16 @@ public:
virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override;
};
class CTeleportDialogQuery : public CDialogQuery
{
public:
TeleportDialog td; //copy of pack... debug purposes
CTeleportDialogQuery(const TeleportDialog &td);
virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override;
};
class CHeroLevelUpDialogQuery : public CDialogQuery
{
public:

View File

@ -83,7 +83,7 @@ bool DismissHero::applyGh( CGameHandler *gh )
bool MoveHero::applyGh( CGameHandler *gh )
{
ERROR_IF_NOT_OWNS(hid);
return gh->moveHero(hid,dest,0,gh->getPlayerAt(c));
return gh->moveHero(hid,dest,0,transit,gh->getPlayerAt(c));
}
bool CastleTeleportHero::applyGh( CGameHandler *gh )