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:
commit
ec879046ca
@ -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);
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -591,8 +591,36 @@ TGoalVec Explore::getAllPossibleSubgoals()
|
||||
case Obj::REDWOOD_OBSERVATORY:
|
||||
case Obj::PILLAR_OF_FIRE:
|
||||
case Obj::CARTOGRAPHER:
|
||||
case Obj::SUBTERRANEAN_GATE: //TODO: check ai->knownSubterraneanGates
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
157
AI/VCAI/VCAI.cpp
157
AI/VCAI/VCAI.cpp
@ -95,6 +95,7 @@ VCAI::VCAI(void)
|
||||
{
|
||||
LOG_TRACE(logAi);
|
||||
makingTurn = nullptr;
|
||||
destinationTeleport = ObjectInstanceID();
|
||||
}
|
||||
|
||||
VCAI::~VCAI(void)
|
||||
@ -122,7 +123,13 @@ 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)
|
||||
{
|
||||
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;
|
||||
@ -130,6 +137,8 @@ void VCAI::heroMoved(const TryMoveHero & details)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VCAI::stackChagedCount(const StackLocation &location, const TQuantity &change, bool isAbsolute)
|
||||
{
|
||||
@ -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();
|
||||
|
@ -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;
|
||||
|
3
AUTHORS
3
AUTHORS
@ -51,3 +51,6 @@ Alexey aka Macron1Robot, <>
|
||||
|
||||
Alexander Shishkin aka alexvins,
|
||||
* MinGW platform support, modding related programming
|
||||
|
||||
Arseniy Shestakov aka SXX,
|
||||
* pathfinding improvements, programming
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
||||
|
10
Global.h
10
Global.h
@ -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-=;
|
||||
|
@ -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)
|
||||
@ -2634,27 +2654,44 @@ bool CPlayerInterface::capturedAllEvents()
|
||||
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)
|
||||
int3 currentCoord = path.nodes[i].coord;
|
||||
int3 nextCoord = path.nodes[i-1].coord;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
//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)
|
||||
{
|
||||
{ //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;
|
||||
}
|
||||
@ -2666,8 +2703,7 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance* h, CGPath path)
|
||||
sh = CCS->soundh->playSound(soundBase::horseFlying, -1);
|
||||
#endif
|
||||
{
|
||||
newTerrain = cb->getTile(CGHeroInstance::convertPosition(path.nodes[i].coord, false))->terType;
|
||||
|
||||
newTerrain = cb->getTile(CGHeroInstance::convertPosition(currentCoord, false))->terType;
|
||||
if(newTerrain != currentTerrain)
|
||||
{
|
||||
CCS->soundh->stopSound(sh);
|
||||
@ -2676,18 +2712,21 @@ 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__;
|
||||
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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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 {};
|
||||
|
@ -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
|
||||
|
@ -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 },
|
||||
|
@ -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 },
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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){};
|
||||
|
@ -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);
|
||||
@ -3349,29 +3346,37 @@ 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)
|
||||
{
|
||||
@ -3381,36 +3386,33 @@ void CPathfinder::calculatePaths()
|
||||
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)
|
||||
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,14 +3435,29 @@ 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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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,114 +775,235 @@ 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
|
||||
{
|
||||
case Obj::MONOLITH_ONE_WAY_ENTRANCE: //one way - find corresponding exit monolith
|
||||
return type == BOTH || type == ENTRANCE;
|
||||
}
|
||||
|
||||
bool CGTeleport::isExit() const
|
||||
{
|
||||
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())
|
||||
return type == BOTH || type == EXIT;
|
||||
}
|
||||
|
||||
bool CGTeleport::isChannelEntrance(ObjectInstanceID id) const
|
||||
{
|
||||
destinationid = *RandomGeneratorUtil::nextItem(objs[Obj::MONOLITH_ONE_WAY_EXIT][subID], cb->gameState()->getRandomGenerator());
|
||||
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)
|
||||
{
|
||||
if(h->id == objTopVisObj->id) // Just to be sure it's won't happen.
|
||||
return false;
|
||||
|
||||
// Check if it's friendly hero or not
|
||||
if(gs->getPlayerRelations(h->tempOwner, objTopVisObj->tempOwner))
|
||||
{
|
||||
// Exchange between heroes only possible via subterranean gates
|
||||
if(!dynamic_cast<const CGSubterraneanGate *>(obj))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<ObjectInstanceID> CGTeleport::getPassableExits(CGameState * gs, const CGHeroInstance * h, std::vector<ObjectInstanceID> exits)
|
||||
{
|
||||
vstd::erase_if(exits, [&](ObjectInstanceID exit) -> bool
|
||||
{
|
||||
return !isExitPassable(gs, h, gs->getObj(exit));
|
||||
});
|
||||
return exits;
|
||||
}
|
||||
|
||||
void CGTeleport::addToChannel(std::map<TeleportChannelID, shared_ptr<TeleportChannel> > &channelsList, const CGTeleport * obj)
|
||||
{
|
||||
shared_ptr<TeleportChannel> tc;
|
||||
if(channelsList.find(obj->channel) == channelsList.end())
|
||||
{
|
||||
tc = make_shared<TeleportChannel>();
|
||||
channelsList.insert(std::make_pair(obj->channel, tc));
|
||||
}
|
||||
else
|
||||
{
|
||||
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);
|
||||
tc = channelsList[obj->channel];
|
||||
|
||||
if (ID == Obj::WHIRLPOOL)
|
||||
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))
|
||||
{
|
||||
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);
|
||||
tc->passability = TeleportChannel::PASSABLE;
|
||||
}
|
||||
}
|
||||
|
||||
TQuantity countToTake = h->getStackCount(targetstack) * 0.5;
|
||||
vstd::amax(countToTake, 1);
|
||||
TeleportChannelID CGMonolith::findMeChannel(std::vector<Obj> IDs, int SubID) const
|
||||
{
|
||||
for(auto obj : cb->gameState()->map->objects)
|
||||
{
|
||||
auto teleportObj = dynamic_cast<const CGTeleport *>(cb->getObj(obj->id));
|
||||
if(teleportObj && vstd::contains(IDs, teleportObj->ID) && teleportObj->subID == SubID)
|
||||
return teleportObj->channel;
|
||||
}
|
||||
return TeleportChannelID();
|
||||
}
|
||||
|
||||
void CGMonolith::onHeroVisit( const CGHeroInstance * h ) const
|
||||
{
|
||||
TeleportDialog td(h, channel);
|
||||
if(isEntrance())
|
||||
{
|
||||
if(cb->isTeleportChannelBidirectional(channel) && 1 < cb->getTeleportChannelExits(channel).size())
|
||||
td.exits = cb->getTeleportChannelExits(channel);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
if(cb->isTeleportChannelImpassable(channel))
|
||||
{
|
||||
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
|
||||
logGlobal->warnStream() << "Cannot find corresponding exit monolith for "<< id;
|
||||
break;
|
||||
case Obj::SUBTERRANEAN_GATE: //find nearest subterranean gate on the other level
|
||||
{
|
||||
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.
|
||||
showInfoDialog(h, 70, 0);
|
||||
|
||||
cb->showTeleportDialog(&td);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(destinationid == ObjectInstanceID())
|
||||
|
||||
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
|
||||
{
|
||||
logGlobal->warnStream() << "Cannot find exit... (obj at " << pos << ") :( ";
|
||||
return;
|
||||
}
|
||||
if (ID == Obj::WHIRLPOOL)
|
||||
{
|
||||
std::set<int3> tiles = cb->getObj(destinationid)->getBlockedPos();
|
||||
auto & tile = *RandomGeneratorUtil::nextItem(tiles, cb->gameState()->getRandomGenerator());
|
||||
cb->moveHero(h->id, tile + int3(1,0,0), true);
|
||||
}
|
||||
else if(objId == ObjectInstanceID())
|
||||
objId = getRandomExit(hero);
|
||||
else
|
||||
cb->moveHero (h->id,CGHeroInstance::convertPosition(cb->getObj(destinationid)->pos,true) - getVisitableOffset(), true);
|
||||
assert(vstd::contains(exits, objId)); // Likely cheating attempt: not random teleporter choosen, but it's not from provided list
|
||||
|
||||
auto obj = cb->getObj(objId);
|
||||
if(obj)
|
||||
cb->moveHero(hero->id,CGHeroInstance::convertPosition(obj->pos,true) - getVisitableOffset(), true);
|
||||
}
|
||||
|
||||
void CGTeleport::initObj()
|
||||
void CGMonolith::initObj()
|
||||
{
|
||||
int si = subID;
|
||||
std::vector<Obj> IDs;
|
||||
IDs.push_back(ID);
|
||||
switch(ID)
|
||||
{
|
||||
case Obj::SUBTERRANEAN_GATE://ignore subterranean gates subid
|
||||
case Obj::WHIRLPOOL:
|
||||
{
|
||||
si = 0;
|
||||
case Obj::MONOLITH_ONE_WAY_ENTRANCE:
|
||||
type = ENTRANCE;
|
||||
IDs.push_back(Obj::MONOLITH_ONE_WAY_EXIT);
|
||||
break;
|
||||
}
|
||||
case Obj::MONOLITH_ONE_WAY_EXIT:
|
||||
type = EXIT;
|
||||
IDs.push_back(Obj::MONOLITH_ONE_WAY_ENTRANCE);
|
||||
break;
|
||||
case Obj::MONOLITH_TWO_WAY:
|
||||
default:
|
||||
type = BOTH;
|
||||
break;
|
||||
}
|
||||
objs[ID][si].push_back(id);
|
||||
}
|
||||
|
||||
void CGTeleport::postInit() //matches subterranean gates into pairs
|
||||
channel = findMeChannel(IDs, subID);
|
||||
if(channel == TeleportChannelID())
|
||||
channel = TeleportChannelID(cb->gameState()->map->teleportChannels.size());
|
||||
|
||||
addToChannel(cb->gameState()->map->teleportChannels, this);
|
||||
}
|
||||
|
||||
void CGSubterraneanGate::onHeroVisit( const CGHeroInstance * h ) const
|
||||
{
|
||||
TeleportDialog td(h, channel);
|
||||
if(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);
|
||||
auto hlp = dynamic_cast<CGSubterraneanGate *>(gs->getObjInstance(obj->id));
|
||||
if(hlp)
|
||||
gatesSplit[hlp->pos.z].push_back(hlp);
|
||||
}
|
||||
|
||||
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CGWhirlpool::onHeroVisit( const CGHeroInstance * h ) const
|
||||
{
|
||||
TeleportDialog td(h, channel);
|
||||
if(cb->isTeleportChannelImpassable(channel))
|
||||
{
|
||||
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 << ") :(";
|
||||
|
||||
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
|
||||
gates.push_back(std::make_pair(cur->id, ObjectInstanceID()));
|
||||
}
|
||||
objs.erase(Obj::SUBTERRANEAN_GATE);
|
||||
td.exits = getAllExits(true);
|
||||
|
||||
cb->showTeleportDialog(&td);
|
||||
}
|
||||
|
||||
ObjectInstanceID CGTeleport::getMatchingGate(ObjectInstanceID id)
|
||||
void CGWhirlpool::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector<ObjectInstanceID> exits) const
|
||||
{
|
||||
for(auto & gate : gates)
|
||||
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)
|
||||
{
|
||||
if(gate.first == id)
|
||||
return gate.second;
|
||||
if(gate.second == id)
|
||||
return gate.first;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return ObjectInstanceID();
|
||||
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()
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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>();
|
||||
|
@ -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());
|
||||
|
@ -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,6 +1765,7 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, Pl
|
||||
}
|
||||
else if(visitDest == VISIT_DEST)
|
||||
{
|
||||
if(!transit || !CGTeleport::isTeleport(t.topVisitableObj()))
|
||||
visitObjectOnTile(t, h);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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:
|
||||
|
@ -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 )
|
||||
|
Loading…
Reference in New Issue
Block a user