mirror of
https://github.com/vcmi/vcmi.git
synced 2024-11-24 08:32:34 +02:00
Refactoring: Move SectorMap to separate file
This commit is contained in:
parent
f2db64af94
commit
f5ba181173
431
AI/VCAI/SectorMap.cpp
Normal file
431
AI/VCAI/SectorMap.cpp
Normal file
@ -0,0 +1,431 @@
|
||||
/*
|
||||
* SectorMap.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
|
||||
#include "StdInc.h"
|
||||
#include "SectorMap.h"
|
||||
#include "VCAI.h"
|
||||
|
||||
#include "../../CCallback.h"
|
||||
#include "../../lib/mapping/CMap.h"
|
||||
#include "../../lib/mapObjects/MapObjects.h"
|
||||
#include "../../lib/CPathfinder.h"
|
||||
#include "../../lib/CGameState.h"
|
||||
|
||||
extern boost::thread_specific_ptr<CCallback> cb;
|
||||
extern boost::thread_specific_ptr<VCAI> ai;
|
||||
|
||||
SectorMap::SectorMap()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
SectorMap::SectorMap(HeroPtr h)
|
||||
{
|
||||
update();
|
||||
makeParentBFS(h->visitablePos());
|
||||
}
|
||||
|
||||
bool SectorMap::markIfBlocked(TSectorID & sec, crint3 pos, const TerrainTile * t)
|
||||
{
|
||||
if (t->blocked && !t->visitable)
|
||||
{
|
||||
sec = NOT_AVAILABLE;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SectorMap::markIfBlocked(TSectorID & sec, crint3 pos)
|
||||
{
|
||||
return markIfBlocked(sec, pos, getTile(pos));
|
||||
}
|
||||
|
||||
void SectorMap::update()
|
||||
{
|
||||
visibleTiles = cb->getAllVisibleTiles();
|
||||
auto shape = visibleTiles->shape();
|
||||
sector.resize(boost::extents[shape[0]][shape[1]][shape[2]]);
|
||||
|
||||
clear();
|
||||
int curSector = 3; //0 is invisible, 1 is not explored
|
||||
|
||||
CCallback * cbp = cb.get(); //optimization
|
||||
foreach_tile_pos([&](crint3 pos)
|
||||
{
|
||||
if (retrieveTile(pos) == NOT_CHECKED)
|
||||
{
|
||||
if (!markIfBlocked(retrieveTile(pos), pos))
|
||||
exploreNewSector(pos, curSector++, cbp);
|
||||
}
|
||||
});
|
||||
valid = true;
|
||||
}
|
||||
|
||||
SectorMap::TSectorID & SectorMap::retrieveTileN(SectorMap::TSectorArray & a, const int3 & pos)
|
||||
{
|
||||
return a[pos.x][pos.y][pos.z];
|
||||
}
|
||||
|
||||
const SectorMap::TSectorID & SectorMap::retrieveTileN(const SectorMap::TSectorArray & a, const int3 & pos)
|
||||
{
|
||||
return a[pos.x][pos.y][pos.z];
|
||||
}
|
||||
|
||||
void SectorMap::clear()
|
||||
{
|
||||
//TODO: rotate to [z][x][y]
|
||||
auto fow = cb->getVisibilityMap();
|
||||
//TODO: any magic to automate this? will need array->array conversion
|
||||
//std::transform(fow.begin(), fow.end(), sector.begin(), [](const ui8 &f) -> unsigned short
|
||||
//{
|
||||
// return f; //type conversion
|
||||
//});
|
||||
auto width = fow.size();
|
||||
auto height = fow.front().size();
|
||||
auto depth = fow.front().front().size();
|
||||
for (size_t x = 0; x < width; x++)
|
||||
{
|
||||
for (size_t y = 0; y < height; y++)
|
||||
{
|
||||
for (size_t z = 0; z < depth; z++)
|
||||
sector[x][y][z] = fow[x][y][z];
|
||||
}
|
||||
}
|
||||
valid = false;
|
||||
}
|
||||
|
||||
void SectorMap::exploreNewSector(crint3 pos, int num, CCallback * cbp)
|
||||
{
|
||||
Sector & s = infoOnSectors[num];
|
||||
s.id = num;
|
||||
s.water = getTile(pos)->isWater();
|
||||
|
||||
std::queue<int3> toVisit;
|
||||
toVisit.push(pos);
|
||||
while (!toVisit.empty())
|
||||
{
|
||||
int3 curPos = toVisit.front();
|
||||
toVisit.pop();
|
||||
TSectorID & sec = retrieveTile(curPos);
|
||||
if (sec == NOT_CHECKED)
|
||||
{
|
||||
const TerrainTile * t = getTile(curPos);
|
||||
if (!markIfBlocked(sec, curPos, t))
|
||||
{
|
||||
if (t->isWater() == s.water) //sector is only-water or only-land
|
||||
{
|
||||
sec = num;
|
||||
s.tiles.push_back(curPos);
|
||||
foreach_neighbour(cbp, curPos, [&](CCallback * cbp, crint3 neighPos)
|
||||
{
|
||||
if (retrieveTile(neighPos) == NOT_CHECKED)
|
||||
{
|
||||
toVisit.push(neighPos);
|
||||
//parent[neighPos] = curPos;
|
||||
}
|
||||
const TerrainTile * nt = getTile(neighPos);
|
||||
if (nt && nt->isWater() != s.water && canBeEmbarkmentPoint(nt, s.water))
|
||||
{
|
||||
s.embarkmentPoints.push_back(neighPos);
|
||||
}
|
||||
});
|
||||
|
||||
if (t->visitable)
|
||||
{
|
||||
auto obj = t->visitableObjects.front();
|
||||
if (cb->getObj(obj->id, false)) // FIXME: we have to filter invisible objcts like events, but probably TerrainTile shouldn't be used in SectorMap at all
|
||||
s.visitableObjs.push_back(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vstd::removeDuplicates(s.embarkmentPoints);
|
||||
}
|
||||
|
||||
void SectorMap::write(crstring fname)
|
||||
{
|
||||
std::ofstream out(fname);
|
||||
for (int k = 0; k < cb->getMapSize().z; k++)
|
||||
{
|
||||
for (int j = 0; j < cb->getMapSize().y; j++)
|
||||
{
|
||||
for (int i = 0; i < cb->getMapSize().x; i++)
|
||||
{
|
||||
out << (int)sector[i][j][k] << '\t';
|
||||
}
|
||||
out << std::endl;
|
||||
}
|
||||
out << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
this functions returns one target tile or invalid tile. We will use it to poll possible destinations
|
||||
For ship construction etc, another function (goal?) is needed
|
||||
*/
|
||||
int3 SectorMap::firstTileToGet(HeroPtr h, crint3 dst)
|
||||
{
|
||||
int3 ret(-1, -1, -1);
|
||||
|
||||
int sourceSector = retrieveTile(h->visitablePos());
|
||||
int destinationSector = retrieveTile(dst);
|
||||
|
||||
const Sector * src = &infoOnSectors[sourceSector];
|
||||
const Sector * dest = &infoOnSectors[destinationSector];
|
||||
|
||||
if (sourceSector != destinationSector) //use ships, shipyards etc..
|
||||
{
|
||||
if (ai->isAccessibleForHero(dst, h)) //pathfinder can find a way using ships and gates if tile is not blocked by objects
|
||||
return dst;
|
||||
|
||||
std::map<const Sector *, const Sector *> preds;
|
||||
std::queue<const Sector *> sectorQueue;
|
||||
sectorQueue.push(src);
|
||||
while (!sectorQueue.empty())
|
||||
{
|
||||
const Sector * s = sectorQueue.front();
|
||||
sectorQueue.pop();
|
||||
|
||||
for (int3 ep : s->embarkmentPoints)
|
||||
{
|
||||
Sector * neigh = &infoOnSectors[retrieveTile(ep)];
|
||||
//preds[s].push_back(neigh);
|
||||
if (!preds[neigh])
|
||||
{
|
||||
preds[neigh] = s;
|
||||
sectorQueue.push(neigh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!preds[dest])
|
||||
{
|
||||
//write("test.txt");
|
||||
|
||||
return ret;
|
||||
//throw cannotFulfillGoalException(boost::str(boost::format("Cannot find connection between sectors %d and %d") % src->id % dst->id));
|
||||
}
|
||||
|
||||
std::vector<const Sector *> toTraverse;
|
||||
toTraverse.push_back(dest);
|
||||
while (toTraverse.back() != src)
|
||||
{
|
||||
toTraverse.push_back(preds[toTraverse.back()]);
|
||||
}
|
||||
|
||||
if (preds[dest])
|
||||
{
|
||||
//TODO: would be nice to find sectors in loop
|
||||
const Sector * sectorToReach = toTraverse.at(toTraverse.size() - 2);
|
||||
|
||||
if (!src->water && sectorToReach->water) //embark
|
||||
{
|
||||
//embark on ship -> look for an EP with a boat
|
||||
auto firstEP = boost::find_if(src->embarkmentPoints, [=](crint3 pos) -> bool
|
||||
{
|
||||
const TerrainTile * t = getTile(pos);
|
||||
if (t && t->visitableObjects.size() == 1 && t->topVisitableId() == Obj::BOAT)
|
||||
{
|
||||
if (retrieveTile(pos) == sectorToReach->id)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (firstEP != src->embarkmentPoints.end())
|
||||
{
|
||||
return *firstEP;
|
||||
}
|
||||
else
|
||||
{
|
||||
//we need to find a shipyard with an access to the desired sector's EP
|
||||
//TODO what about Summon Boat spell?
|
||||
std::vector<const IShipyard *> shipyards;
|
||||
for (const CGTownInstance * t : cb->getTownsInfo())
|
||||
{
|
||||
if (t->hasBuilt(BuildingID::SHIPYARD))
|
||||
shipyards.push_back(t);
|
||||
}
|
||||
|
||||
for (const CGObjectInstance * obj : ai->getFlaggedObjects())
|
||||
{
|
||||
if (obj->ID != Obj::TOWN) //towns were handled in the previous loop
|
||||
{
|
||||
if (const IShipyard * shipyard = IShipyard::castFrom(obj))
|
||||
shipyards.push_back(shipyard);
|
||||
}
|
||||
}
|
||||
|
||||
shipyards.erase(boost::remove_if(shipyards, [=](const IShipyard * shipyard) -> bool
|
||||
{
|
||||
return shipyard->shipyardStatus() != 0 || retrieveTile(shipyard->bestLocation()) != sectorToReach->id;
|
||||
}), shipyards.end());
|
||||
|
||||
if (!shipyards.size())
|
||||
{
|
||||
//TODO consider possibility of building shipyard in a town
|
||||
return ret;
|
||||
|
||||
//throw cannotFulfillGoalException("There is no known shipyard!");
|
||||
}
|
||||
|
||||
//we have only shipyards that possibly can build ships onto the appropriate EP
|
||||
auto ownedGoodShipyard = boost::find_if(shipyards, [](const IShipyard * s) -> bool
|
||||
{
|
||||
return s->o->tempOwner == ai->playerID;
|
||||
});
|
||||
|
||||
if (ownedGoodShipyard != shipyards.end())
|
||||
{
|
||||
const IShipyard * s = *ownedGoodShipyard;
|
||||
TResources shipCost;
|
||||
s->getBoatCost(shipCost);
|
||||
if (cb->getResourceAmount().canAfford(shipCost))
|
||||
{
|
||||
int3 ret = s->bestLocation();
|
||||
cb->buildBoat(s); //TODO: move actions elsewhere
|
||||
return ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
//TODO gather res
|
||||
return ret;
|
||||
|
||||
//throw cannotFulfillGoalException("Not enough resources to build a boat");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//TODO pick best shipyard to take over
|
||||
return shipyards.front()->o->visitablePos();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (src->water && !sectorToReach->water)
|
||||
{
|
||||
//TODO
|
||||
//disembark
|
||||
return ret;
|
||||
}
|
||||
else //use subterranean gates - not needed since gates are now handled via Pathfinder
|
||||
{
|
||||
return ret;
|
||||
//throw cannotFulfillGoalException("Land-land and water-water inter-sector transitions are not implemented!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return ret;
|
||||
//throw cannotFulfillGoalException("Inter-sector route detection failed: not connected sectors?");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return findFirstVisitableTile(h, dst);
|
||||
}
|
||||
}
|
||||
|
||||
int3 SectorMap::findFirstVisitableTile(HeroPtr h, crint3 dst)
|
||||
{
|
||||
int3 ret(-1, -1, -1);
|
||||
int3 curtile = dst;
|
||||
|
||||
while (curtile != h->visitablePos())
|
||||
{
|
||||
auto topObj = cb->getTopObj(curtile);
|
||||
if (topObj && topObj->ID == Obj::HERO && topObj != h.h)
|
||||
{
|
||||
if (cb->getPlayerRelations(h->tempOwner, topObj->tempOwner) != PlayerRelations::ENEMIES)
|
||||
{
|
||||
logAi->warn("Another allied hero stands in our way");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
if (ai->myCb->getPathsInfo(h.get())->getPathInfo(curtile)->reachable())
|
||||
{
|
||||
return curtile;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto i = parent.find(curtile);
|
||||
if (i != parent.end())
|
||||
{
|
||||
assert(curtile != i->second);
|
||||
curtile = i->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ret;
|
||||
//throw cannotFulfillGoalException("Unreachable tile in sector? Should not happen!");
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SectorMap::makeParentBFS(crint3 source)
|
||||
{
|
||||
parent.clear();
|
||||
|
||||
int mySector = retrieveTile(source);
|
||||
std::queue<int3> toVisit;
|
||||
toVisit.push(source);
|
||||
while (!toVisit.empty())
|
||||
{
|
||||
int3 curPos = toVisit.front();
|
||||
toVisit.pop();
|
||||
TSectorID & sec = retrieveTile(curPos);
|
||||
assert(sec == mySector); //consider only tiles from the same sector
|
||||
UNUSED(sec);
|
||||
|
||||
foreach_neighbour(curPos, [&](crint3 neighPos)
|
||||
{
|
||||
if (retrieveTile(neighPos) == mySector && !vstd::contains(parent, neighPos))
|
||||
{
|
||||
if (cb->canMoveBetween(curPos, neighPos))
|
||||
{
|
||||
toVisit.push(neighPos);
|
||||
parent[neighPos] = curPos;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
SectorMap::TSectorID & SectorMap::retrieveTile(crint3 pos)
|
||||
{
|
||||
return retrieveTileN(sector, pos);
|
||||
}
|
||||
|
||||
TerrainTile * SectorMap::getTile(crint3 pos) const
|
||||
{
|
||||
//out of bounds access should be handled by boost::multi_array
|
||||
//still we cached this array to avoid any checks
|
||||
return visibleTiles->operator[](pos.x)[pos.y][pos.z];
|
||||
}
|
||||
|
||||
std::vector<const CGObjectInstance *> SectorMap::getNearbyObjs(HeroPtr h, bool sectorsAround)
|
||||
{
|
||||
const Sector * heroSector = &infoOnSectors[retrieveTile(h->visitablePos())];
|
||||
if (sectorsAround)
|
||||
{
|
||||
std::vector<const CGObjectInstance *> ret;
|
||||
for (auto embarkPoint : heroSector->embarkmentPoints)
|
||||
{
|
||||
const Sector * embarkSector = &infoOnSectors[retrieveTile(embarkPoint)];
|
||||
range::copy(embarkSector->visitableObjs, std::back_inserter(ret));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
return heroSector->visitableObjs;
|
||||
}
|
70
AI/VCAI/SectorMap.h
Normal file
70
AI/VCAI/SectorMap.h
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* SectorMap.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AIUtility.h"
|
||||
|
||||
enum
|
||||
{
|
||||
NOT_VISIBLE = 0,
|
||||
NOT_CHECKED = 1,
|
||||
NOT_AVAILABLE
|
||||
};
|
||||
|
||||
struct SectorMap
|
||||
{
|
||||
//a sector is set of tiles that would be mutually reachable if all visitable objs would be passable (incl monsters)
|
||||
struct Sector
|
||||
{
|
||||
int id;
|
||||
std::vector<int3> tiles;
|
||||
std::vector<int3> embarkmentPoints; //tiles of other sectors onto which we can (dis)embark
|
||||
std::vector<const CGObjectInstance *> visitableObjs;
|
||||
bool water; //all tiles of sector are land or water
|
||||
Sector()
|
||||
{
|
||||
id = -1;
|
||||
water = false;
|
||||
}
|
||||
};
|
||||
|
||||
typedef unsigned short TSectorID; //smaller than int to allow -1 value. Max number of sectors 65K should be enough for any proper map.
|
||||
typedef boost::multi_array<TSectorID, 3> TSectorArray;
|
||||
|
||||
bool valid; //some kind of lazy eval
|
||||
std::map<int3, int3> parent;
|
||||
TSectorArray sector;
|
||||
//std::vector<std::vector<std::vector<unsigned char>>> pathfinderSector;
|
||||
|
||||
std::map<int, Sector> infoOnSectors;
|
||||
std::shared_ptr<boost::multi_array<TerrainTile *, 3>> visibleTiles;
|
||||
|
||||
SectorMap();
|
||||
SectorMap(HeroPtr h);
|
||||
void update();
|
||||
void clear();
|
||||
void exploreNewSector(crint3 pos, int num, CCallback * cbp);
|
||||
void write(crstring fname);
|
||||
|
||||
bool markIfBlocked(TSectorID & sec, crint3 pos, const TerrainTile * t);
|
||||
bool markIfBlocked(TSectorID & sec, crint3 pos);
|
||||
TSectorID & retrieveTile(crint3 pos);
|
||||
TSectorID & retrieveTileN(TSectorArray & vectors, const int3 & pos);
|
||||
const TSectorID & retrieveTileN(const TSectorArray & vectors, const int3 & pos);
|
||||
TerrainTile * getTile(crint3 pos) const;
|
||||
std::vector<const CGObjectInstance *> getNearbyObjs(HeroPtr h, bool sectorsAround);
|
||||
|
||||
void makeParentBFS(crint3 source);
|
||||
|
||||
int3 firstTileToGet(HeroPtr h, crint3 dst); //if h wants to reach tile dst, which tile he should visit to clear the way?
|
||||
int3 findFirstVisitableTile(HeroPtr h, crint3 dst);
|
||||
};
|
405
AI/VCAI/VCAI.cpp
405
AI/VCAI/VCAI.cpp
@ -2886,153 +2886,7 @@ bool AIStatus::channelProbing()
|
||||
return ongoingChannelProbing;
|
||||
}
|
||||
|
||||
SectorMap::SectorMap()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
SectorMap::SectorMap(HeroPtr h)
|
||||
{
|
||||
update();
|
||||
makeParentBFS(h->visitablePos());
|
||||
}
|
||||
|
||||
bool SectorMap::markIfBlocked(TSectorID & sec, crint3 pos, const TerrainTile * t)
|
||||
{
|
||||
if(t->blocked && !t->visitable)
|
||||
{
|
||||
sec = NOT_AVAILABLE;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SectorMap::markIfBlocked(TSectorID & sec, crint3 pos)
|
||||
{
|
||||
return markIfBlocked(sec, pos, getTile(pos));
|
||||
}
|
||||
|
||||
void SectorMap::update()
|
||||
{
|
||||
visibleTiles = cb->getAllVisibleTiles();
|
||||
auto shape = visibleTiles->shape();
|
||||
sector.resize(boost::extents[shape[0]][shape[1]][shape[2]]);
|
||||
|
||||
clear();
|
||||
int curSector = 3; //0 is invisible, 1 is not explored
|
||||
|
||||
CCallback * cbp = cb.get(); //optimization
|
||||
foreach_tile_pos([&](crint3 pos)
|
||||
{
|
||||
if(retrieveTile(pos) == NOT_CHECKED)
|
||||
{
|
||||
if(!markIfBlocked(retrieveTile(pos), pos))
|
||||
exploreNewSector(pos, curSector++, cbp);
|
||||
}
|
||||
});
|
||||
valid = true;
|
||||
}
|
||||
|
||||
SectorMap::TSectorID & SectorMap::retrieveTileN(SectorMap::TSectorArray & a, const int3 & pos)
|
||||
{
|
||||
return a[pos.x][pos.y][pos.z];
|
||||
}
|
||||
|
||||
const SectorMap::TSectorID & SectorMap::retrieveTileN(const SectorMap::TSectorArray & a, const int3 & pos)
|
||||
{
|
||||
return a[pos.x][pos.y][pos.z];
|
||||
}
|
||||
|
||||
void SectorMap::clear()
|
||||
{
|
||||
//TODO: rotate to [z][x][y]
|
||||
auto fow = cb->getVisibilityMap();
|
||||
//TODO: any magic to automate this? will need array->array conversion
|
||||
//std::transform(fow.begin(), fow.end(), sector.begin(), [](const ui8 &f) -> unsigned short
|
||||
//{
|
||||
// return f; //type conversion
|
||||
//});
|
||||
auto width = fow.size();
|
||||
auto height = fow.front().size();
|
||||
auto depth = fow.front().front().size();
|
||||
for(size_t x = 0; x < width; x++)
|
||||
{
|
||||
for(size_t y = 0; y < height; y++)
|
||||
{
|
||||
for(size_t z = 0; z < depth; z++)
|
||||
sector[x][y][z] = fow[x][y][z];
|
||||
}
|
||||
}
|
||||
valid = false;
|
||||
}
|
||||
|
||||
void SectorMap::exploreNewSector(crint3 pos, int num, CCallback * cbp)
|
||||
{
|
||||
Sector & s = infoOnSectors[num];
|
||||
s.id = num;
|
||||
s.water = getTile(pos)->isWater();
|
||||
|
||||
std::queue<int3> toVisit;
|
||||
toVisit.push(pos);
|
||||
while(!toVisit.empty())
|
||||
{
|
||||
int3 curPos = toVisit.front();
|
||||
toVisit.pop();
|
||||
TSectorID & sec = retrieveTile(curPos);
|
||||
if(sec == NOT_CHECKED)
|
||||
{
|
||||
const TerrainTile * t = getTile(curPos);
|
||||
if(!markIfBlocked(sec, curPos, t))
|
||||
{
|
||||
if(t->isWater() == s.water) //sector is only-water or only-land
|
||||
{
|
||||
sec = num;
|
||||
s.tiles.push_back(curPos);
|
||||
foreach_neighbour(cbp, curPos, [&](CCallback * cbp, crint3 neighPos)
|
||||
{
|
||||
if(retrieveTile(neighPos) == NOT_CHECKED)
|
||||
{
|
||||
toVisit.push(neighPos);
|
||||
//parent[neighPos] = curPos;
|
||||
}
|
||||
const TerrainTile * nt = getTile(neighPos);
|
||||
if(nt && nt->isWater() != s.water && canBeEmbarkmentPoint(nt, s.water))
|
||||
{
|
||||
s.embarkmentPoints.push_back(neighPos);
|
||||
}
|
||||
});
|
||||
|
||||
if(t->visitable)
|
||||
{
|
||||
auto obj = t->visitableObjects.front();
|
||||
if(cb->getObj(obj->id, false)) // FIXME: we have to filter invisible objcts like events, but probably TerrainTile shouldn't be used in SectorMap at all
|
||||
s.visitableObjs.push_back(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vstd::removeDuplicates(s.embarkmentPoints);
|
||||
}
|
||||
|
||||
void SectorMap::write(crstring fname)
|
||||
{
|
||||
std::ofstream out(fname);
|
||||
for(int k = 0; k < cb->getMapSize().z; k++)
|
||||
{
|
||||
for(int j = 0; j < cb->getMapSize().y; j++)
|
||||
{
|
||||
for(int i = 0; i < cb->getMapSize().x; i++)
|
||||
{
|
||||
out << (int)sector[i][j][k] << '\t';
|
||||
}
|
||||
out << std::endl;
|
||||
}
|
||||
out << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
bool isWeeklyRevisitable(const CGObjectInstance * obj)
|
||||
{
|
||||
@ -3167,263 +3021,4 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj)
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
this functions returns one target tile or invalid tile. We will use it to poll possible destinations
|
||||
For ship construction etc, another function (goal?) is needed
|
||||
*/
|
||||
int3 SectorMap::firstTileToGet(HeroPtr h, crint3 dst)
|
||||
{
|
||||
int3 ret(-1, -1, -1);
|
||||
|
||||
int sourceSector = retrieveTile(h->visitablePos());
|
||||
int destinationSector = retrieveTile(dst);
|
||||
|
||||
const Sector * src = &infoOnSectors[sourceSector];
|
||||
const Sector * dest = &infoOnSectors[destinationSector];
|
||||
|
||||
if(sourceSector != destinationSector) //use ships, shipyards etc..
|
||||
{
|
||||
if(ai->isAccessibleForHero(dst, h)) //pathfinder can find a way using ships and gates if tile is not blocked by objects
|
||||
return dst;
|
||||
|
||||
std::map<const Sector *, const Sector *> preds;
|
||||
std::queue<const Sector *> sectorQueue;
|
||||
sectorQueue.push(src);
|
||||
while(!sectorQueue.empty())
|
||||
{
|
||||
const Sector * s = sectorQueue.front();
|
||||
sectorQueue.pop();
|
||||
|
||||
for(int3 ep : s->embarkmentPoints)
|
||||
{
|
||||
Sector * neigh = &infoOnSectors[retrieveTile(ep)];
|
||||
//preds[s].push_back(neigh);
|
||||
if(!preds[neigh])
|
||||
{
|
||||
preds[neigh] = s;
|
||||
sectorQueue.push(neigh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!preds[dest])
|
||||
{
|
||||
//write("test.txt");
|
||||
|
||||
return ret;
|
||||
//throw cannotFulfillGoalException(boost::str(boost::format("Cannot find connection between sectors %d and %d") % src->id % dst->id));
|
||||
}
|
||||
|
||||
std::vector<const Sector *> toTraverse;
|
||||
toTraverse.push_back(dest);
|
||||
while(toTraverse.back() != src)
|
||||
{
|
||||
toTraverse.push_back(preds[toTraverse.back()]);
|
||||
}
|
||||
|
||||
if(preds[dest])
|
||||
{
|
||||
//TODO: would be nice to find sectors in loop
|
||||
const Sector * sectorToReach = toTraverse.at(toTraverse.size() - 2);
|
||||
|
||||
if(!src->water && sectorToReach->water) //embark
|
||||
{
|
||||
//embark on ship -> look for an EP with a boat
|
||||
auto firstEP = boost::find_if(src->embarkmentPoints, [=](crint3 pos) -> bool
|
||||
{
|
||||
const TerrainTile * t = getTile(pos);
|
||||
if(t && t->visitableObjects.size() == 1 && t->topVisitableId() == Obj::BOAT)
|
||||
{
|
||||
if(retrieveTile(pos) == sectorToReach->id)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if(firstEP != src->embarkmentPoints.end())
|
||||
{
|
||||
return *firstEP;
|
||||
}
|
||||
else
|
||||
{
|
||||
//we need to find a shipyard with an access to the desired sector's EP
|
||||
//TODO what about Summon Boat spell?
|
||||
std::vector<const IShipyard *> shipyards;
|
||||
for(const CGTownInstance * t : cb->getTownsInfo())
|
||||
{
|
||||
if(t->hasBuilt(BuildingID::SHIPYARD))
|
||||
shipyards.push_back(t);
|
||||
}
|
||||
|
||||
for(const CGObjectInstance * obj : ai->getFlaggedObjects())
|
||||
{
|
||||
if(obj->ID != Obj::TOWN) //towns were handled in the previous loop
|
||||
{
|
||||
if(const IShipyard * shipyard = IShipyard::castFrom(obj))
|
||||
shipyards.push_back(shipyard);
|
||||
}
|
||||
}
|
||||
|
||||
shipyards.erase(boost::remove_if(shipyards, [=](const IShipyard * shipyard) -> bool
|
||||
{
|
||||
return shipyard->shipyardStatus() != 0 || retrieveTile(shipyard->bestLocation()) != sectorToReach->id;
|
||||
}), shipyards.end());
|
||||
|
||||
if(!shipyards.size())
|
||||
{
|
||||
//TODO consider possibility of building shipyard in a town
|
||||
return ret;
|
||||
|
||||
//throw cannotFulfillGoalException("There is no known shipyard!");
|
||||
}
|
||||
|
||||
//we have only shipyards that possibly can build ships onto the appropriate EP
|
||||
auto ownedGoodShipyard = boost::find_if(shipyards, [](const IShipyard * s) -> bool
|
||||
{
|
||||
return s->o->tempOwner == ai->playerID;
|
||||
});
|
||||
|
||||
if(ownedGoodShipyard != shipyards.end())
|
||||
{
|
||||
const IShipyard * s = *ownedGoodShipyard;
|
||||
TResources shipCost;
|
||||
s->getBoatCost(shipCost);
|
||||
if(cb->getResourceAmount().canAfford(shipCost))
|
||||
{
|
||||
int3 ret = s->bestLocation();
|
||||
cb->buildBoat(s); //TODO: move actions elsewhere
|
||||
return ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
//TODO gather res
|
||||
return ret;
|
||||
|
||||
//throw cannotFulfillGoalException("Not enough resources to build a boat");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//TODO pick best shipyard to take over
|
||||
return shipyards.front()->o->visitablePos();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(src->water && !sectorToReach->water)
|
||||
{
|
||||
//TODO
|
||||
//disembark
|
||||
return ret;
|
||||
}
|
||||
else //use subterranean gates - not needed since gates are now handled via Pathfinder
|
||||
{
|
||||
return ret;
|
||||
//throw cannotFulfillGoalException("Land-land and water-water inter-sector transitions are not implemented!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return ret;
|
||||
//throw cannotFulfillGoalException("Inter-sector route detection failed: not connected sectors?");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return findFirstVisitableTile(h, dst);
|
||||
}
|
||||
}
|
||||
|
||||
int3 SectorMap::findFirstVisitableTile(HeroPtr h, crint3 dst)
|
||||
{
|
||||
int3 ret(-1, -1, -1);
|
||||
int3 curtile = dst;
|
||||
|
||||
while(curtile != h->visitablePos())
|
||||
{
|
||||
auto topObj = cb->getTopObj(curtile);
|
||||
if(topObj && topObj->ID == Obj::HERO && topObj != h.h)
|
||||
{
|
||||
if(cb->getPlayerRelations(h->tempOwner, topObj->tempOwner) != PlayerRelations::ENEMIES)
|
||||
{
|
||||
logAi->warn("Another allied hero stands in our way");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
if(ai->myCb->getPathsInfo(h.get())->getPathInfo(curtile)->reachable())
|
||||
{
|
||||
return curtile;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto i = parent.find(curtile);
|
||||
if(i != parent.end())
|
||||
{
|
||||
assert(curtile != i->second);
|
||||
curtile = i->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ret;
|
||||
//throw cannotFulfillGoalException("Unreachable tile in sector? Should not happen!");
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SectorMap::makeParentBFS(crint3 source)
|
||||
{
|
||||
parent.clear();
|
||||
|
||||
int mySector = retrieveTile(source);
|
||||
std::queue<int3> toVisit;
|
||||
toVisit.push(source);
|
||||
while(!toVisit.empty())
|
||||
{
|
||||
int3 curPos = toVisit.front();
|
||||
toVisit.pop();
|
||||
TSectorID & sec = retrieveTile(curPos);
|
||||
assert(sec == mySector); //consider only tiles from the same sector
|
||||
UNUSED(sec);
|
||||
|
||||
foreach_neighbour(curPos, [&](crint3 neighPos)
|
||||
{
|
||||
if(retrieveTile(neighPos) == mySector && !vstd::contains(parent, neighPos))
|
||||
{
|
||||
if(cb->canMoveBetween(curPos, neighPos))
|
||||
{
|
||||
toVisit.push(neighPos);
|
||||
parent[neighPos] = curPos;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
SectorMap::TSectorID & SectorMap::retrieveTile(crint3 pos)
|
||||
{
|
||||
return retrieveTileN(sector, pos);
|
||||
}
|
||||
|
||||
TerrainTile * SectorMap::getTile(crint3 pos) const
|
||||
{
|
||||
//out of bounds access should be handled by boost::multi_array
|
||||
//still we cached this array to avoid any checks
|
||||
return visibleTiles->operator[](pos.x)[pos.y][pos.z];
|
||||
}
|
||||
|
||||
std::vector<const CGObjectInstance *> SectorMap::getNearbyObjs(HeroPtr h, bool sectorsAround)
|
||||
{
|
||||
const Sector * heroSector = &infoOnSectors[retrieveTile(h->visitablePos())];
|
||||
if(sectorsAround)
|
||||
{
|
||||
std::vector<const CGObjectInstance *> ret;
|
||||
for(auto embarkPoint : heroSector->embarkmentPoints)
|
||||
{
|
||||
const Sector * embarkSector = &infoOnSectors[retrieveTile(embarkPoint)];
|
||||
range::copy(embarkSector->visitableObjs, std::back_inserter(ret));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
return heroSector->visitableObjs;
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "AIUtility.h"
|
||||
#include "SectorMap.h"
|
||||
#include "Goals.h"
|
||||
#include "../../lib/AI_Base.h"
|
||||
#include "../../CCallback.h"
|
||||
@ -70,62 +71,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
NOT_VISIBLE = 0,
|
||||
NOT_CHECKED = 1,
|
||||
NOT_AVAILABLE
|
||||
};
|
||||
|
||||
struct SectorMap
|
||||
{
|
||||
//a sector is set of tiles that would be mutually reachable if all visitable objs would be passable (incl monsters)
|
||||
struct Sector
|
||||
{
|
||||
int id;
|
||||
std::vector<int3> tiles;
|
||||
std::vector<int3> embarkmentPoints; //tiles of other sectors onto which we can (dis)embark
|
||||
std::vector<const CGObjectInstance *> visitableObjs;
|
||||
bool water; //all tiles of sector are land or water
|
||||
Sector()
|
||||
{
|
||||
id = -1;
|
||||
water = false;
|
||||
}
|
||||
};
|
||||
|
||||
typedef unsigned short TSectorID; //smaller than int to allow -1 value. Max number of sectors 65K should be enough for any proper map.
|
||||
typedef boost::multi_array<TSectorID, 3> TSectorArray;
|
||||
|
||||
bool valid; //some kind of lazy eval
|
||||
std::map<int3, int3> parent;
|
||||
TSectorArray sector;
|
||||
//std::vector<std::vector<std::vector<unsigned char>>> pathfinderSector;
|
||||
|
||||
std::map<int, Sector> infoOnSectors;
|
||||
std::shared_ptr<boost::multi_array<TerrainTile *, 3>> visibleTiles;
|
||||
|
||||
SectorMap();
|
||||
SectorMap(HeroPtr h);
|
||||
void update();
|
||||
void clear();
|
||||
void exploreNewSector(crint3 pos, int num, CCallback * cbp);
|
||||
void write(crstring fname);
|
||||
|
||||
bool markIfBlocked(TSectorID & sec, crint3 pos, const TerrainTile * t);
|
||||
bool markIfBlocked(TSectorID & sec, crint3 pos);
|
||||
TSectorID & retrieveTile(crint3 pos);
|
||||
TSectorID & retrieveTileN(TSectorArray & vectors, const int3 & pos);
|
||||
const TSectorID & retrieveTileN(const TSectorArray & vectors, const int3 & pos);
|
||||
TerrainTile * getTile(crint3 pos) const;
|
||||
std::vector<const CGObjectInstance *> getNearbyObjs(HeroPtr h, bool sectorsAround);
|
||||
|
||||
void makeParentBFS(crint3 source);
|
||||
|
||||
int3 firstTileToGet(HeroPtr h, crint3 dst); //if h wants to reach tile dst, which tile he should visit to clear the way?
|
||||
int3 findFirstVisitableTile(HeroPtr h, crint3 dst);
|
||||
};
|
||||
|
||||
class DLL_EXPORT VCAI : public CAdventureAI
|
||||
{
|
||||
public:
|
||||
@ -151,6 +96,7 @@ public:
|
||||
std::set<const CGObjectInstance *> alreadyVisited;
|
||||
std::set<const CGObjectInstance *> reservedObjs; //to be visited by specific hero
|
||||
|
||||
//TODO: move to separate PathHandler class?
|
||||
std::map<HeroPtr, std::shared_ptr<SectorMap>> cachedSectorMaps; //TODO: serialize? not necessary
|
||||
|
||||
AIStatus status;
|
||||
|
Loading…
Reference in New Issue
Block a user