mirror of
https://github.com/vcmi/vcmi.git
synced 2025-11-25 22:42:04 +02:00
Merge pull request #93 from ArseniyShestakov/feature/pathfindingTeleports
Okay let's do this!
This commit is contained in:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user