2022-08-09 07:54:32 +02:00
|
|
|
/*
|
|
|
|
* Zone.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 "Zone.h"
|
|
|
|
#include "RmgMap.h"
|
|
|
|
#include "Functions.h"
|
|
|
|
#include "TileInfo.h"
|
|
|
|
#include "CMapGenerator.h"
|
|
|
|
#include "RmgPath.h"
|
|
|
|
|
2022-07-26 15:07:42 +02:00
|
|
|
VCMI_LIB_NAMESPACE_BEGIN
|
|
|
|
|
2022-08-09 07:54:32 +02:00
|
|
|
std::function<bool(const int3 &)> AREA_NO_FILTER = [](const int3 & t)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
2023-05-20 11:46:32 +02:00
|
|
|
Zone::Zone(RmgMap & map, CMapGenerator & generator, CRandomGenerator & r)
|
2023-05-07 07:48:12 +02:00
|
|
|
: finished(false)
|
|
|
|
, townType(ETownType::NEUTRAL)
|
2023-02-11 18:05:02 +02:00
|
|
|
, terrainType(ETerrainId::GRASS)
|
|
|
|
, map(map)
|
|
|
|
, generator(generator)
|
2022-08-09 07:54:32 +02:00
|
|
|
{
|
2023-05-20 11:46:32 +02:00
|
|
|
rand.setSeed(r.nextInt());
|
2022-08-09 07:54:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool Zone::isUnderground() const
|
|
|
|
{
|
|
|
|
return getPos().z;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Zone::setOptions(const ZoneOptions& options)
|
|
|
|
{
|
|
|
|
ZoneOptions::operator=(options);
|
|
|
|
}
|
|
|
|
|
|
|
|
float3 Zone::getCenter() const
|
|
|
|
{
|
|
|
|
return center;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Zone::setCenter(const float3 &f)
|
|
|
|
{
|
|
|
|
//limit boundaries to (0,1) square
|
|
|
|
|
|
|
|
//alternate solution - wrap zone around unitary square. If it doesn't fit on one side, will come out on the opposite side
|
|
|
|
center = f;
|
|
|
|
|
|
|
|
center.x = static_cast<float>(std::fmod(center.x, 1));
|
|
|
|
center.y = static_cast<float>(std::fmod(center.y, 1));
|
|
|
|
|
|
|
|
if(center.x < 0) //fmod seems to work only for positive numbers? we want to stay positive
|
|
|
|
center.x = 1 - std::abs(center.x);
|
|
|
|
if(center.y < 0)
|
|
|
|
center.y = 1 - std::abs(center.y);
|
|
|
|
}
|
|
|
|
|
|
|
|
int3 Zone::getPos() const
|
|
|
|
{
|
|
|
|
return pos;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Zone::setPos(const int3 &Pos)
|
|
|
|
{
|
|
|
|
pos = Pos;
|
|
|
|
}
|
|
|
|
|
|
|
|
const rmg::Area & Zone::getArea() const
|
|
|
|
{
|
|
|
|
return dArea;
|
|
|
|
}
|
|
|
|
|
|
|
|
rmg::Area & Zone::area()
|
|
|
|
{
|
|
|
|
return dArea;
|
|
|
|
}
|
|
|
|
|
|
|
|
rmg::Area & Zone::areaPossible()
|
|
|
|
{
|
2023-05-19 20:30:15 +02:00
|
|
|
//FIXME: make const, only modify via mutex-protected interface
|
2022-08-09 07:54:32 +02:00
|
|
|
return dAreaPossible;
|
|
|
|
}
|
|
|
|
|
|
|
|
rmg::Area & Zone::areaUsed()
|
|
|
|
{
|
|
|
|
return dAreaUsed;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Zone::clearTiles()
|
|
|
|
{
|
2023-05-19 20:30:15 +02:00
|
|
|
//Lock lock(mx);
|
2022-08-09 07:54:32 +02:00
|
|
|
dArea.clear();
|
|
|
|
dAreaPossible.clear();
|
|
|
|
dAreaFree.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Zone::initFreeTiles()
|
|
|
|
{
|
|
|
|
rmg::Tileset possibleTiles;
|
2023-05-19 20:30:15 +02:00
|
|
|
//Lock lock(mx);
|
2022-08-09 07:54:32 +02:00
|
|
|
vstd::copy_if(dArea.getTiles(), vstd::set_inserter(possibleTiles), [this](const int3 &tile) -> bool
|
|
|
|
{
|
|
|
|
return map.isPossible(tile);
|
|
|
|
});
|
|
|
|
dAreaPossible.assign(possibleTiles);
|
|
|
|
|
|
|
|
if(dAreaFree.empty())
|
|
|
|
{
|
|
|
|
dAreaPossible.erase(pos);
|
|
|
|
dAreaFree.add(pos); //zone must have at least one free tile where other paths go - for instance in the center
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
rmg::Area & Zone::freePaths()
|
|
|
|
{
|
|
|
|
return dAreaFree;
|
|
|
|
}
|
|
|
|
|
2023-04-09 17:26:32 +02:00
|
|
|
FactionID Zone::getTownType() const
|
2022-08-09 07:54:32 +02:00
|
|
|
{
|
2023-04-09 17:26:32 +02:00
|
|
|
return FactionID(townType);
|
2022-08-09 07:54:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Zone::setTownType(si32 town)
|
|
|
|
{
|
|
|
|
townType = town;
|
|
|
|
}
|
|
|
|
|
2022-09-29 11:44:46 +02:00
|
|
|
TerrainId Zone::getTerrainType() const
|
2022-08-09 07:54:32 +02:00
|
|
|
{
|
|
|
|
return terrainType;
|
|
|
|
}
|
|
|
|
|
2022-09-29 11:44:46 +02:00
|
|
|
void Zone::setTerrainType(TerrainId terrain)
|
2022-08-09 07:54:32 +02:00
|
|
|
{
|
|
|
|
terrainType = terrain;
|
|
|
|
}
|
|
|
|
|
2023-02-11 18:05:02 +02:00
|
|
|
rmg::Path Zone::searchPath(const rmg::Area & src, bool onlyStraight, const std::function<bool(const int3 &)> & areafilter) const
|
2022-08-09 07:54:32 +02:00
|
|
|
///connect current tile to any other free tile within zone
|
|
|
|
{
|
|
|
|
auto movementCost = [this](const int3 & s, const int3 & d)
|
|
|
|
{
|
|
|
|
if(map.isFree(d))
|
|
|
|
return 1;
|
|
|
|
else if (map.isPossible(d))
|
|
|
|
return 2;
|
|
|
|
return 3;
|
|
|
|
};
|
2023-05-07 07:48:12 +02:00
|
|
|
|
2022-08-09 07:54:32 +02:00
|
|
|
auto area = (dAreaPossible + dAreaFree).getSubarea(areafilter);
|
2023-02-11 18:05:02 +02:00
|
|
|
rmg::Path freePath(area);
|
|
|
|
rmg::Path resultPath(area);
|
2022-08-09 07:54:32 +02:00
|
|
|
freePath.connect(dAreaFree);
|
2023-05-07 07:48:12 +02:00
|
|
|
|
2022-08-09 07:54:32 +02:00
|
|
|
//connect to all pieces
|
2022-08-28 10:54:06 +02:00
|
|
|
auto goals = connectedAreas(src, onlyStraight);
|
2022-08-09 07:54:32 +02:00
|
|
|
for(auto & goal : goals)
|
|
|
|
{
|
|
|
|
auto path = freePath.search(goal, onlyStraight, movementCost);
|
|
|
|
if(path.getPathArea().empty())
|
|
|
|
return rmg::Path::invalid();
|
2023-05-07 07:48:12 +02:00
|
|
|
|
2022-08-09 07:54:32 +02:00
|
|
|
freePath.connect(path.getPathArea());
|
2022-08-09 14:12:58 +02:00
|
|
|
resultPath.connect(path.getPathArea());
|
2022-08-09 07:54:32 +02:00
|
|
|
}
|
2023-05-07 07:48:12 +02:00
|
|
|
|
2022-08-09 14:12:58 +02:00
|
|
|
return resultPath;
|
2022-08-09 07:54:32 +02:00
|
|
|
}
|
|
|
|
|
2023-02-11 18:05:02 +02:00
|
|
|
rmg::Path Zone::searchPath(const int3 & src, bool onlyStraight, const std::function<bool(const int3 &)> & areafilter) const
|
2022-08-09 07:54:32 +02:00
|
|
|
///connect current tile to any other free tile within zone
|
|
|
|
{
|
2023-05-07 07:48:12 +02:00
|
|
|
return searchPath(rmg::Area({ src }), onlyStraight, areafilter);
|
2023-05-06 18:54:19 +02:00
|
|
|
}
|
|
|
|
|
2023-05-07 07:48:12 +02:00
|
|
|
TModificators Zone::getModificators()
|
2023-05-06 18:54:19 +02:00
|
|
|
{
|
2023-05-07 07:48:12 +02:00
|
|
|
return modificators;
|
2023-05-06 18:54:19 +02:00
|
|
|
}
|
|
|
|
|
2022-08-09 07:54:32 +02:00
|
|
|
void Zone::connectPath(const rmg::Path & path)
|
|
|
|
///connect current tile to any other free tile within zone
|
|
|
|
{
|
|
|
|
dAreaPossible.subtract(path.getPathArea());
|
|
|
|
dAreaFree.unite(path.getPathArea());
|
2023-02-11 18:05:02 +02:00
|
|
|
for(const auto & t : path.getPathArea().getTilesVector())
|
2022-08-09 07:54:32 +02:00
|
|
|
map.setOccupied(t, ETileType::FREE);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Zone::fractalize()
|
|
|
|
{
|
|
|
|
rmg::Area clearedTiles(dAreaFree);
|
|
|
|
rmg::Area possibleTiles(dAreaPossible);
|
|
|
|
rmg::Area tilesToIgnore; //will be erased in this iteration
|
2022-09-22 10:49:55 +02:00
|
|
|
|
2023-06-10 14:57:25 +02:00
|
|
|
//Squared
|
|
|
|
float minDistance = 10 * 10;
|
|
|
|
float spanFactor = (pos.z ? 0.25 : 0.5f); //Narrower passages in the Underground
|
|
|
|
|
|
|
|
int treasureValue = 0;
|
|
|
|
int treasureDensity = 0;
|
|
|
|
for (auto t : treasureInfo)
|
|
|
|
{
|
|
|
|
treasureValue += ((t.min + t.max) / 2) * t.density / 1000.f; //Thousands
|
|
|
|
treasureDensity += t.density;
|
|
|
|
}
|
|
|
|
|
2023-06-10 18:02:26 +02:00
|
|
|
if (treasureValue > 200)
|
2023-06-10 14:57:25 +02:00
|
|
|
{
|
|
|
|
//Less obstacles - max span is 1 (no obstacles)
|
2023-06-10 18:02:26 +02:00
|
|
|
spanFactor = 1.0f - ((std::max(0, (1000 - treasureValue)) / (1000.f - 200)) * (1 - spanFactor));
|
2023-06-10 14:57:25 +02:00
|
|
|
}
|
|
|
|
else if (treasureValue < 100)
|
|
|
|
{
|
|
|
|
//Dense obstacles
|
|
|
|
spanFactor *= (treasureValue / 100.f);
|
|
|
|
vstd::amax(spanFactor, 0.2f);
|
|
|
|
}
|
|
|
|
if (treasureDensity <= 10)
|
|
|
|
{
|
|
|
|
vstd::amin(spanFactor, 0.25f); //Add extra obstacles to fill up space
|
|
|
|
}
|
|
|
|
float blockDistance = minDistance * spanFactor; //More obstacles in the Underground
|
2022-08-09 07:54:32 +02:00
|
|
|
|
|
|
|
if(type != ETemplateZoneType::JUNCTION)
|
|
|
|
{
|
|
|
|
//junction is not fractalized, has only one straight path
|
|
|
|
//everything else remains blocked
|
|
|
|
while(!possibleTiles.empty())
|
|
|
|
{
|
|
|
|
//link tiles in random order
|
|
|
|
std::vector<int3> tilesToMakePath = possibleTiles.getTilesVector();
|
2023-05-20 11:46:32 +02:00
|
|
|
RandomGeneratorUtil::randomShuffle(tilesToMakePath, getRand());
|
2022-08-09 07:54:32 +02:00
|
|
|
|
|
|
|
int3 nodeFound(-1, -1, -1);
|
2023-02-11 18:05:02 +02:00
|
|
|
|
|
|
|
for(const auto & tileToMakePath : tilesToMakePath)
|
2022-08-09 07:54:32 +02:00
|
|
|
{
|
|
|
|
//find closest free tile
|
|
|
|
int3 closestTile = clearedTiles.nearest(tileToMakePath);
|
|
|
|
if(closestTile.dist2dSQ(tileToMakePath) <= minDistance)
|
|
|
|
tilesToIgnore.add(tileToMakePath);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//if tiles are not close enough, make path to it
|
|
|
|
nodeFound = tileToMakePath;
|
|
|
|
clearedTiles.add(nodeFound); //from now on nearby tiles will be considered handled
|
|
|
|
break; //next iteration - use already cleared tiles
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
possibleTiles.subtract(tilesToIgnore);
|
|
|
|
if(!nodeFound.valid()) //nothing else can be done (?)
|
|
|
|
break;
|
|
|
|
tilesToIgnore.clear();
|
|
|
|
}
|
|
|
|
}
|
2023-05-19 20:30:15 +02:00
|
|
|
Lock lock(areaMutex);
|
2022-08-09 07:54:32 +02:00
|
|
|
//cut straight paths towards the center. A* is too slow for that.
|
2022-08-28 10:54:06 +02:00
|
|
|
auto areas = connectedAreas(clearedTiles, false);
|
2022-08-09 07:54:32 +02:00
|
|
|
for(auto & area : areas)
|
|
|
|
{
|
|
|
|
if(dAreaFree.overlap(area))
|
|
|
|
continue; //already found
|
|
|
|
|
|
|
|
auto availableArea = dAreaPossible + dAreaFree;
|
|
|
|
rmg::Path path(availableArea);
|
|
|
|
path.connect(dAreaFree);
|
|
|
|
auto res = path.search(area, false);
|
|
|
|
if(res.getPathArea().empty())
|
|
|
|
{
|
|
|
|
dAreaPossible.subtract(area);
|
|
|
|
dAreaFree.subtract(area);
|
2023-02-11 18:05:02 +02:00
|
|
|
for(const auto & t : area.getTiles())
|
2022-08-09 07:54:32 +02:00
|
|
|
map.setOccupied(t, ETileType::BLOCKED);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
dAreaPossible.subtract(res.getPathArea());
|
|
|
|
dAreaFree.unite(res.getPathArea());
|
2023-02-11 18:05:02 +02:00
|
|
|
for(const auto & t : res.getPathArea().getTiles())
|
2022-08-09 07:54:32 +02:00
|
|
|
map.setOccupied(t, ETileType::FREE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//now block most distant tiles away from passages
|
|
|
|
auto areaToBlock = dArea.getSubarea([this, blockDistance](const int3 & t)
|
|
|
|
{
|
2023-02-11 18:05:02 +02:00
|
|
|
auto distance = static_cast<float>(dAreaFree.distanceSqr(t));
|
2022-08-09 07:54:32 +02:00
|
|
|
return distance > blockDistance;
|
|
|
|
});
|
|
|
|
dAreaPossible.subtract(areaToBlock);
|
|
|
|
dAreaFree.subtract(areaToBlock);
|
2023-05-19 20:30:15 +02:00
|
|
|
|
|
|
|
lock.unlock();
|
2023-02-11 18:05:02 +02:00
|
|
|
for(const auto & t : areaToBlock.getTiles())
|
2022-08-09 07:54:32 +02:00
|
|
|
map.setOccupied(t, ETileType::BLOCKED);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Zone::initModificators()
|
|
|
|
{
|
|
|
|
for(auto & modificator : modificators)
|
|
|
|
{
|
|
|
|
modificator->init();
|
|
|
|
}
|
|
|
|
logGlobal->info("Zone %d modificators initialized", getId());
|
|
|
|
}
|
|
|
|
|
2023-05-20 11:46:32 +02:00
|
|
|
CRandomGenerator& Zone::getRand()
|
|
|
|
{
|
|
|
|
return rand;
|
|
|
|
}
|
|
|
|
|
2022-07-26 15:07:42 +02:00
|
|
|
VCMI_LIB_NAMESPACE_END
|