1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-06 09:09:40 +02:00

Merge branch 'develop' into rmg-split-enum-monster-strength

This commit is contained in:
Warzyw647
2023-05-31 19:53:19 +02:00
335 changed files with 12445 additions and 8462 deletions

View File

@@ -0,0 +1,346 @@
/*
* ConnectionsPlacer.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 "ConnectionsPlacer.h"
#include "../CMapGenerator.h"
#include "../RmgMap.h"
#include "../../TerrainHandler.h"
#include "../../mapping/CMap.h"
#include "../../mapping/CMapEditManager.h"
#include "../../mapObjects/CObjectClassesHandler.h"
#include "../RmgPath.h"
#include "../RmgObject.h"
#include "ObjectManager.h"
#include "../Functions.h"
#include "RoadPlacer.h"
#include "../TileInfo.h"
#include "WaterAdopter.h"
#include "WaterProxy.h"
#include "TownPlacer.h"
#include <boost/interprocess/sync/scoped_lock.hpp>
VCMI_LIB_NAMESPACE_BEGIN
void ConnectionsPlacer::process()
{
collectNeighbourZones();
auto diningPhilosophers = [this](std::function<void(const rmg::ZoneConnection&)> f)
{
for (auto& c : dConnections)
{
if (c.getZoneA() != zone.getId() && c.getZoneB() != zone.getId())
continue;
auto otherZone = map.getZones().at(c.getZoneB());
auto* cp = otherZone->getModificator<ConnectionsPlacer>();
while (cp)
{
RecursiveLock lock1(externalAccessMutex, boost::try_to_lock_t{});
RecursiveLock lock2(cp->externalAccessMutex, boost::try_to_lock_t{});
if (lock1.owns_lock() && lock2.owns_lock())
{
if (!vstd::contains(dCompleted, c))
{
f(c);
}
break;
}
}
}
};
diningPhilosophers([this](const rmg::ZoneConnection& c)
{
selfSideDirectConnection(c);
});
createBorder();
diningPhilosophers([this](const rmg::ZoneConnection& c)
{
selfSideIndirectConnection(c);
});
}
void ConnectionsPlacer::init()
{
DEPENDENCY(WaterAdopter);
DEPENDENCY(TownPlacer);
POSTFUNCTION(RoadPlacer);
POSTFUNCTION(ObjectManager);
for(auto c : map.getMapGenOptions().getMapTemplate()->getConnections())
addConnection(c);
}
void ConnectionsPlacer::addConnection(const rmg::ZoneConnection& connection)
{
dConnections.push_back(connection);
}
void ConnectionsPlacer::otherSideConnection(const rmg::ZoneConnection & connection)
{
dCompleted.push_back(connection);
}
void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & connection)
{
bool success = false;
auto otherZoneId = (connection.getZoneA() == zone.getId() ? connection.getZoneB() : connection.getZoneA());
auto & otherZone = map.getZones().at(otherZoneId);
//1. Try to make direct connection
//Do if it's not prohibited by terrain settings
const auto * ourTerrain = VLC->terrainTypeHandler->getById(zone.getTerrainType());
const auto * otherTerrain = VLC->terrainTypeHandler->getById(otherZone->getTerrainType());
bool directProhibited = vstd::contains(ourTerrain->prohibitTransitions, otherZone->getTerrainType())
|| vstd::contains(otherTerrain->prohibitTransitions, zone.getTerrainType());
auto directConnectionIterator = dNeighbourZones.find(otherZoneId);
if(!directProhibited && directConnectionIterator != dNeighbourZones.end())
{
int3 guardPos(-1, -1, -1);
int3 borderPos;
while(!directConnectionIterator->second.empty())
{
borderPos = *RandomGeneratorUtil::nextItem(directConnectionIterator->second, zone.getRand());
guardPos = zone.areaPossible().nearest(borderPos);
assert(borderPos != guardPos);
float dist = map.getTileInfo(guardPos).getNearestObjectDistance();
if (dist >= 3) //Don't place guards at adjacent tiles
{
auto safetyGap = rmg::Area({ guardPos });
safetyGap.unite(safetyGap.getBorderOutside());
safetyGap.intersect(zone.areaPossible());
if (!safetyGap.empty())
{
safetyGap.intersect(otherZone->areaPossible());
if (safetyGap.empty())
break; //successfull position
}
}
//failed position
directConnectionIterator->second.erase(borderPos);
guardPos = int3(-1, -1, -1);
}
if(guardPos.valid())
{
assert(zone.getModificator<ObjectManager>());
auto & manager = *zone.getModificator<ObjectManager>();
auto * monsterType = manager.chooseGuard(connection.getGuardStrength(), true);
rmg::Area border(zone.getArea().getBorder());
border.unite(otherZone->getArea().getBorder());
auto costFunction = [&border](const int3 & s, const int3 & d)
{
return 1.f / (1.f + border.distanceSqr(d));
};
auto ourArea = zone.areaPossible() + zone.freePaths();
auto theirArea = otherZone->areaPossible() + otherZone->freePaths();
theirArea.add(guardPos);
rmg::Path ourPath(ourArea);
rmg::Path theirPath(theirArea);
ourPath.connect(zone.freePaths());
ourPath = ourPath.search(guardPos, true, costFunction);
theirPath.connect(otherZone->freePaths());
theirPath = theirPath.search(guardPos, true, costFunction);
if(ourPath.valid() && theirPath.valid())
{
zone.connectPath(ourPath);
otherZone->connectPath(theirPath);
if(monsterType)
{
rmg::Object monster(*monsterType);
monster.setPosition(guardPos);
manager.placeObject(monster, false, true);
//Place objects away from the monster in the other zone, too
otherZone->getModificator<ObjectManager>()->updateDistances(monster);
}
else
{
zone.areaPossible().erase(guardPos);
zone.freePaths().add(guardPos);
map.setOccupied(guardPos, ETileType::FREE);
}
assert(zone.getModificator<RoadPlacer>());
zone.getModificator<RoadPlacer>()->addRoadNode(guardPos);
assert(otherZone->getModificator<RoadPlacer>());
otherZone->getModificator<RoadPlacer>()->addRoadNode(borderPos);
assert(otherZone->getModificator<ConnectionsPlacer>());
otherZone->getModificator<ConnectionsPlacer>()->otherSideConnection(connection);
success = true;
}
}
}
//2. connect via water
bool waterMode = map.getMapGenOptions().getWaterContent() != EWaterContent::NONE;
if(waterMode && zone.isUnderground() == otherZone->isUnderground())
{
if(generator.getZoneWater() && generator.getZoneWater()->getModificator<WaterProxy>())
{
if(generator.getZoneWater()->getModificator<WaterProxy>()->waterKeepConnection(connection.getZoneA(), connection.getZoneB()))
{
assert(otherZone->getModificator<ConnectionsPlacer>());
otherZone->getModificator<ConnectionsPlacer>()->otherSideConnection(connection);
success = true;
}
}
}
if(success)
dCompleted.push_back(connection);
}
void ConnectionsPlacer::selfSideIndirectConnection(const rmg::ZoneConnection & connection)
{
bool success = false;
auto otherZoneId = (connection.getZoneA() == zone.getId() ? connection.getZoneB() : connection.getZoneA());
auto & otherZone = map.getZones().at(otherZoneId);
//3. place subterrain gates
if(zone.isUnderground() != otherZone->isUnderground())
{
int3 zShift(0, 0, zone.getPos().z - otherZone->getPos().z);
auto commonArea = zone.areaPossible() * (otherZone->areaPossible() + zShift);
if(!commonArea.empty())
{
assert(zone.getModificator<ObjectManager>());
auto & manager = *zone.getModificator<ObjectManager>();
assert(otherZone->getModificator<ObjectManager>());
auto & managerOther = *otherZone->getModificator<ObjectManager>();
auto factory = VLC->objtypeh->getHandlerFor(Obj::SUBTERRANEAN_GATE, 0);
auto * gate1 = factory->create();
auto * gate2 = factory->create();
rmg::Object rmgGate1(*gate1);
rmg::Object rmgGate2(*gate2);
rmgGate1.setTemplate(zone.getTerrainType());
rmgGate2.setTemplate(otherZone->getTerrainType());
bool guarded1 = manager.addGuard(rmgGate1, connection.getGuardStrength(), true);
bool guarded2 = managerOther.addGuard(rmgGate2, connection.getGuardStrength(), true);
int minDist = 3;
std::scoped_lock doubleLock(zone.areaMutex, otherZone->areaMutex);
rmg::Path path2(otherZone->area());
rmg::Path path1 = manager.placeAndConnectObject(commonArea, rmgGate1, [this, minDist, &path2, &rmgGate1, &zShift, guarded2, &managerOther, &rmgGate2 ](const int3 & tile)
{
auto ti = map.getTileInfo(tile);
auto otherTi = map.getTileInfo(tile - zShift);
float dist = ti.getNearestObjectDistance();
float otherDist = otherTi.getNearestObjectDistance();
if(dist < minDist || otherDist < minDist)
return -1.f;
rmg::Area toPlace(rmgGate1.getArea() + rmgGate1.getAccessibleArea());
toPlace.translate(-zShift);
path2 = managerOther.placeAndConnectObject(toPlace, rmgGate2, minDist, guarded2, true, ObjectManager::OptimizeType::NONE);
return path2.valid() ? (dist * otherDist) : -1.f;
}, guarded1, true, ObjectManager::OptimizeType::DISTANCE);
if(path1.valid() && path2.valid())
{
zone.connectPath(path1);
otherZone->connectPath(path2);
manager.placeObject(rmgGate1, guarded1, true);
managerOther.placeObject(rmgGate2, guarded2, true);
assert(otherZone->getModificator<ConnectionsPlacer>());
otherZone->getModificator<ConnectionsPlacer>()->otherSideConnection(connection);
success = true;
}
}
}
//4. place monoliths/portals
if(!success)
{
auto factory = VLC->objtypeh->getHandlerFor(Obj::MONOLITH_TWO_WAY, generator.getNextMonlithIndex());
auto * teleport1 = factory->create();
auto * teleport2 = factory->create();
zone.getModificator<ObjectManager>()->addRequiredObject(teleport1, connection.getGuardStrength());
otherZone->getModificator<ObjectManager>()->addRequiredObject(teleport2, connection.getGuardStrength());
assert(otherZone->getModificator<ConnectionsPlacer>());
otherZone->getModificator<ConnectionsPlacer>()->otherSideConnection(connection);
success = true;
}
if(success)
dCompleted.push_back(connection);
}
void ConnectionsPlacer::collectNeighbourZones()
{
auto border = zone.area().getBorderOutside();
for(const auto & i : border)
{
if(!map.isOnMap(i))
continue;
auto zid = map.getZoneID(i);
assert(zid != zone.getId());
dNeighbourZones[zid].insert(i);
}
}
void ConnectionsPlacer::createBorder()
{
rmg::Area borderArea(zone.getArea().getBorder());
rmg::Area borderOutsideArea(zone.getArea().getBorderOutside());
auto blockBorder = borderArea.getSubarea([this, &borderOutsideArea](const int3 & t)
{
auto tile = borderOutsideArea.nearest(t);
return map.isOnMap(tile) && map.getZones()[map.getZoneID(tile)]->getType() != ETemplateZoneType::WATER;
});
Zone::Lock lock(zone.areaMutex); //Protect from erasing same tiles again
for(const auto & tile : blockBorder.getTilesVector())
{
if(map.isPossible(tile))
{
map.setOccupied(tile, ETileType::BLOCKED);
zone.areaPossible().erase(tile);
}
map.foreachDirectNeighbour(tile, [this](int3 &nearbyPos)
{
if(map.isPossible(nearbyPos) && map.getZoneID(nearbyPos) == zone.getId())
{
map.setOccupied(nearbyPos, ETileType::BLOCKED);
zone.areaPossible().erase(nearbyPos);
}
});
}
}
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,40 @@
/*
* ConnectionsPlacer.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 "../Zone.h"
#include "../RmgArea.h"
VCMI_LIB_NAMESPACE_BEGIN
class ConnectionsPlacer: public Modificator
{
public:
MODIFICATOR(ConnectionsPlacer);
void process() override;
void init() override;
void addConnection(const rmg::ZoneConnection& connection);
void selfSideDirectConnection(const rmg::ZoneConnection & connection);
void selfSideIndirectConnection(const rmg::ZoneConnection & connection);
void otherSideConnection(const rmg::ZoneConnection & connection);
void createBorder();
protected:
void collectNeighbourZones();
protected:
std::vector<rmg::ZoneConnection> dConnections, dCompleted;
std::map<TRmgTemplateZoneId, rmg::Tileset> dNeighbourZones;
};
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,98 @@
/*
* 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 "MinePlacer.h"
#include "TownPlacer.h"
#include "ConnectionsPlacer.h"
#include "../CMapGenerator.h"
#include "../RmgMap.h"
#include "../../mapping/CMap.h"
#include "../../mapping/CMapEditManager.h"
#include "../../mapObjects/CObjectClassesHandler.h"
#include "../../spells/CSpellHandler.h" //for choosing random spells
#include "../RmgPath.h"
#include "../RmgObject.h"
#include "ObjectManager.h"
#include "../Functions.h"
#include "RoadPlacer.h"
#include "WaterAdopter.h"
#include "../TileInfo.h"
VCMI_LIB_NAMESPACE_BEGIN
void MinePlacer::process()
{
auto * manager = zone.getModificator<ObjectManager>();
if(!manager)
{
logGlobal->error("ObjectManager doesn't exist for zone %d, skip modificator %s", zone.getId(), getName());
return;
}
placeMines(*manager);
}
void MinePlacer::init()
{
DEPENDENCY(TownPlacer);
DEPENDENCY(ConnectionsPlacer);
POSTFUNCTION(ObjectManager);
POSTFUNCTION(RoadPlacer);
}
bool MinePlacer::placeMines(ObjectManager & manager)
{
std::vector<CGMine*> createdMines;
std::vector<std::pair<CGObjectInstance*, ui32>> requiredObjects;
for(const auto & mineInfo : zone.getMinesInfo())
{
const auto res = GameResID(mineInfo.first);
for(int i = 0; i < mineInfo.second; ++i)
{
auto mineHandler = VLC->objtypeh->getHandlerFor(Obj::MINE, res);
const auto & rmginfo = mineHandler->getRMGInfo();
auto * mine = dynamic_cast<CGMine *>(mineHandler->create());
mine->producedResource = res;
mine->tempOwner = PlayerColor::NEUTRAL;
mine->producedQuantity = mine->defaultResProduction();
createdMines.push_back(mine);
if(!i && (res == EGameResID::WOOD || res == EGameResID::ORE))
manager.addCloseObject(mine, rmginfo.value); //only first wood&ore mines are close
else
requiredObjects.push_back(std::pair<CGObjectInstance*, ui32>(mine, rmginfo.value));
}
}
//Shuffle mines to avoid patterns, but don't shuffle key objects like towns
RandomGeneratorUtil::randomShuffle(requiredObjects, zone.getRand());
for (const auto& obj : requiredObjects)
{
manager.addRequiredObject(obj.first, obj.second);
}
//create extra resources
if(int extraRes = generator.getConfig().mineExtraResources)
{
for(auto * mine : createdMines)
{
for(int rc = zone.getRand().nextInt(1, extraRes); rc > 0; --rc)
{
auto * resourse = dynamic_cast<CGResource *>(VLC->objtypeh->getHandlerFor(Obj::RESOURCE, mine->producedResource)->create());
resourse->amount = CGResource::RANDOM_AMOUNT;
manager.addNearbyObject(resourse, mine);
}
}
}
return true;
}
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,30 @@
/*
* MinePlacer.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 "../Zone.h"
VCMI_LIB_NAMESPACE_BEGIN
class ObjectManager;
class MinePlacer: public Modificator
{
public:
MODIFICATOR(MinePlacer);
void process() override;
void init() override;
protected:
bool placeMines(ObjectManager & manager);
};
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,159 @@
/*
* Modificator.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 "Modificator.h"
#include "../Functions.h"
#include "../CMapGenerator.h"
#include "../RmgMap.h"
#include "../../CStopWatch.h"
#include "../../mapping/CMap.h"
VCMI_LIB_NAMESPACE_BEGIN
Modificator::Modificator(Zone & zone, RmgMap & map, CMapGenerator & generator) : zone(zone), map(map), generator(generator)
{
mapProxy = map.getMapProxy();
}
void Modificator::setName(const std::string & n)
{
name = n;
}
const std::string & Modificator::getName() const
{
return name;
}
bool Modificator::isReady()
{
Lock lock(mx, boost::try_to_lock_t{});
if (!lock.owns_lock())
{
return false;
}
else
{
//Check prerequisites
for (auto it = preceeders.begin(); it != preceeders.end();)
{
if ((*it)->isFinished()) //OK
{
//This preceeder won't be checked in the future
it = preceeders.erase(it);
}
else
{
return false;
}
}
//If a job is finished, it should be already erased from a queue
return !finished;
}
}
bool Modificator::isFinished()
{
Lock lock(mx, boost::try_to_lock_t{});
if (!lock.owns_lock())
{
return false;
}
else
{
return finished;
}
}
void Modificator::run()
{
Lock lock(mx);
if(!finished)
{
logGlobal->info("Modificator zone %d - %s - started", zone.getId(), getName());
CStopWatch processTime;
try
{
process();
}
catch(rmgException &e)
{
logGlobal->error("Modificator %s, exception: %s", getName(), e.what());
}
#ifdef RMG_DUMP
dump();
#endif
finished = true;
logGlobal->info("Modificator zone %d - %s - done (%d ms)", zone.getId(), getName(), processTime.getDiff());
}
}
void Modificator::dependency(Modificator * modificator)
{
if(modificator && modificator != this)
{
//TODO: use vstd::contains
if(std::find(preceeders.begin(), preceeders.end(), modificator) == preceeders.end())
preceeders.push_back(modificator);
}
}
void Modificator::postfunction(Modificator * modificator)
{
if(modificator && modificator != this)
{
if(std::find(modificator->preceeders.begin(), modificator->preceeders.end(), this) == modificator->preceeders.end())
modificator->preceeders.push_back(this);
}
}
void Modificator::dump()
{
std::ofstream out(boost::to_string(boost::format("seed_%d_modzone_%d_%s.txt") % generator.getRandomSeed() % zone.getId() % getName()));
int levels = map.levels();
int width = map.width();
int height = map.height();
for(int z = 0; z < levels; z++)
{
for(int j=0; j<height; j++)
{
for(int i=0; i<width; i++)
{
out << dump(int3(i, j, z));
}
out << std::endl;
}
out << std::endl;
}
out << std::endl;
}
char Modificator::dump(const int3 & t)
{
if(zone.freePaths().contains(t))
return '.'; //free path
if(zone.areaPossible().contains(t))
return ' '; //possible
if(zone.areaUsed().contains(t))
return 'U'; //used
if(zone.area().contains(t))
{
if(map.shouldBeBlocked(t))
return '#'; //obstacle
else
return '^'; //visitable points?
}
return '?';
}
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,81 @@
/*
* Modificator.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 "../../GameConstants.h"
#include "../../int3.h"
#include "../Zone.h"
#include "../threadpool/MapProxy.h"
class RmgMap;
class CMapGenerator;
class Zone;
class MapProxy;
#define MODIFICATOR(x) x(Zone & z, RmgMap & m, CMapGenerator & g): Modificator(z, m, g) {setName(#x);}
#define DEPENDENCY(x) dependency(zone.getModificator<x>());
#define POSTFUNCTION(x) postfunction(zone.getModificator<x>());
#define DEPENDENCY_ALL(x) for(auto & z : map.getZones()) \
{ \
dependency(z.second->getModificator<x>()); \
}
#define POSTFUNCTION_ALL(x) for(auto & z : map.getZones()) \
{ \
postfunction(z.second->getModificator<x>()); \
}
VCMI_LIB_NAMESPACE_BEGIN
class Modificator
{
public:
Modificator() = delete;
Modificator(Zone & zone, RmgMap & map, CMapGenerator & generator);
virtual void init() {/*override to add dependencies*/}
virtual char dump(const int3 &);
virtual ~Modificator() = default;
void setName(const std::string & n);
const std::string & getName() const;
bool isReady();
bool isFinished();
void run();
void dependency(Modificator * modificator);
void postfunction(Modificator * modificator);
protected:
RmgMap & map;
std::shared_ptr<MapProxy> mapProxy;
CMapGenerator & generator;
Zone & zone;
bool finished = false;
mutable boost::recursive_mutex externalAccessMutex; //Used to communicate between Modificators
using RecursiveLock = boost::unique_lock<boost::recursive_mutex>;
using Lock = boost::unique_lock<boost::shared_mutex>;
private:
virtual void process() = 0;
std::string name;
std::list<Modificator*> preceeders; //must be ordered container
mutable boost::shared_mutex mx; //Used only for task scheduling
void dump();
};
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,171 @@
/*
* ObjectDistributor.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 "ObjectDistributor.h"
#include "../../VCMI_Lib.h"
#include "../RmgMap.h"
#include "../CMapGenerator.h"
#include "TreasurePlacer.h"
#include "QuestArtifactPlacer.h"
#include "TownPlacer.h"
#include "TerrainPainter.h"
#include "../../mapObjects/CObjectClassesHandler.h"
#include "../../mapObjects/MapObjects.h"
#include "../Functions.h"
#include "../RmgObject.h"
VCMI_LIB_NAMESPACE_BEGIN
void ObjectDistributor::process()
{
distributeLimitedObjects();
distributeSeerHuts();
}
void ObjectDistributor::init()
{
//All of the terrain types need to be determined
DEPENDENCY_ALL(TerrainPainter);
POSTFUNCTION(TreasurePlacer);
}
void ObjectDistributor::distributeLimitedObjects()
{
//FIXME: Must be called after TerrainPainter::process()
ObjectInfo oi;
auto zones = map.getZones();
for (auto primaryID : VLC->objtypeh->knownObjects())
{
for (auto secondaryID : VLC->objtypeh->knownSubObjects(primaryID))
{
auto handler = VLC->objtypeh->getHandlerFor(primaryID, secondaryID);
if (!handler->isStaticObject() && handler->getRMGInfo().value)
{
auto rmgInfo = handler->getRMGInfo();
//Skip objects which don't have global per-map limit here
if (rmgInfo.mapLimit)
{
//Count all zones where this object can be placed
std::vector<std::shared_ptr<Zone>> matchingZones;
for (const auto& it : zones)
{
if (!handler->getTemplates(it.second->getTerrainType()).empty() &&
rmgInfo.value <= it.second->getMaxTreasureValue())
{
matchingZones.push_back(it.second);
}
}
size_t numZones = matchingZones.size();
if (!numZones)
continue;
auto rmgInfo = handler->getRMGInfo();
for (auto& zone : matchingZones)
{
//We already know there are some templates
auto templates = handler->getTemplates(zone->getTerrainType());
//FIXME: Templates empty?! Maybe zone changed terrain type over time?
//Assume the template with fewest terrains is the most suitable
auto temp = *boost::min_element(templates, [](std::shared_ptr<const ObjectTemplate> lhs, std::shared_ptr<const ObjectTemplate> rhs) -> bool
{
return lhs->getAllowedTerrains().size() < rhs->getAllowedTerrains().size();
});
oi.generateObject = [temp]() -> CGObjectInstance *
{
return VLC->objtypeh->getHandlerFor(temp->id, temp->subid)->create(temp);
};
oi.value = rmgInfo.value;
oi.probability = rmgInfo.rarity;
oi.templ = temp;
//Rounding up will make sure all possible objects are exhausted
uint32_t mapLimit = rmgInfo.mapLimit.value();
uint32_t maxPerZone = std::ceil(float(mapLimit) / numZones);
//But not more than zone limit
oi.maxPerZone = std::min(maxPerZone, rmgInfo.zoneLimit);
numZones--;
rmgInfo.setMapLimit(mapLimit - oi.maxPerZone);
//Don't add objects with 0 count remaining
if (oi.maxPerZone)
{
zone->getModificator<TreasurePlacer>()->addObjectToRandomPool(oi);
}
}
}
}
}
}
}
void ObjectDistributor::distributeSeerHuts()
{
//TODO: Move typedef outside the class?
//Copy by value to random shuffle
const auto & zoneMap = map.getZones();
RmgMap::ZoneVector zones(zoneMap.begin(), zoneMap.end());
RandomGeneratorUtil::randomShuffle(zones, zone.getRand());
const auto & possibleQuestArts = generator.getAllPossibleQuestArtifacts();
size_t availableArts = possibleQuestArts.size();
auto artIt = possibleQuestArts.begin();
for (int i = zones.size() - 1; i >= 0 ; i--)
{
size_t localArts = std::ceil((float)availableArts / (i + 1));
availableArts -= localArts;
auto * qap = zones[i].second->getModificator<QuestArtifactPlacer>();
if (qap)
{
for (;localArts > 0 && artIt != possibleQuestArts.end(); artIt++, localArts--)
{
qap->addRandomArtifact(*artIt);
}
}
}
}
void ObjectDistributor::distributePrisons()
{
//Copy by value to random shuffle
const auto & zoneMap = map.getZones();
RmgMap::ZoneVector zones(zoneMap.begin(), zoneMap.end());
RandomGeneratorUtil::randomShuffle(zones, zone.getRand());
size_t allowedPrisons = generator.getPrisonsRemaning();
for (int i = zones.size() - 1; i >= 0; i--)
{
auto zone = zones[i].second;
auto * tp = zone->getModificator<TreasurePlacer>();
if (tp)
{
tp->setMaxPrisons(std::ceil(float(allowedPrisons) / (i + 1)));
allowedPrisons -= tp->getMaxPrisons();
}
}
}
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,34 @@
/*
* ObjectDistributor.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 "../Zone.h"
#include "../RmgObject.h"
VCMI_LIB_NAMESPACE_BEGIN
class CGObjectInstance;
class ObjectTemplate;
class ObjectDistributor : public Modificator
{
void distributeLimitedObjects();
void distributeSeerHuts();
void distributePrisons();
public:
MODIFICATOR(ObjectDistributor);
void process() override;
void init() override;
};
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,545 @@
/*
* ObjectManager.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 "ObjectManager.h"
#include "../CMapGenerator.h"
#include "../TileInfo.h"
#include "../RmgMap.h"
#include "RoadPlacer.h"
#include "RiverPlacer.h"
#include "WaterAdopter.h"
#include "ConnectionsPlacer.h"
#include "TownPlacer.h"
#include "MinePlacer.h"
#include "TreasurePlacer.h"
#include "QuestArtifactPlacer.h"
#include "../../CCreatureHandler.h"
#include "../../mapObjects/CommonConstructors.h"
#include "../../mapObjects/MapObjects.h" //needed to resolve templates for CommonConstructors.h
#include "../../mapping/CMap.h"
#include "../../mapping/CMapEditManager.h"
#include "../Functions.h"
#include "../RmgObject.h"
VCMI_LIB_NAMESPACE_BEGIN
void ObjectManager::process()
{
zone.fractalize();
createRequiredObjects();
}
void ObjectManager::init()
{
DEPENDENCY(WaterAdopter);
DEPENDENCY_ALL(ConnectionsPlacer); //Monoliths can be placed by other zone, too
DEPENDENCY(TownPlacer); //Only secondary towns
DEPENDENCY(MinePlacer);
POSTFUNCTION(RoadPlacer);
createDistancesPriorityQueue();
}
void ObjectManager::createDistancesPriorityQueue()
{
RecursiveLock lock(externalAccessMutex);
tilesByDistance.clear();
for(const auto & tile : zone.areaPossible().getTilesVector())
{
tilesByDistance.push(std::make_pair(tile, map.getNearestObjectDistance(tile)));
}
}
void ObjectManager::addRequiredObject(CGObjectInstance * obj, si32 strength)
{
RecursiveLock lock(externalAccessMutex);
requiredObjects.emplace_back(obj, strength);
}
void ObjectManager::addCloseObject(CGObjectInstance * obj, si32 strength)
{
RecursiveLock lock(externalAccessMutex);
closeObjects.emplace_back(obj, strength);
}
void ObjectManager::addNearbyObject(CGObjectInstance * obj, CGObjectInstance * nearbyTarget)
{
RecursiveLock lock(externalAccessMutex);
nearbyObjects.emplace_back(obj, nearbyTarget);
}
void ObjectManager::updateDistances(const rmg::Object & obj)
{
RecursiveLock lock(externalAccessMutex);
tilesByDistance.clear();
for (auto tile : zone.areaPossible().getTiles()) //don't need to mark distance for not possible tiles
{
ui32 d = obj.getArea().distanceSqr(tile); //optimization, only relative distance is interesting
map.setNearestObjectDistance(tile, std::min(static_cast<float>(d), map.getNearestObjectDistance(tile)));
tilesByDistance.push(std::make_pair(tile, map.getNearestObjectDistance(tile)));
}
}
const rmg::Area & ObjectManager::getVisitableArea() const
{
RecursiveLock lock(externalAccessMutex);
return objectsVisitableArea;
}
std::vector<CGObjectInstance*> ObjectManager::getMines() const
{
std::vector<CGObjectInstance*> mines;
RecursiveLock lock(externalAccessMutex);
for(auto * object : objects)
{
if (object->ID == Obj::MINE)
{
mines.push_back(object);
}
}
return mines;
}
int3 ObjectManager::findPlaceForObject(const rmg::Area & searchArea, rmg::Object & obj, const std::function<float(const int3)> & weightFunction, OptimizeType optimizer) const
{
float bestWeight = 0.f;
int3 result(-1, -1, -1);
if(optimizer & OptimizeType::DISTANCE)
{
auto open = tilesByDistance;
while(!open.empty())
{
auto node = open.top();
open.pop();
int3 tile = node.first;
if(!searchArea.contains(tile))
continue;
obj.setPosition(tile);
if (obj.getVisibleTop().y < 0)
continue;
if(!searchArea.contains(obj.getArea()) || !searchArea.overlap(obj.getAccessibleArea()))
continue;
float weight = weightFunction(tile);
if(weight > bestWeight)
{
bestWeight = weight;
result = tile;
if(!(optimizer & OptimizeType::WEIGHT))
break;
}
}
}
else
{
for(const auto & tile : searchArea.getTiles())
{
obj.setPosition(tile);
if (obj.getVisibleTop().y < 0)
continue;
if(!searchArea.contains(obj.getArea()) || !searchArea.overlap(obj.getAccessibleArea()))
continue;
float weight = weightFunction(tile);
if(weight > bestWeight)
{
bestWeight = weight;
result = tile;
if(!(optimizer & OptimizeType::WEIGHT))
break;
}
}
}
//FIXME: Race condition for tiles? For Area?
if(result.valid())
obj.setPosition(result);
return result;
}
int3 ObjectManager::findPlaceForObject(const rmg::Area & searchArea, rmg::Object & obj, si32 min_dist, OptimizeType optimizer) const
{
return findPlaceForObject(searchArea, obj, [this, min_dist, &obj](const int3 & tile)
{
auto ti = map.getTileInfo(tile);
float dist = ti.getNearestObjectDistance();
if(dist < min_dist)
return -1.f;
for(const auto & t : obj.getArea().getTilesVector())
{
if(map.getTileInfo(t).getNearestObjectDistance() < min_dist)
return -1.f;
}
return dist;
}, optimizer);
}
rmg::Path ObjectManager::placeAndConnectObject(const rmg::Area & searchArea, rmg::Object & obj, si32 min_dist, bool isGuarded, bool onlyStraight, OptimizeType optimizer) const
{
return placeAndConnectObject(searchArea, obj, [this, min_dist, &obj](const int3 & tile)
{
auto ti = map.getTileInfo(tile);
float dist = ti.getNearestObjectDistance();
if(dist < min_dist)
return -1.f;
for(const auto & t : obj.getArea().getTilesVector())
{
if(map.getTileInfo(t).getNearestObjectDistance() < min_dist)
return -1.f;
}
return dist;
}, isGuarded, onlyStraight, optimizer);
}
rmg::Path ObjectManager::placeAndConnectObject(const rmg::Area & searchArea, rmg::Object & obj, const std::function<float(const int3)> & weightFunction, bool isGuarded, bool onlyStraight, OptimizeType optimizer) const
{
int3 pos;
auto possibleArea = searchArea;
while(true)
{
pos = findPlaceForObject(possibleArea, obj, weightFunction, optimizer);
if(!pos.valid())
{
return rmg::Path::invalid();
}
possibleArea.erase(pos); //do not place again at this point
auto accessibleArea = obj.getAccessibleArea(isGuarded) * (zone.areaPossible() + zone.freePaths());
//we should exclude tiles which will be covered
if(isGuarded)
{
const auto & guardedArea = obj.instances().back()->getAccessibleArea();
accessibleArea.intersect(guardedArea);
accessibleArea.add(obj.instances().back()->getPosition(true));
}
auto path = zone.searchPath(accessibleArea, onlyStraight, [&obj, isGuarded](const int3 & t)
{
if(isGuarded)
{
const auto & guardedArea = obj.instances().back()->getAccessibleArea();
const auto & unguardedArea = obj.getAccessibleArea(isGuarded);
if(unguardedArea.contains(t) && !guardedArea.contains(t))
return false;
//guard position is always target
if(obj.instances().back()->getPosition(true) == t)
return true;
}
return !obj.getArea().contains(t);
});
if(path.valid())
{
return path;
}
}
}
bool ObjectManager::createRequiredObjects()
{
logGlobal->trace("Creating required objects");
//RecursiveLock lock(externalAccessMutex); //Why could requiredObjects be modified during the loop?
for(const auto & object : requiredObjects)
{
auto * obj = object.first;
//FIXME: Invalid dObject inside object?
rmg::Object rmgObject(*obj);
rmgObject.setTemplate(zone.getTerrainType());
bool guarded = addGuard(rmgObject, object.second, (obj->ID == Obj::MONOLITH_TWO_WAY));
Zone::Lock lock(zone.areaMutex);
auto path = placeAndConnectObject(zone.areaPossible(), rmgObject, 3, guarded, false, OptimizeType::DISTANCE);
if(!path.valid())
{
logGlobal->error("Failed to fill zone %d due to lack of space", zone.getId());
return false;
}
zone.connectPath(path);
placeObject(rmgObject, guarded, true);
for(const auto & nearby : nearbyObjects)
{
if(nearby.second != obj)
continue;
rmg::Object rmgNearObject(*nearby.first);
rmg::Area possibleArea(rmgObject.instances().front()->getBlockedArea().getBorderOutside());
possibleArea.intersect(zone.areaPossible());
if(possibleArea.empty())
{
rmgNearObject.clear();
continue;
}
rmgNearObject.setPosition(*RandomGeneratorUtil::nextItem(possibleArea.getTiles(), zone.getRand()));
placeObject(rmgNearObject, false, false);
}
}
for(const auto & object : closeObjects)
{
auto * obj = object.first;
//TODO: Wrap into same area proxy?
Zone::Lock lock(zone.areaMutex);
auto possibleArea = zone.areaPossible();
rmg::Object rmgObject(*obj);
rmgObject.setTemplate(zone.getTerrainType());
bool guarded = addGuard(rmgObject, object.second, (obj->ID == Obj::MONOLITH_TWO_WAY));
auto path = placeAndConnectObject(zone.areaPossible(), rmgObject,
[this, &rmgObject](const int3 & tile)
{
float dist = rmgObject.getArea().distanceSqr(zone.getPos());
dist *= (dist > 12.f * 12.f) ? 10.f : 1.f; //tiles closer 12 are preferrable
dist = 1000000.f - dist; //some big number
return dist + map.getNearestObjectDistance(tile);
}, guarded, false, OptimizeType::WEIGHT);
if(!path.valid())
{
logGlobal->error("Failed to fill zone %d due to lack of space", zone.getId());
return false;
}
zone.connectPath(path);
placeObject(rmgObject, guarded, true);
for(const auto & nearby : nearbyObjects)
{
if(nearby.second != obj)
continue;
rmg::Object rmgNearObject(*nearby.first);
rmg::Area possibleArea(rmgObject.instances().front()->getBlockedArea().getBorderOutside());
possibleArea.intersect(zone.areaPossible());
if(possibleArea.empty())
{
rmgNearObject.clear();
continue;
}
rmgNearObject.setPosition(*RandomGeneratorUtil::nextItem(possibleArea.getTiles(), zone.getRand()));
placeObject(rmgNearObject, false, false);
}
}
//create object on specific positions
//TODO: implement guards
for (const auto &obj : instantObjects)
{
rmg::Object rmgObject(*obj.first);
rmgObject.setPosition(obj.second);
placeObject(rmgObject, false, false);
}
requiredObjects.clear();
closeObjects.clear();
nearbyObjects.clear();
instantObjects.clear();
return true;
}
void ObjectManager::placeObject(rmg::Object & object, bool guarded, bool updateDistance)
{
object.finalize(map);
Zone::Lock lock(zone.areaMutex);
zone.areaPossible().subtract(object.getArea());
bool keepVisitable = zone.freePaths().contains(object.getVisitablePosition());
zone.freePaths().subtract(object.getArea()); //just to avoid areas overlapping
if(keepVisitable)
zone.freePaths().add(object.getVisitablePosition());
zone.areaUsed().unite(object.getArea());
zone.areaUsed().erase(object.getVisitablePosition());
if(guarded)
{
auto guardedArea = object.instances().back()->getAccessibleArea();
guardedArea.add(object.instances().back()->getVisitablePosition());
auto areaToBlock = object.getAccessibleArea(true);
areaToBlock.subtract(guardedArea);
zone.areaPossible().subtract(areaToBlock);
for(const auto & i : areaToBlock.getTilesVector())
if(map.isOnMap(i) && map.isPossible(i))
map.setOccupied(i, ETileType::BLOCKED);
}
if(updateDistance)
updateDistances(object);
for(auto * instance : object.instances())
{
objectsVisitableArea.add(instance->getVisitablePosition());
objects.push_back(&instance->object());
if(auto * m = zone.getModificator<RoadPlacer>())
{
if(instance->object().appearance->isVisitableFromTop())
m->areaForRoads().add(instance->getVisitablePosition());
else
{
m->areaIsolated().add(instance->getVisitablePosition() + int3(0, -1, 0));
}
}
switch (instance->object().ID)
{
case Obj::RANDOM_TREASURE_ART:
case Obj::RANDOM_MINOR_ART: //In OH3 quest artifacts have higher value than normal arts
{
if (auto * qap = zone.getModificator<QuestArtifactPlacer>())
{
qap->rememberPotentialArtifactToReplace(&instance->object());
}
break;
}
default:
break;
}
}
switch(object.instances().front()->object().ID)
{
case Obj::TOWN:
case Obj::RANDOM_TOWN:
case Obj::MONOLITH_TWO_WAY:
case Obj::MONOLITH_ONE_WAY_ENTRANCE:
case Obj::MONOLITH_ONE_WAY_EXIT:
case Obj::SUBTERRANEAN_GATE:
case Obj::SHIPYARD:
if(auto * m = zone.getModificator<RoadPlacer>())
m->addRoadNode(object.instances().front()->getVisitablePosition());
break;
case Obj::WATER_WHEEL:
if(auto * m = zone.getModificator<RiverPlacer>())
m->addRiverNode(object.instances().front()->getVisitablePosition());
break;
default:
break;
}
}
CGCreature * ObjectManager::chooseGuard(si32 strength, bool zoneGuard)
{
//precalculate actual (randomized) monster strength based on this post
//http://forum.vcmi.eu/viewtopic.php?p=12426#12426
if(!zoneGuard && zone.monsterStrength == EMonsterStrength::ZONE_NONE)
return nullptr; //no guards inside this zone except for zone guards
int mapMonsterStrength = map.getMapGenOptions().getMonsterStrength();
int monsterStrength = (zoneGuard ? 0 : zone.monsterStrength - EMonsterStrength::ZONE_NORMAL) + mapMonsterStrength - 1; //array index from 0 to 4
static const std::array<int, 5> value1{2500, 1500, 1000, 500, 0};
static const std::array<int, 5> value2{7500, 7500, 7500, 5000, 5000};
static const std::array<float, 5> multiplier1{0.5, 0.75, 1.0, 1.5, 1.5};
static const std::array<float, 5> multiplier2{0.5, 0.75, 1.0, 1.0, 1.5};
int strength1 = static_cast<int>(std::max(0.f, (strength - value1.at(monsterStrength)) * multiplier1.at(monsterStrength)));
int strength2 = static_cast<int>(std::max(0.f, (strength - value2.at(monsterStrength)) * multiplier2.at(monsterStrength)));
strength = strength1 + strength2;
if (strength < generator.getConfig().minGuardStrength)
return nullptr; //no guard at all
CreatureID creId = CreatureID::NONE;
int amount = 0;
std::vector<CreatureID> possibleCreatures;
for(auto cre : VLC->creh->objects)
{
if(cre->special)
continue;
if(!cre->getAIValue()) //bug #2681
continue;
if(!vstd::contains(zone.getMonsterTypes(), cre->getFaction()))
continue;
if((static_cast<si32>(cre->getAIValue() * (cre->ammMin + cre->ammMax) / 2) < strength) && (strength < static_cast<si32>(cre->getAIValue()) * 100)) //at least one full monster. size between average size of given stack and 100
{
possibleCreatures.push_back(cre->getId());
}
}
if(!possibleCreatures.empty())
{
creId = *RandomGeneratorUtil::nextItem(possibleCreatures, zone.getRand());
amount = strength / VLC->creh->objects[creId]->getAIValue();
if (amount >= 4)
amount = static_cast<int>(amount * zone.getRand().nextDouble(0.75, 1.25));
}
else //just pick any available creature
{
creId = CreatureID(132); //Azure Dragon
amount = strength / VLC->creh->objects[creId]->getAIValue();
}
auto guardFactory = VLC->objtypeh->getHandlerFor(Obj::MONSTER, creId);
auto * guard = dynamic_cast<CGCreature *>(guardFactory->create());
guard->character = CGCreature::HOSTILE;
auto * hlp = new CStackInstance(creId, amount);
//will be set during initialization
guard->putStack(SlotID(0), hlp);
return guard;
}
bool ObjectManager::addGuard(rmg::Object & object, si32 strength, bool zoneGuard)
{
auto * guard = chooseGuard(strength, zoneGuard);
if(!guard)
return false;
rmg::Area visitablePos({object.getVisitablePosition()});
visitablePos.unite(visitablePos.getBorderOutside());
auto accessibleArea = object.getAccessibleArea();
accessibleArea.intersect(visitablePos);
if(accessibleArea.empty())
{
delete guard;
return false;
}
auto guardTiles = accessibleArea.getTilesVector();
auto guardPos = *std::min_element(guardTiles.begin(), guardTiles.end(), [&object](const int3 & l, const int3 & r)
{
auto p = object.getVisitablePosition();
if(l.y > r.y)
return true;
if(l.y == r.y)
return abs(l.x - p.x) < abs(r.x - p.x);
return false;
});
auto & instance = object.addInstance(*guard);
instance.setPosition(guardPos - object.getPosition());
instance.setAnyTemplate(); //terrain is irrelevant for monsters, but monsters need some template now
return true;
}
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,84 @@
/*
* ObjectManager.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 "../Zone.h"
#include "../RmgObject.h"
#include <boost/heap/priority_queue.hpp> //A*
VCMI_LIB_NAMESPACE_BEGIN
class CGObjectInstance;
class ObjectTemplate;
class CGCreature;
using TDistance = std::pair<int3, float>;
struct DistanceMaximizeFunctor
{
bool operator()(const TDistance & lhs, const TDistance & rhs) const
{
return (rhs.second > lhs.second);
}
};
class ObjectManager: public Modificator
{
public:
enum OptimizeType
{
NONE = 0x00000000,
WEIGHT = 0x00000001,
DISTANCE = 0x00000010
};
public:
MODIFICATOR(ObjectManager);
void process() override;
void init() override;
void addRequiredObject(CGObjectInstance * obj, si32 guardStrength=0);
void addCloseObject(CGObjectInstance * obj, si32 guardStrength = 0);
void addNearbyObject(CGObjectInstance * obj, CGObjectInstance * nearbyTarget);
bool createRequiredObjects();
int3 findPlaceForObject(const rmg::Area & searchArea, rmg::Object & obj, si32 min_dist, OptimizeType optimizer) const;
int3 findPlaceForObject(const rmg::Area & searchArea, rmg::Object & obj, const std::function<float(const int3)> & weightFunction, OptimizeType optimizer) const;
rmg::Path placeAndConnectObject(const rmg::Area & searchArea, rmg::Object & obj, si32 min_dist, bool isGuarded, bool onlyStraight, OptimizeType optimizer) const;
rmg::Path placeAndConnectObject(const rmg::Area & searchArea, rmg::Object & obj, const std::function<float(const int3)> & weightFunction, bool isGuarded, bool onlyStraight, OptimizeType optimizer) const;
CGCreature * chooseGuard(si32 strength, bool zoneGuard = false);
bool addGuard(rmg::Object & object, si32 strength, bool zoneGuard = false);
void placeObject(rmg::Object & object, bool guarded, bool updateDistance);
void updateDistances(const rmg::Object & obj);
void createDistancesPriorityQueue();
const rmg::Area & getVisitableArea() const;
std::vector<CGObjectInstance*> getMines() const;
protected:
//content info
std::vector<std::pair<CGObjectInstance*, ui32>> requiredObjects;
std::vector<std::pair<CGObjectInstance*, ui32>> closeObjects;
std::vector<std::pair<CGObjectInstance*, int3>> instantObjects;
std::vector<std::pair<CGObjectInstance*, CGObjectInstance*>> nearbyObjects;
std::vector<CGObjectInstance*> objects;
rmg::Area objectsVisitableArea;
boost::heap::priority_queue<TDistance, boost::heap::compare<DistanceMaximizeFunctor>> tilesByDistance;
};
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,101 @@
/*
* ObstaclePlacer.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 "ObstaclePlacer.h"
#include "ObjectManager.h"
#include "TreasurePlacer.h"
#include "RockPlacer.h"
#include "WaterRoutes.h"
#include "WaterProxy.h"
#include "RoadPlacer.h"
#include "RiverPlacer.h"
#include "../RmgMap.h"
#include "../CMapGenerator.h"
#include "../../CRandomGenerator.h"
#include "../Functions.h"
#include "../../mapping/CMapEditManager.h"
#include "../../mapping/CMap.h"
#include "../../mapping/ObstacleProxy.h"
VCMI_LIB_NAMESPACE_BEGIN
void ObstaclePlacer::process()
{
manager = zone.getModificator<ObjectManager>();
if(!manager)
return;
collectPossibleObstacles(zone.getTerrainType());
blockedArea = zone.area().getSubarea([this](const int3 & t)
{
return map.shouldBeBlocked(t);
});
blockedArea.subtract(zone.areaUsed());
zone.areaPossible().subtract(blockedArea);
prohibitedArea = zone.freePaths() + zone.areaUsed() + manager->getVisitableArea();
auto objs = createObstacles(zone.getRand());
mapProxy->insertObjects(objs);
}
void ObstaclePlacer::init()
{
DEPENDENCY(ObjectManager);
DEPENDENCY(TreasurePlacer);
DEPENDENCY(WaterRoutes);
DEPENDENCY(WaterProxy);
DEPENDENCY(RoadPlacer);
DEPENDENCY_ALL(RockPlacer);
}
bool ObstaclePlacer::isInTheMap(const int3& tile)
{
return map.isOnMap(tile);
}
void ObstaclePlacer::placeObject(rmg::Object & object, std::set<CGObjectInstance*> &)
{
manager->placeObject(object, false, false);
}
std::pair<bool, bool> ObstaclePlacer::verifyCoverage(const int3 & t) const
{
return {map.shouldBeBlocked(t), zone.areaPossible().contains(t)};
}
void ObstaclePlacer::postProcess(const rmg::Object & object)
{
//river processing
riverManager = zone.getModificator<RiverPlacer>();
if(riverManager)
{
const auto objTypeName = object.instances().front()->object().typeName;
if(objTypeName == "mountain")
riverManager->riverSource().unite(object.getArea());
else if(objTypeName == "lake")
riverManager->riverSink().unite(object.getArea());
}
}
bool ObstaclePlacer::isProhibited(const rmg::Area & objArea) const
{
if(prohibitedArea.overlap(objArea))
return true;
if(!zone.area().contains(objArea))
return true;
return false;
}
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,46 @@
/*
* ObstaclePlacer.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 "Modificator.h"
#include "../../mapping/ObstacleProxy.h"
VCMI_LIB_NAMESPACE_BEGIN
class CMap;
class CMapEditManager;
class RiverPlacer;
class ObjectManager;
class ObstaclePlacer: public Modificator, public ObstacleProxy
{
public:
MODIFICATOR(ObstaclePlacer);
void process() override;
void init() override;
bool isInTheMap(const int3& tile) override;
std::pair<bool, bool> verifyCoverage(const int3 & t) const override;
void placeObject(rmg::Object & object, std::set<CGObjectInstance*> & instances) override;
void postProcess(const rmg::Object & object) override;
bool isProhibited(const rmg::Area & objArea) const override;
private:
rmg::Area prohibitedArea;
RiverPlacer * riverManager;
ObjectManager * manager;
};
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,149 @@
/*
* QuestArtifact.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 "QuestArtifactPlacer.h"
#include "../CMapGenerator.h"
#include "../RmgMap.h"
#include "TreasurePlacer.h"
#include "../CZonePlacer.h"
#include "../../VCMI_Lib.h"
#include "../../mapObjects/CObjectHandler.h"
#include "../../mapObjects/CommonConstructors.h"
#include "../../mapObjects/MapObjects.h"
VCMI_LIB_NAMESPACE_BEGIN
void QuestArtifactPlacer::process()
{
findZonesForQuestArts();
placeQuestArtifacts(zone.getRand());
}
void QuestArtifactPlacer::init()
{
DEPENDENCY_ALL(TreasurePlacer);
}
void QuestArtifactPlacer::addQuestArtZone(std::shared_ptr<Zone> otherZone)
{
RecursiveLock lock(externalAccessMutex);
questArtZones.push_back(otherZone);
}
void QuestArtifactPlacer::addQuestArtifact(const ArtifactID& id)
{
RecursiveLock lock(externalAccessMutex);
logGlobal->info("Need to place quest artifact artifact %s", VLC->artifacts()->getById(id)->getNameTranslated());
questArtifactsToPlace.emplace_back(id);
}
void QuestArtifactPlacer::rememberPotentialArtifactToReplace(CGObjectInstance* obj)
{
RecursiveLock lock(externalAccessMutex);
artifactsToReplace.push_back(obj);
}
std::vector<CGObjectInstance*> QuestArtifactPlacer::getPossibleArtifactsToReplace() const
{
RecursiveLock lock(externalAccessMutex);
return artifactsToReplace;
}
void QuestArtifactPlacer::findZonesForQuestArts()
{
const auto& distances = generator.getZonePlacer()->getDistanceMap().at(zone.getId());
for (auto const& connectedZone : distances)
{
// Choose zones that are 1 or 2 connections away
if (vstd::iswithin(connectedZone.second, 1, 2))
{
addQuestArtZone(map.getZones().at(connectedZone.first));
}
}
logGlobal->info("Number of nearby zones suitable for quest artifacts: %d", questArtZones.size());
}
void QuestArtifactPlacer::placeQuestArtifacts(CRandomGenerator & rand)
{
for (const auto & artifactToPlace : questArtifactsToPlace)
{
RandomGeneratorUtil::randomShuffle(questArtZones, rand);
for (auto zone : questArtZones)
{
auto* qap = zone->getModificator<QuestArtifactPlacer>();
std::vector<CGObjectInstance *> artifactsToReplace = qap->getPossibleArtifactsToReplace();
if (artifactsToReplace.empty())
continue;
auto artifactToReplace = *RandomGeneratorUtil::nextItem(artifactsToReplace, rand);
logGlobal->info("Replacing %s at %s with the quest artifact %s",
artifactToReplace->getObjectName(),
artifactToReplace->getPosition().toString(),
VLC->artifacts()->getById(artifactToPlace)->getNameTranslated());
artifactToReplace->ID = Obj::ARTIFACT;
artifactToReplace->subID = artifactToPlace;
//Update appearance. Terrain is irrelevant.
auto handler = VLC->objtypeh->getHandlerFor(Obj::ARTIFACT, artifactToPlace);
auto templates = handler->getTemplates();
artifactToReplace->appearance = templates.front();
//FIXME: Instance name is still "randomArtifact"
for (auto z : map.getZones())
{
//Every qap has its OWN collection of artifacts
auto * localQap = zone->getModificator<QuestArtifactPlacer>();
if (localQap)
{
localQap->dropReplacedArtifact(artifactToReplace);
}
}
break;
}
}
}
void QuestArtifactPlacer::dropReplacedArtifact(CGObjectInstance* obj)
{
RecursiveLock lock(externalAccessMutex);
boost::remove(artifactsToReplace, obj);
}
size_t QuestArtifactPlacer::getMaxQuestArtifactCount() const
{
RecursiveLock lock(externalAccessMutex);
return questArtifacts.size();
}
ArtifactID QuestArtifactPlacer::drawRandomArtifact()
{
RecursiveLock lock(externalAccessMutex);
if (!questArtifacts.empty())
{
ArtifactID ret = questArtifacts.back();
questArtifacts.pop_back();
RandomGeneratorUtil::randomShuffle(questArtifacts, zone.getRand());
return ret;
}
else
{
throw rmgException("No quest artifacts left for this zone!");
}
}
void QuestArtifactPlacer::addRandomArtifact(ArtifactID artid)
{
RecursiveLock lock(externalAccessMutex);
questArtifacts.push_back(artid);
}
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,51 @@
/*
* QuestArtifactPlacer, 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 "../Zone.h"
#include "../Functions.h"
#include "../../mapObjects/ObjectTemplate.h"
VCMI_LIB_NAMESPACE_BEGIN
class CRandomGenerator;
class QuestArtifactPlacer : public Modificator
{
public:
MODIFICATOR(QuestArtifactPlacer);
void process() override;
void init() override;
void addQuestArtZone(std::shared_ptr<Zone> otherZone);
void findZonesForQuestArts();
void addQuestArtifact(const ArtifactID& id);
void rememberPotentialArtifactToReplace(CGObjectInstance* obj);
std::vector<CGObjectInstance*> getPossibleArtifactsToReplace() const;
void placeQuestArtifacts(CRandomGenerator & rand);
void dropReplacedArtifact(CGObjectInstance* obj);
size_t getMaxQuestArtifactCount() const;
ArtifactID drawRandomArtifact();
void addRandomArtifact(ArtifactID artid);
protected:
std::vector<std::shared_ptr<Zone>> questArtZones; //artifacts required for Seer Huts will be placed here - or not if null
std::vector<ArtifactID> questArtifactsToPlace;
std::vector<CGObjectInstance*> artifactsToReplace; //Common artifacts which may be replaced by quest artifacts from other zones
size_t maxQuestArtifacts;
std::vector<ArtifactID> questArtifacts;
};
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,405 @@
/*
* RiverPlacer.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 "RiverPlacer.h"
#include "../Functions.h"
#include "../CMapGenerator.h"
#include "../RmgMap.h"
#include "../../RiverHandler.h"
#include "../../TerrainHandler.h"
#include "../../mapping/CMap.h"
#include "../../mapping/CMapEditManager.h"
#include "../../mapObjects/CObjectClassesHandler.h"
#include "../RmgPath.h"
#include "ObjectManager.h"
#include "ObstaclePlacer.h"
#include "WaterProxy.h"
#include "RoadPlacer.h"
VCMI_LIB_NAMESPACE_BEGIN
const int RIVER_DELTA_ID = 143;
const int RIVER_DELTA_SUBTYPE = 0;
const std::array<std::array<int, 25>, 4> deltaTemplates
{
//0 - must be on ground
//1 - delta entry
//2 - must be on water
//3 - anything
//4 - prohibit river placement
//5 - must be on ground + position
//6 - must be on water + position
std::array<int, 25>{
3, 4, 3, 4, 3,
3, 4, 1, 4, 3,
3, 0, 0, 0, 3,
3, 0, 0, 0, 3,
3, 2, 2, 6, 3
},
std::array<int, 25>{
3, 2, 2, 2, 3,
3, 0, 0, 0, 3,
3, 0, 0, 5, 3,
3, 4, 1, 4, 3,
3, 4, 3, 4, 3
},
std::array<int, 25>{
3, 3, 3, 3, 3,
4, 4, 0, 0, 2,
3, 1, 0, 0, 2,
4, 4, 0, 0, 6,
3, 3, 3, 3, 3
},
std::array<int, 25> {
3, 3, 3, 3, 3,
2, 0, 0, 4, 4,
2, 0, 0, 1, 3,
2, 0, 5, 4, 4,
3, 3, 3, 3, 3
}
};
void RiverPlacer::process()
{
preprocess();
for(const auto & t : riverNodes)
connectRiver(t);
if(!rivers.empty())
drawRivers();
}
void RiverPlacer::init()
{
DEPENDENCY_ALL(WaterProxy);
DEPENDENCY(ObjectManager);
DEPENDENCY(ObstaclePlacer);
}
void RiverPlacer::drawRivers()
{
auto tiles = rivers.getTilesVector();
mapProxy->drawRivers(zone.getRand(), tiles, zone.getTerrainType());
}
char RiverPlacer::dump(const int3 & t)
{
if(riverNodes.count(t))
return '@';
if(rivers.contains(t))
return '~';
if(sink.contains(t))
return '2';
if(source.contains(t))
return '1';
if(zone.area().contains(t))
return ' ';
return '?';
}
void RiverPlacer::addRiverNode(const int3 & node)
{
assert(zone.area().contains(node));
riverNodes.insert(node);
}
rmg::Area & RiverPlacer::riverSource()
{
return source;
}
rmg::Area & RiverPlacer::riverSink()
{
return sink;
}
rmg::Area & RiverPlacer::riverProhibit()
{
return prohibit;
}
void RiverPlacer::prepareHeightmap()
{
rmg::Area roads;
if(auto * m = zone.getModificator<RoadPlacer>())
{
roads.unite(m->getRoads());
}
for(const auto & t : zone.area().getTilesVector())
{
heightMap[t] = zone.getRand().nextInt(5);
if(roads.contains(t))
heightMap[t] += 30.f;
if(zone.areaUsed().contains(t))
heightMap[t] += 1000.f;
}
//make grid
for(int j = 0; j < map.height(); j += 2)
{
for(int i = 0; i < map.width(); i += 2)
{
int3 t{i, j, zone.getPos().z};
if(zone.area().contains(t))
heightMap[t] += 10.f;
}
}
}
void RiverPlacer::preprocess()
{
rmg::Area outOfMapTiles;
std::map<TRmgTemplateZoneId, rmg::Area> neighbourZonesTiles;
rmg::Area borderArea(zone.getArea().getBorder());
TRmgTemplateZoneId connectedToWaterZoneId = -1;
for(const auto & t : zone.getArea().getBorderOutside())
{
if(!map.isOnMap(t))
{
outOfMapTiles.add(t);
}
else if(map.getZoneID(t) != zone.getId())
{
if(map.getZones()[map.getZoneID(t)]->getType() == ETemplateZoneType::WATER)
connectedToWaterZoneId = map.getZoneID(t);
neighbourZonesTiles[map.getZoneID(t)].add(t);
}
}
rmg::Area outOfMapInternal(outOfMapTiles.getBorderOutside());
outOfMapInternal.intersect(borderArea);
//looking outside map
if(!outOfMapInternal.empty())
{
auto elem = *RandomGeneratorUtil::nextItem(outOfMapInternal.getTilesVector(), zone.getRand());
source.add(elem);
outOfMapInternal.erase(elem);
}
if(!outOfMapInternal.empty())
{
auto elem = *RandomGeneratorUtil::nextItem(outOfMapInternal.getTilesVector(), zone.getRand());
sink.add(elem);
outOfMapInternal.erase(elem);
}
//calculate delta positions
if(connectedToWaterZoneId > -1)
{
auto river = VLC->terrainTypeHandler->getById(zone.getTerrainType())->river;
auto & a = neighbourZonesTiles[connectedToWaterZoneId];
auto availableArea = zone.areaPossible() + zone.freePaths();
for(const auto & tileToProcess : availableArea.getTilesVector())
{
int templateId = -1;
for(int tId = 0; tId < 4; ++tId)
{
templateId = tId;
for(int i = 0; i < 25; ++i)
{
if((deltaTemplates[tId][i] == 2 || deltaTemplates[tId][i] == 6) && !a.contains(tileToProcess + int3(i % 5 - 2, i / 5 - 2, 0)))
{
templateId = -1;
break;
}
if((deltaTemplates[tId][i] < 2 || deltaTemplates[tId][i] == 5) && !availableArea.contains(tileToProcess + int3(i % 5 - 2, i / 5 - 2, 0)))
{
templateId = -1;
break;
}
}
if(templateId > -1)
break;
}
if(templateId > -1)
{
for(int i = 0; i < 25; ++i)
{
auto p = tileToProcess + int3(i % 5 - 2, i / 5 - 2, 0);
if(deltaTemplates[templateId][i] == 1)
{
sink.add(p);
deltaSink.add(p);
deltaOrientations[p] = templateId + 1;
//specific case: deltas for ice rivers amd mud rivers are messed :(
if(river == River::ICY_RIVER)
{
switch(deltaOrientations[p])
{
case 1:
deltaOrientations[p] = 2;
break;
case 2:
deltaOrientations[p] = 3;
break;
case 3:
deltaOrientations[p] = 4;
break;
case 4:
deltaOrientations[p] = 1;
break;
}
}
if(river == River::MUD_RIVER)
{
switch(deltaOrientations[p])
{
case 1:
deltaOrientations[p] = 4;
break;
case 2:
deltaOrientations[p] = 3;
break;
case 3:
deltaOrientations[p] = 1;
break;
case 4:
deltaOrientations[p] = 2;
break;
}
}
for(auto j = 0; j < 25; ++j)
{
if(deltaTemplates[templateId][j] >= 5)
{
deltaPositions[p] = tileToProcess + int3(j % 5 - 2, j / 5 - 2, 0);
}
}
}
if(deltaTemplates[templateId][i] == 0 || deltaTemplates[templateId][i] == 4 || deltaTemplates[templateId][i] == 5)
{
prohibit.add(p);
}
}
}
}
}
prepareHeightmap();
//decorative river
if(!sink.empty() && !source.empty() && riverNodes.empty() && !zone.areaPossible().empty())
{
addRiverNode(*RandomGeneratorUtil::nextItem(source.getTilesVector(), zone.getRand()));
}
if(source.empty())
{
logGlobal->info("River source is empty!");
//looking outside map
for(auto & i : heightMap)
{
if(i.second > 0)
source.add(i.first);
}
}
if(sink.empty())
{
logGlobal->error("River sink is empty!");
for(auto & i : heightMap)
{
if(i.second <= 0)
sink.add(i.first);
}
}
}
void RiverPlacer::connectRiver(const int3 & tile)
{
auto riverType = VLC->terrainTypeHandler->getById(zone.getTerrainType())->river;
const auto * river = VLC->riverTypeHandler->getById(riverType);
if(river->getId() == River::NO_RIVER)
return;
rmg::Area roads;
if(auto * m = zone.getModificator<RoadPlacer>())
{
roads.unite(m->getRoads());
}
auto movementCost = [this, &roads](const int3 & s, const int3 & d)
{
float cost = heightMap[d];
if(roads.contains(s))
cost += 1000.f; //allow road intersection, but avoid long overlaps
return cost;
};
auto availableArea = zone.area() - prohibit;
rmg::Path pathToSource(availableArea);
pathToSource.connect(source);
pathToSource.connect(rivers);
pathToSource = pathToSource.search(tile, true, movementCost);
availableArea.subtract(pathToSource.getPathArea());
rmg::Path pathToSink(availableArea);
pathToSink.connect(sink);
pathToSource.connect(rivers);
pathToSink = pathToSink.search(tile, true, movementCost);
if(pathToSource.getPathArea().empty() || pathToSink.getPathArea().empty())
{
logGlobal->error("Cannot build river");
return;
}
//delta placement
auto deltaPos = pathToSink.getPathArea() * deltaSink;
if(!deltaPos.empty())
{
assert(deltaPos.getTilesVector().size() == 1);
auto pos = deltaPos.getTilesVector().front();
auto handler = VLC->objtypeh->getHandlerFor(RIVER_DELTA_ID, RIVER_DELTA_SUBTYPE);
assert(handler->isStaticObject());
std::vector<std::shared_ptr<const ObjectTemplate>> tmplates;
for(auto & temp : handler->getTemplates())
{
if(temp->canBePlacedAt(zone.getTerrainType()))
tmplates.push_back(temp);
}
if(tmplates.size() > 3)
{
if(tmplates.size() % 4 != 0)
throw rmgException(boost::to_string(boost::format("River templates for (%d,%d) at terrain %s, river %s are incorrect") %
RIVER_DELTA_ID % RIVER_DELTA_SUBTYPE % zone.getTerrainType() % river->shortIdentifier));
std::string targetTemplateName = river->deltaName + std::to_string(deltaOrientations[pos]) + ".def";
for(auto & templ : tmplates)
{
if(templ->animationFile == targetTemplateName)
{
auto * obj = handler->create(templ);
rmg::Object deltaObj(*obj, deltaPositions[pos]);
deltaObj.finalize(map);
}
}
}
}
rivers.unite(pathToSource.getPathArea());
rivers.unite(pathToSink.getPathArea());
}
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,52 @@
/*
* RiverPlacer.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 "../Zone.h"
VCMI_LIB_NAMESPACE_BEGIN
class RiverPlacer: public Modificator
{
public:
MODIFICATOR(RiverPlacer);
void process() override;
void init() override;
char dump(const int3 &) override;
void addRiverNode(const int3 & node);
rmg::Area & riverSource();
rmg::Area & riverSink();
rmg::Area & riverProhibit();
protected:
void drawRivers();
void preprocess();
void connectRiver(const int3 & tile);
void prepareHeightmap();
private:
rmg::Area rivers;
rmg::Area source;
rmg::Area sink;
rmg::Area prohibit;
rmg::Tileset riverNodes;
rmg::Area deltaSink;
std::map<int3, int3> deltaPositions;
std::map<int3, int> deltaOrientations;
std::map<int3, int> heightMap;
};
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,147 @@
/*
* RoadPlacer.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 "RoadPlacer.h"
#include "ObjectManager.h"
#include "ObstaclePlacer.h"
#include "../Functions.h"
#include "../CMapGenerator.h"
#include "../RmgMap.h"
#include "../threadpool/MapProxy.h"
#include "../../mapping/CMapEditManager.h"
#include "../../CModHandler.h"
#include "../RmgPath.h"
VCMI_LIB_NAMESPACE_BEGIN
void RoadPlacer::process()
{
if(generator.getConfig().defaultRoadType.empty() && generator.getConfig().secondaryRoadType.empty())
return; //do not generate roads at all
connectRoads();
}
rmg::Area & RoadPlacer::areaForRoads()
{
return areaRoads;
}
rmg::Area & RoadPlacer::areaIsolated()
{
return isolated;
}
const rmg::Area & RoadPlacer::getRoads() const
{
return roads;
}
bool RoadPlacer::createRoad(const int3 & dst)
{
auto searchArea = zone.areaPossible() + areaRoads + zone.freePaths() - isolated + roads;
rmg::Path path(searchArea);
path.connect(roads);
auto res = path.search(dst, true);
if(!res.valid())
{
res = path.search(dst, false, [](const int3 & src, const int3 & dst)
{
float weight = dst.dist2dSQ(src);
return weight * weight;
});
if(!res.valid())
{
logGlobal->warn("Failed to create road");
return false;
}
}
roads.unite(res.getPathArea());
return true;
}
void RoadPlacer::drawRoads(bool secondary)
{
if((secondary && generator.getConfig().secondaryRoadType.empty())
|| (!secondary && generator.getConfig().defaultRoadType.empty()))
return;
//RecursiveLock lock(externalAccessMutex);
{
//FIXME: double lock - unsafe
Zone::Lock lock(zone.areaMutex);
zone.areaPossible().subtract(roads);
zone.freePaths().unite(roads);
}
auto tiles = roads.getTilesVector();
std::string roadName = (secondary ? generator.getConfig().secondaryRoadType : generator.getConfig().defaultRoadType);
RoadId roadType(*VLC->modh->identifiers.getIdentifier(CModHandler::scopeGame(), "road", roadName));
mapProxy->drawRoads(zone.getRand(), tiles, roadType);
}
void RoadPlacer::addRoadNode(const int3& node)
{
RecursiveLock lock(externalAccessMutex);
roadNodes.insert(node);
}
void RoadPlacer::connectRoads()
{
bool noRoadNodes = false;
//Assumes objects are already placed
if (roadNodes.size() < 2)
{
//If there are no nodes, draw roads to mines
noRoadNodes = true;
if (auto* m = zone.getModificator<ObjectManager>())
{
for(auto * object : m->getMines())
{
addRoadNode(object->visitablePos());
}
}
}
if(roadNodes.size() < 2)
return;
//take any tile from road nodes as destination zone for all other road nodes
RecursiveLock lock(externalAccessMutex);
if(roads.empty())
roads.add(*roadNodes.begin());
for(const auto & node : roadNodes)
{
createRoad(node);
}
//Draw dirt roads if there are only mines
drawRoads(noRoadNodes);
}
char RoadPlacer::dump(const int3 & t)
{
if(roadNodes.count(t))
return '@';
if(roads.contains(t))
return '+';
if(isolated.contains(t))
return 'i';
return Modificator::dump(t);
}
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,41 @@
/*
* RoadPlacer.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 "../Zone.h"
VCMI_LIB_NAMESPACE_BEGIN
class RoadPlacer: public Modificator
{
public:
MODIFICATOR(RoadPlacer);
void process() override;
char dump(const int3 &) override;
void addRoadNode(const int3 & node);
void connectRoads(); //fills "roads" according to "roadNodes"
rmg::Area & areaForRoads();
rmg::Area & areaIsolated();
const rmg::Area & getRoads() const;
protected:
bool createRoad(const int3 & dst);
void drawRoads(bool secondary = false); //actually updates tiles
protected:
rmg::Tileset roadNodes; //tiles to be connected with roads
rmg::Area roads; //all tiles with roads
rmg::Area areaRoads, isolated;
};
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,78 @@
/*
* RockFiller.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 "RockFiller.h"
#include "RockPlacer.h"
#include "TreasurePlacer.h"
#include "ObjectManager.h"
#include "RoadPlacer.h"
#include "RiverPlacer.h"
#include "../RmgMap.h"
#include "../CMapGenerator.h"
#include "../Functions.h"
#include "../../TerrainHandler.h"
#include "../../CRandomGenerator.h"
#include "../lib/mapping/CMapEditManager.h"
#include "../TileInfo.h"
#include "../threadpool/MapProxy.h"
VCMI_LIB_NAMESPACE_BEGIN
class TileInfo;
void RockFiller::process()
{
processMap();
}
void RockFiller::processMap()
{
//Merge all areas
for(auto & z : map.getZones())
{
auto zone = z.second;
if(auto * m = zone->getModificator<RockPlacer>())
{
auto tiles = m->rockArea.getTilesVector();
mapProxy->drawTerrain(zone->getRand(), tiles, m->rockTerrain);
}
}
for(auto & z : map.getZones())
{
auto zone = z.second;
if(auto * m = zone->getModificator<RockPlacer>())
{
//Now make sure all accessible tiles have no additional rock on them
auto tiles = m->accessibleArea.getTilesVector();
mapProxy->drawTerrain(zone->getRand(), tiles, zone->getTerrainType());
m->postProcess();
}
}
}
void RockFiller::init()
{
DEPENDENCY_ALL(RockPlacer);
POSTFUNCTION_ALL(RoadPlacer);
}
char RockFiller::dump(const int3 & t)
{
if(!map.getTile(t).terType->isPassable())
{
return zone.area().contains(t) ? 'R' : 'E';
}
return Modificator::dump(t);
}
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,28 @@
/*
* RockFiller.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 "../Zone.h"
VCMI_LIB_NAMESPACE_BEGIN
class RockFiller: public Modificator
{
public:
MODIFICATOR(RockFiller);
void process() override;
void init() override;
char dump(const int3 &) override;
void processMap();
};
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,82 @@
/*
* RockPlacer.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 "RockPlacer.h"
#include "TreasurePlacer.h"
#include "ObjectManager.h"
#include "RoadPlacer.h"
#include "RiverPlacer.h"
#include "../RmgMap.h"
#include "../CMapGenerator.h"
#include "../Functions.h"
#include "../../TerrainHandler.h"
#include "../../CRandomGenerator.h"
#include "../../mapping/CMapEditManager.h"
#include "../TileInfo.h"
VCMI_LIB_NAMESPACE_BEGIN
class TileInfo;
void RockPlacer::process()
{
blockRock();
}
void RockPlacer::blockRock()
{
rockTerrain = VLC->terrainTypeHandler->getById(zone.getTerrainType())->rockTerrain;
assert(!VLC->terrainTypeHandler->getById(rockTerrain)->isPassable());
accessibleArea = zone.freePaths() + zone.areaUsed();
if(auto * m = zone.getModificator<ObjectManager>())
accessibleArea.unite(m->getVisitableArea());
//negative approach - create rock tiles first, then make sure all accessible tiles have no rock
rockArea = zone.area().getSubarea([this](const int3 & t)
{
return map.shouldBeBlocked(t);
});
}
void RockPlacer::postProcess()
{
Zone::Lock lock(zone.areaMutex);
//Finally mark rock tiles as occupied, spawn no obstacles there
rockArea = zone.area().getSubarea([this](const int3 & t)
{
return !map.getTile(t).terType->isPassable();
});
zone.areaUsed().unite(rockArea);
zone.areaPossible().subtract(rockArea);
//TODO: Might need mutex here as well
if(auto * m = zone.getModificator<RiverPlacer>())
m->riverProhibit().unite(rockArea);
if(auto * m = zone.getModificator<RoadPlacer>())
m->areaIsolated().unite(rockArea);
}
void RockPlacer::init()
{
DEPENDENCY_ALL(TreasurePlacer);
}
char RockPlacer::dump(const int3 & t)
{
if(!map.getTile(t).terType->isPassable())
{
return zone.area().contains(t) ? 'R' : 'E';
}
return Modificator::dump(t);
}
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,35 @@
/*
* RockPlacer.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 "../Zone.h"
VCMI_LIB_NAMESPACE_BEGIN
class RockPlacer: public Modificator
{
friend class RockFiller;
public:
MODIFICATOR(RockPlacer);
void process() override;
void init() override;
char dump(const int3 &) override;
void blockRock();
void postProcess();
protected:
rmg::Area rockArea, accessibleArea;
TerrainId rockTerrain;
};
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,105 @@
/*
* TerrainPainter.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 "TerrainPainter.h"
#include "TownPlacer.h"
#include "WaterAdopter.h"
#include "WaterProxy.h"
#include "ConnectionsPlacer.h"
#include "ObjectManager.h"
#include "../Functions.h"
#include "../CMapGenerator.h"
#include "../RmgMap.h"
#include "../../VCMI_Lib.h"
#include "../../TerrainHandler.h"
VCMI_LIB_NAMESPACE_BEGIN
void TerrainPainter::process()
{
initTerrainType();
auto v = zone.getArea().getTilesVector();
mapProxy->drawTerrain(zone.getRand(), v, zone.getTerrainType());
}
void TerrainPainter::init()
{
DEPENDENCY(TownPlacer);
DEPENDENCY_ALL(WaterAdopter);
POSTFUNCTION_ALL(WaterProxy);
POSTFUNCTION_ALL(ConnectionsPlacer);
POSTFUNCTION(ObjectManager);
}
void TerrainPainter::initTerrainType()
{
if(zone.getType()==ETemplateZoneType::WATER)
{
//collect all water terrain types
std::vector<TerrainId> waterTerrains;
for(const auto & terrain : VLC->terrainTypeHandler->objects)
if(terrain->isWater())
waterTerrains.push_back(terrain->getId());
zone.setTerrainType(*RandomGeneratorUtil::nextItem(waterTerrains, zone.getRand()));
}
else
{
if(zone.isMatchTerrainToTown() && zone.getTownType() != ETownType::NEUTRAL)
{
auto terrainType = (*VLC->townh)[zone.getTownType()]->nativeTerrain;
if (terrainType <= ETerrainId::NONE)
{
logGlobal->warn("Town %s has invalid terrain type: %d", zone.getTownType(), terrainType);
zone.setTerrainType(ETerrainId::DIRT);
}
else
{
zone.setTerrainType(terrainType);
}
}
else
{
auto terrainTypes = zone.getTerrainTypes();
if (terrainTypes.empty())
{
logGlobal->warn("No terrain types found, falling back to DIRT");
zone.setTerrainType(ETerrainId::DIRT);
}
else
{
zone.setTerrainType(*RandomGeneratorUtil::nextItem(terrainTypes, zone.getRand()));
}
}
//Now, replace disallowed terrains on surface and in the underground
const auto & terrainType = VLC->terrainTypeHandler->getById(zone.getTerrainType());
if(zone.isUnderground())
{
if(!terrainType->isUnderground())
{
zone.setTerrainType(ETerrainId::SUBTERRANEAN);
}
}
else
{
if (!terrainType->isSurface())
{
zone.setTerrainType(ETerrainId::DIRT);
}
}
}
}
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,27 @@
/*
* TerrainPainter.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 "../Zone.h"
VCMI_LIB_NAMESPACE_BEGIN
class TerrainPainter: public Modificator
{
public:
MODIFICATOR(TerrainPainter);
void process() override;
void init() override;
void initTerrainType();
};
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,246 @@
/*
* TownPlacer.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 "TownPlacer.h"
#include "../CMapGenerator.h"
#include "../RmgMap.h"
#include "../../mapping/CMap.h"
#include "../../mapping/CMapEditManager.h"
#include "../../mapObjects/CObjectClassesHandler.h"
#include "../../spells/CSpellHandler.h" //for choosing random spells
#include "../RmgPath.h"
#include "../RmgObject.h"
#include "ObjectManager.h"
#include "../Functions.h"
#include "RoadPlacer.h"
#include "MinePlacer.h"
#include "WaterAdopter.h"
#include "../TileInfo.h"
VCMI_LIB_NAMESPACE_BEGIN
void TownPlacer::process()
{
auto * manager = zone.getModificator<ObjectManager>();
if(!manager)
{
logGlobal->error("ObjectManager doesn't exist for zone %d, skip modificator %s", zone.getId(), getName());
return;
}
placeTowns(*manager);
}
void TownPlacer::init()
{
POSTFUNCTION(MinePlacer);
POSTFUNCTION(RoadPlacer);
}
void TownPlacer::placeTowns(ObjectManager & manager)
{
if(zone.getOwner() && ((zone.getType() == ETemplateZoneType::CPU_START) || (zone.getType() == ETemplateZoneType::PLAYER_START)))
{
//set zone types to player faction, generate main town
logGlobal->info("Preparing playing zone");
int player_id = *zone.getOwner() - 1;
auto& playerInfo = map.getPlayer(player_id);
PlayerColor player(player_id);
if(playerInfo.canAnyonePlay())
{
player = PlayerColor(player_id);
zone.setTownType(map.getMapGenOptions().getPlayersSettings().find(player)->second.getStartingTown());
if(zone.getTownType() == CMapGenOptions::CPlayerSettings::RANDOM_TOWN)
zone.setTownType(getRandomTownType(true));
}
else //no player - randomize town
{
player = PlayerColor::NEUTRAL;
zone.setTownType(getRandomTownType());
}
auto townFactory = VLC->objtypeh->getHandlerFor(Obj::TOWN, zone.getTownType());
CGTownInstance * town = dynamic_cast<CGTownInstance *>(townFactory->create());
town->tempOwner = player;
town->builtBuildings.insert(BuildingID::FORT);
town->builtBuildings.insert(BuildingID::DEFAULT);
for(auto spell : VLC->spellh->objects) //add all regular spells to town
{
if(!spell->isSpecial() && !spell->isCreatureAbility())
town->possibleSpells.push_back(spell->id);
}
auto position = placeMainTown(manager, *town);
totalTowns++;
//register MAIN town of zone only
map.registerZone(town->getFaction());
if(playerInfo.canAnyonePlay()) //configure info for owning player
{
logGlobal->trace("Fill player info %d", player_id);
// Update player info
playerInfo.allowedFactions.clear();
playerInfo.allowedFactions.insert(zone.getTownType());
playerInfo.hasMainTown = true;
playerInfo.posOfMainTown = position;
playerInfo.generateHeroAtMainTown = true;
//now create actual towns
addNewTowns(zone.getPlayerTowns().getCastleCount() - 1, true, player, manager);
addNewTowns(zone.getPlayerTowns().getTownCount(), false, player, manager);
}
else
{
addNewTowns(zone.getPlayerTowns().getCastleCount() - 1, true, PlayerColor::NEUTRAL, manager);
addNewTowns(zone.getPlayerTowns().getTownCount(), false, PlayerColor::NEUTRAL, manager);
}
}
else //randomize town types for any other zones as well
{
zone.setTownType(getRandomTownType());
}
addNewTowns(zone.getNeutralTowns().getCastleCount(), true, PlayerColor::NEUTRAL, manager);
addNewTowns(zone.getNeutralTowns().getTownCount(), false, PlayerColor::NEUTRAL, manager);
if(!totalTowns) //if there's no town present, get random faction for dwellings and pandoras
{
//25% chance for neutral
if (zone.getRand().nextInt(1, 100) <= 25)
{
zone.setTownType(ETownType::NEUTRAL);
}
else
{
if(!zone.getTownTypes().empty())
zone.setTownType(*RandomGeneratorUtil::nextItem(zone.getTownTypes(), zone.getRand()));
else if(!zone.getMonsterTypes().empty())
zone.setTownType(*RandomGeneratorUtil::nextItem(zone.getMonsterTypes(), zone.getRand())); //this happens in Clash of Dragons in treasure zones, where all towns are banned
else //just in any case
zone.setTownType(getRandomTownType());
}
}
}
int3 TownPlacer::placeMainTown(ObjectManager & manager, CGTownInstance & town)
{
//towns are big objects and should be centered around visitable position
rmg::Object rmgObject(town);
rmgObject.setTemplate(zone.getTerrainType());
int3 position(-1, -1, -1);
{
Zone::Lock lock(zone.areaMutex);
position = manager.findPlaceForObject(zone.areaPossible(), rmgObject, [this](const int3& t)
{
float distance = zone.getPos().dist2dSQ(t);
return 100000.f - distance; //some big number
}, ObjectManager::OptimizeType::WEIGHT);
}
rmgObject.setPosition(position + int3(2, 2, 0)); //place visitable tile in the exact center of a zone
manager.placeObject(rmgObject, false, true);
cleanupBoundaries(rmgObject);
zone.setPos(rmgObject.getVisitablePosition()); //roads lead to main town
return position;
}
void TownPlacer::cleanupBoundaries(const rmg::Object & rmgObject)
{
Zone::Lock lock(zone.areaMutex);
for(const auto & t : rmgObject.getArea().getBorderOutside())
{
if(map.isOnMap(t))
{
map.setOccupied(t, ETileType::FREE);
zone.areaPossible().erase(t);
zone.freePaths().add(t);
}
}
}
void TownPlacer::addNewTowns(int count, bool hasFort, const PlayerColor & player, ObjectManager & manager)
{
for(int i = 0; i < count; i++)
{
si32 subType = zone.getTownType();
if(totalTowns>0)
{
if(!zone.areTownsSameType())
{
if(!zone.getTownTypes().empty())
subType = *RandomGeneratorUtil::nextItem(zone.getTownTypes(), zone.getRand());
else
subType = *RandomGeneratorUtil::nextItem(zone.getDefaultTownTypes(), zone.getRand()); //it is possible to have zone with no towns allowed
}
}
auto townFactory = VLC->objtypeh->getHandlerFor(Obj::TOWN, subType);
auto * town = dynamic_cast<CGTownInstance *>(townFactory->create());
town->ID = Obj::TOWN;
town->tempOwner = player;
if (hasFort)
town->builtBuildings.insert(BuildingID::FORT);
town->builtBuildings.insert(BuildingID::DEFAULT);
for(auto spell : VLC->spellh->objects) //add all regular spells to town
{
if(!spell->isSpecial() && !spell->isCreatureAbility())
town->possibleSpells.push_back(spell->id);
}
if(totalTowns <= 0)
{
//FIXME: discovered bug with small zones - getPos is close to map boarder and we have outOfMap exception
//register MAIN town of zone
map.registerZone(town->getFaction());
//first town in zone goes in the middle
placeMainTown(manager, *town);
}
else
manager.addRequiredObject(town);
totalTowns++;
}
}
si32 TownPlacer::getRandomTownType(bool matchUndergroundType)
{
auto townTypesAllowed = (!zone.getTownTypes().empty() ? zone.getTownTypes() : zone.getDefaultTownTypes());
if(matchUndergroundType)
{
std::set<FactionID> townTypesVerify;
for(auto factionIdx : townTypesAllowed)
{
bool preferUnderground = (*VLC->townh)[factionIdx]->preferUndergroundPlacement;
if(zone.isUnderground() ? preferUnderground : !preferUnderground)
{
townTypesVerify.insert(factionIdx);
}
}
if(!townTypesVerify.empty())
townTypesAllowed = townTypesVerify;
}
return *RandomGeneratorUtil::nextItem(townTypesAllowed, zone.getRand());
}
int TownPlacer::getTotalTowns() const
{
return totalTowns;
}
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,40 @@
/*
* TownPlacer.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 "../Zone.h"
VCMI_LIB_NAMESPACE_BEGIN
class ObjectManager;
class CGTownInstance;
class TownPlacer: public Modificator
{
public:
MODIFICATOR(TownPlacer);
void process() override;
void init() override;
int getTotalTowns() const;
protected:
void cleanupBoundaries(const rmg::Object & rmgObject);
void addNewTowns(int count, bool hasFort, const PlayerColor & player, ObjectManager & manager);
si32 getRandomTownType(bool matchUndergroundType = false);
void placeTowns(ObjectManager & manager);
bool placeMines(ObjectManager & manager);
int3 placeMainTown(ObjectManager & manager, CGTownInstance & town);
protected:
int totalTowns = 0;
};
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,858 @@
/*
* TreasurePlacer.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 "TreasurePlacer.h"
#include "../CMapGenerator.h"
#include "../Functions.h"
#include "ObjectManager.h"
#include "RoadPlacer.h"
#include "ConnectionsPlacer.h"
#include "../RmgMap.h"
#include "../TileInfo.h"
#include "../CZonePlacer.h"
#include "QuestArtifactPlacer.h"
#include "../../mapObjects/CommonConstructors.h"
#include "../../mapObjects/MapObjects.h" //needed to resolve templates for CommonConstructors.h
#include "../../CCreatureHandler.h"
#include "../../spells/CSpellHandler.h" //for choosing random spells
#include "../../mapping/CMap.h"
#include "../../mapping/CMapEditManager.h"
VCMI_LIB_NAMESPACE_BEGIN
void TreasurePlacer::process()
{
addAllPossibleObjects();
auto * m = zone.getModificator<ObjectManager>();
if(m)
createTreasures(*m);
}
void TreasurePlacer::init()
{
DEPENDENCY(ObjectManager);
DEPENDENCY(ConnectionsPlacer);
POSTFUNCTION(RoadPlacer);
}
void TreasurePlacer::addObjectToRandomPool(const ObjectInfo& oi)
{
RecursiveLock lock(externalAccessMutex);
possibleObjects.push_back(oi);
}
void TreasurePlacer::addAllPossibleObjects()
{
ObjectInfo oi;
for(auto primaryID : VLC->objtypeh->knownObjects())
{
for(auto secondaryID : VLC->objtypeh->knownSubObjects(primaryID))
{
auto handler = VLC->objtypeh->getHandlerFor(primaryID, secondaryID);
if(!handler->isStaticObject() && handler->getRMGInfo().value)
{
auto rmgInfo = handler->getRMGInfo();
if (rmgInfo.mapLimit || rmgInfo.value > zone.getMaxTreasureValue())
{
//Skip objects with per-map limit here
continue;
}
auto templates = handler->getTemplates(zone.getTerrainType());
if (templates.empty())
continue;
//TODO: Reuse chooseRandomAppearance (eg. WoG treasure chests)
//Assume the template with fewest terrains is the most suitable
auto temp = *boost::min_element(templates, [](std::shared_ptr<const ObjectTemplate> lhs, std::shared_ptr<const ObjectTemplate> rhs) -> bool
{
return lhs->getAllowedTerrains().size() < rhs->getAllowedTerrains().size();
});
oi.generateObject = [temp]() -> CGObjectInstance *
{
return VLC->objtypeh->getHandlerFor(temp->id, temp->subid)->create(temp);
};
oi.value = rmgInfo.value;
oi.probability = rmgInfo.rarity;
oi.templ = temp;
oi.maxPerZone = rmgInfo.zoneLimit;
addObjectToRandomPool(oi);
}
}
}
if(zone.getType() == ETemplateZoneType::WATER)
return;
//prisons
//levels 1, 5, 10, 20, 30
static int prisonsLevels = std::min(generator.getConfig().prisonExperience.size(), generator.getConfig().prisonValues.size());
size_t prisonsLeft = getMaxPrisons();
for(int i = prisonsLevels - 1; i >= 0 ;i--)
{
oi.value = generator.getConfig().prisonValues[i];
if (oi.value > zone.getMaxTreasureValue())
{
continue;
}
oi.generateObject = [i, this]() -> CGObjectInstance *
{
auto possibleHeroes = generator.getAllPossibleHeroes();
HeroTypeID hid = *RandomGeneratorUtil::nextItem(possibleHeroes, zone.getRand());
auto factory = VLC->objtypeh->getHandlerFor(Obj::PRISON, 0);
auto * obj = dynamic_cast<CGHeroInstance *>(factory->create());
obj->subID = hid; //will be initialized later
obj->exp = generator.getConfig().prisonExperience[i];
obj->setOwner(PlayerColor::NEUTRAL);
generator.banHero(hid);
obj->appearance = VLC->objtypeh->getHandlerFor(Obj::PRISON, 0)->getTemplates(zone.getTerrainType()).front(); //can't init template with hero subID
return obj;
};
oi.setTemplate(Obj::PRISON, 0, zone.getTerrainType());
oi.value = generator.getConfig().prisonValues[i];
oi.probability = 30;
//Distribute all allowed prisons, starting from the most valuable
oi.maxPerZone = (std::ceil((float)prisonsLeft / (i + 1)));
prisonsLeft -= oi.maxPerZone;
addObjectToRandomPool(oi);
}
//all following objects are unlimited
oi.maxPerZone = std::numeric_limits<ui32>::max();
std::vector<CCreature *> creatures; //native creatures for this zone
for(auto cre : VLC->creh->objects)
{
if(!cre->special && cre->getFaction() == zone.getTownType())
{
creatures.push_back(cre);
}
}
//dwellings
auto dwellingTypes = {Obj::CREATURE_GENERATOR1, Obj::CREATURE_GENERATOR4};
for(auto dwellingType : dwellingTypes)
{
auto subObjects = VLC->objtypeh->knownSubObjects(dwellingType);
if(dwellingType == Obj::CREATURE_GENERATOR1)
{
//don't spawn original "neutral" dwellings that got replaced by Conflux dwellings in AB
static int elementalConfluxROE[] = {7, 13, 16, 47};
for(int & i : elementalConfluxROE)
vstd::erase_if_present(subObjects, i);
}
for(auto secondaryID : subObjects)
{
const auto * dwellingHandler = dynamic_cast<const CDwellingInstanceConstructor *>(VLC->objtypeh->getHandlerFor(dwellingType, secondaryID).get());
auto creatures = dwellingHandler->getProducedCreatures();
if(creatures.empty())
continue;
const auto * cre = creatures.front();
if(cre->getFaction() == zone.getTownType())
{
auto nativeZonesCount = static_cast<float>(map.getZoneCount(cre->getFaction()));
oi.value = static_cast<ui32>(cre->getAIValue() * cre->getGrowth() * (1 + (nativeZonesCount / map.getTotalZoneCount()) + (nativeZonesCount / 2)));
oi.probability = 40;
for(const auto & tmplate : dwellingHandler->getTemplates())
{
if(tmplate->canBePlacedAt(zone.getTerrainType()))
{
oi.generateObject = [tmplate, secondaryID, dwellingType]() -> CGObjectInstance *
{
auto * obj = VLC->objtypeh->getHandlerFor(dwellingType, secondaryID)->create(tmplate);
obj->tempOwner = PlayerColor::NEUTRAL;
return obj;
};
oi.templ = tmplate;
addObjectToRandomPool(oi);
}
}
}
}
}
for(int i = 0; i < generator.getConfig().scrollValues.size(); i++)
{
oi.generateObject = [i, this]() -> CGObjectInstance *
{
auto factory = VLC->objtypeh->getHandlerFor(Obj::SPELL_SCROLL, 0);
auto * obj = dynamic_cast<CGArtifact *>(factory->create());
std::vector<SpellID> out;
for(auto spell : VLC->spellh->objects) //spellh size appears to be greater (?)
{
if(map.isAllowedSpell(spell->id) && spell->level == i + 1)
{
out.push_back(spell->id);
}
}
auto * a = CArtifactInstance::createScroll(*RandomGeneratorUtil::nextItem(out, zone.getRand()));
obj->storedArtifact = a;
return obj;
};
oi.setTemplate(Obj::SPELL_SCROLL, 0, zone.getTerrainType());
oi.value = generator.getConfig().scrollValues[i];
oi.probability = 30;
addObjectToRandomPool(oi);
}
//pandora box with gold
for(int i = 1; i < 5; i++)
{
oi.generateObject = [i]() -> CGObjectInstance *
{
auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
auto * obj = dynamic_cast<CGPandoraBox *>(factory->create());
obj->resources[EGameResID::GOLD] = i * 5000;
return obj;
};
oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
oi.value = i * generator.getConfig().pandoraMultiplierGold;
oi.probability = 5;
addObjectToRandomPool(oi);
}
//pandora box with experience
for(int i = 1; i < 5; i++)
{
oi.generateObject = [i]() -> CGObjectInstance *
{
auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
auto * obj = dynamic_cast<CGPandoraBox *>(factory->create());
obj->gainedExp = i * 5000;
return obj;
};
oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
oi.value = i * generator.getConfig().pandoraMultiplierExperience;
oi.probability = 20;
addObjectToRandomPool(oi);
}
//pandora box with creatures
const std::vector<int> & tierValues = generator.getConfig().pandoraCreatureValues;
auto creatureToCount = [tierValues](CCreature * creature) -> int
{
if(!creature->getAIValue() || tierValues.empty()) //bug #2681
return 0; //this box won't be generated
//Follow the rules from https://heroes.thelazy.net/index.php/Pandora%27s_Box
int actualTier = creature->getLevel() > tierValues.size() ?
tierValues.size() - 1 :
creature->getLevel() - 1;
float creaturesAmount = std::floor((static_cast<float>(tierValues[actualTier])) / creature->getAIValue());
if (creaturesAmount < 1)
{
return 0;
}
else if(creaturesAmount <= 5)
{
//No change
}
else if(creaturesAmount <= 12)
{
creaturesAmount = std::ceil(creaturesAmount / 2) * 2;
}
else if(creaturesAmount <= 50)
{
creaturesAmount = std::round(creaturesAmount / 5) * 5;
}
else
{
creaturesAmount = std::round(creaturesAmount / 10) * 10;
}
return static_cast<int>(creaturesAmount);
};
for(auto * creature : creatures)
{
int creaturesAmount = creatureToCount(creature);
if(!creaturesAmount)
continue;
oi.generateObject = [creature, creaturesAmount]() -> CGObjectInstance *
{
auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
auto * obj = dynamic_cast<CGPandoraBox *>(factory->create());
auto * stack = new CStackInstance(creature, creaturesAmount);
obj->creatures.putStack(SlotID(0), stack);
return obj;
};
oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
oi.value = static_cast<ui32>((2 * (creature->getAIValue()) * creaturesAmount * (1 + static_cast<float>(map.getZoneCount(creature->getFaction())) / map.getTotalZoneCount())) / 3);
oi.probability = 3;
addObjectToRandomPool(oi);
}
//Pandora with 12 spells of certain level
for(int i = 1; i <= GameConstants::SPELL_LEVELS; i++)
{
oi.generateObject = [i, this]() -> CGObjectInstance *
{
auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
auto * obj = dynamic_cast<CGPandoraBox *>(factory->create());
std::vector <CSpell *> spells;
for(auto spell : VLC->spellh->objects)
{
if(map.isAllowedSpell(spell->id) && spell->level == i)
spells.push_back(spell);
}
RandomGeneratorUtil::randomShuffle(spells, zone.getRand());
for(int j = 0; j < std::min(12, static_cast<int>(spells.size())); j++)
{
obj->spells.push_back(spells[j]->id);
}
return obj;
};
oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
oi.value = (i + 1) * generator.getConfig().pandoraMultiplierSpells; //5000 - 15000
oi.probability = 2;
addObjectToRandomPool(oi);
}
//Pandora with 15 spells of certain school
for(int i = 0; i < 4; i++)
{
oi.generateObject = [i, this]() -> CGObjectInstance *
{
auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
auto * obj = dynamic_cast<CGPandoraBox *>(factory->create());
std::vector <CSpell *> spells;
for(auto spell : VLC->spellh->objects)
{
if(map.isAllowedSpell(spell->id) && spell->school[SpellSchool(i)])
spells.push_back(spell);
}
RandomGeneratorUtil::randomShuffle(spells, zone.getRand());
for(int j = 0; j < std::min(15, static_cast<int>(spells.size())); j++)
{
obj->spells.push_back(spells[j]->id);
}
return obj;
};
oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
oi.value = generator.getConfig().pandoraSpellSchool;
oi.probability = 2;
addObjectToRandomPool(oi);
}
// Pandora box with 60 random spells
oi.generateObject = [this]() -> CGObjectInstance *
{
auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
auto * obj = dynamic_cast<CGPandoraBox *>(factory->create());
std::vector <CSpell *> spells;
for(auto spell : VLC->spellh->objects)
{
if(map.isAllowedSpell(spell->id))
spells.push_back(spell);
}
RandomGeneratorUtil::randomShuffle(spells, zone.getRand());
for(int j = 0; j < std::min(60, static_cast<int>(spells.size())); j++)
{
obj->spells.push_back(spells[j]->id);
}
return obj;
};
oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
oi.value = generator.getConfig().pandoraSpell60;
oi.probability = 2;
addObjectToRandomPool(oi);
//Seer huts with creatures or generic rewards
if(zone.getConnections().size()) //Unlikely, but...
{
auto * qap = zone.getModificator<QuestArtifactPlacer>();
if(!qap)
{
return; //TODO: throw?
}
const int questArtsRemaining = qap->getMaxQuestArtifactCount();
//Generate Seer Hut one by one. Duplicated oi possible and should work fine.
oi.maxPerZone = 1;
std::vector<ObjectInfo> possibleSeerHuts;
//14 creatures per town + 4 for each of gold / exp reward
possibleSeerHuts.reserve(14 + 4 + 4);
RandomGeneratorUtil::randomShuffle(creatures, zone.getRand());
for(int i = 0; i < static_cast<int>(creatures.size()); i++)
{
auto * creature = creatures[i];
int creaturesAmount = creatureToCount(creature);
if(!creaturesAmount)
continue;
int randomAppearance = chooseRandomAppearance(zone.getRand(), Obj::SEER_HUT, zone.getTerrainType());
oi.generateObject = [creature, creaturesAmount, randomAppearance, this, qap]() -> CGObjectInstance *
{
auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
auto * obj = dynamic_cast<CGSeerHut *>(factory->create());
obj->rewardType = CGSeerHut::CREATURE;
obj->rID = creature->getId();
obj->rVal = creaturesAmount;
obj->quest->missionType = CQuest::MISSION_ART;
ArtifactID artid = qap->drawRandomArtifact();
obj->quest->addArtifactID(artid);
obj->quest->lastDay = -1;
obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false;
generator.banQuestArt(artid);
zone.getModificator<QuestArtifactPlacer>()->addQuestArtifact(artid);
return obj;
};
oi.probability = 3;
oi.setTemplate(Obj::SEER_HUT, randomAppearance, zone.getTerrainType());
oi.value = static_cast<ui32>(((2 * (creature->getAIValue()) * creaturesAmount * (1 + static_cast<float>(map.getZoneCount(creature->getFaction())) / map.getTotalZoneCount())) - 4000) / 3);
if (oi.value > zone.getMaxTreasureValue())
{
continue;
}
else
{
possibleSeerHuts.push_back(oi);
}
}
static int seerLevels = std::min(generator.getConfig().questValues.size(), generator.getConfig().questRewardValues.size());
for(int i = 0; i < seerLevels; i++) //seems that code for exp and gold reward is similiar
{
int randomAppearance = chooseRandomAppearance(zone.getRand(), Obj::SEER_HUT, zone.getTerrainType());
oi.setTemplate(Obj::SEER_HUT, randomAppearance, zone.getTerrainType());
oi.value = generator.getConfig().questValues[i];
if (oi.value > zone.getMaxTreasureValue())
{
//Both variants have same value
continue;
}
oi.probability = 10;
oi.generateObject = [i, randomAppearance, this, qap]() -> CGObjectInstance *
{
auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
auto * obj = dynamic_cast<CGSeerHut *>(factory->create());
obj->rewardType = CGSeerHut::EXPERIENCE;
obj->rID = 0; //unitialized?
obj->rVal = generator.getConfig().questRewardValues[i];
obj->quest->missionType = CQuest::MISSION_ART;
ArtifactID artid = qap->drawRandomArtifact();
obj->quest->addArtifactID(artid);
obj->quest->lastDay = -1;
obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false;
generator.banQuestArt(artid);
zone.getModificator<QuestArtifactPlacer>()->addQuestArtifact(artid);
return obj;
};
possibleSeerHuts.push_back(oi);
oi.generateObject = [i, randomAppearance, this, qap]() -> CGObjectInstance *
{
auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
auto * obj = dynamic_cast<CGSeerHut *>(factory->create());
obj->rewardType = CGSeerHut::RESOURCES;
obj->rID = GameResID(EGameResID::GOLD);
obj->rVal = generator.getConfig().questRewardValues[i];
obj->quest->missionType = CQuest::MISSION_ART;
ArtifactID artid = qap->drawRandomArtifact();
obj->quest->addArtifactID(artid);
obj->quest->lastDay = -1;
obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false;
generator.banQuestArt(artid);
zone.getModificator<QuestArtifactPlacer>()->addQuestArtifact(artid);
return obj;
};
possibleSeerHuts.push_back(oi);
}
for (size_t i = 0; i < questArtsRemaining; i++)
{
addObjectToRandomPool(*RandomGeneratorUtil::nextItem(possibleSeerHuts, zone.getRand()));
}
}
}
size_t TreasurePlacer::getPossibleObjectsSize() const
{
RecursiveLock lock(externalAccessMutex);
return possibleObjects.size();
}
void TreasurePlacer::setMaxPrisons(size_t count)
{
RecursiveLock lock(externalAccessMutex);
maxPrisons = count;
}
size_t TreasurePlacer::getMaxPrisons() const
{
RecursiveLock lock(externalAccessMutex);
return maxPrisons;
}
bool TreasurePlacer::isGuardNeededForTreasure(int value)
{// no guard in a zone with "monsters: none" and for small treasures; water zones cen get monster strength ZONE_NONE elsewhere if needed
return zone.monsterStrength != EMonsterStrength::ZONE_NONE && value > minGuardedValue;
}
std::vector<ObjectInfo*> TreasurePlacer::prepareTreasurePile(const CTreasureInfo& treasureInfo)
{
std::vector<ObjectInfo*> objectInfos;
int maxValue = treasureInfo.max;
int minValue = treasureInfo.min;
const ui32 desiredValue =zone.getRand().nextInt(minValue, maxValue);
int currentValue = 0;
bool hasLargeObject = false;
while(currentValue <= static_cast<int>(desiredValue) - 100) //no objects with value below 100 are available
{
auto * oi = getRandomObject(desiredValue, currentValue, maxValue, !hasLargeObject);
if(!oi) //fail
break;
if(oi->templ->isVisitableFromTop())
{
objectInfos.push_back(oi);
}
else
{
objectInfos.insert(objectInfos.begin(), oi); //large object shall at first place
hasLargeObject = true;
}
//remove from possible objects
assert(oi->maxPerZone);
oi->maxPerZone--;
currentValue += oi->value;
}
return objectInfos;
}
rmg::Object TreasurePlacer::constructTreasurePile(const std::vector<ObjectInfo*> & treasureInfos, bool densePlacement)
{
rmg::Object rmgObject;
for(const auto & oi : treasureInfos)
{
auto blockedArea = rmgObject.getArea();
auto accessibleArea = rmgObject.getAccessibleArea();
if(rmgObject.instances().empty())
accessibleArea.add(int3());
auto * object = oi->generateObject();
object->appearance = oi->templ;
auto & instance = rmgObject.addInstance(*object);
do
{
if(accessibleArea.empty())
{
//fail - fallback
rmgObject.clear();
return rmgObject;
}
std::vector<int3> bestPositions;
if(densePlacement)
{
int bestPositionsWeight = std::numeric_limits<int>::max();
for(const auto & t : accessibleArea.getTilesVector())
{
instance.setPosition(t);
int w = rmgObject.getAccessibleArea().getTilesVector().size();
if(w < bestPositionsWeight)
{
bestPositions.clear();
bestPositions.push_back(t);
bestPositionsWeight = w;
}
else if(w == bestPositionsWeight)
{
bestPositions.push_back(t);
}
}
}
else
{
bestPositions = accessibleArea.getTilesVector();
}
int3 nextPos = *RandomGeneratorUtil::nextItem(bestPositions, zone.getRand());
instance.setPosition(nextPos - rmgObject.getPosition());
auto instanceAccessibleArea = instance.getAccessibleArea();
if(instance.getBlockedArea().getTilesVector().size() == 1)
{
if(instance.object().appearance->isVisitableFromTop() && instance.object().ID != Obj::CORPSE)
instanceAccessibleArea.add(instance.getVisitablePosition());
}
//first object is good
if(rmgObject.instances().size() == 1)
break;
//condition for good position
if(!blockedArea.overlap(instance.getBlockedArea()) && accessibleArea.overlap(instanceAccessibleArea))
break;
//fail - new position
accessibleArea.erase(nextPos);
} while(true);
}
return rmgObject;
}
ObjectInfo * TreasurePlacer::getRandomObject(ui32 desiredValue, ui32 currentValue, ui32 maxValue, bool allowLargeObjects)
{
std::vector<std::pair<ui32, ObjectInfo*>> thresholds; //handle complex object via pointer
ui32 total = 0;
//calculate actual treasure value range based on remaining value
ui32 maxVal = maxValue - currentValue;
ui32 minValue = static_cast<ui32>(0.25f * (desiredValue - currentValue));
for(ObjectInfo & oi : possibleObjects) //copy constructor turned out to be costly
{
if(oi.value > maxVal)
break; //this assumes values are sorted in ascending order
if(!oi.templ->isVisitableFromTop() && !allowLargeObjects)
continue;
if(oi.value >= minValue && oi.maxPerZone > 0)
{
total += oi.probability;
thresholds.emplace_back(total, &oi);
}
}
if(thresholds.empty())
{
return nullptr;
}
else
{
int r = zone.getRand().nextInt(1, total);
auto sorter = [](const std::pair<ui32, ObjectInfo *> & rhs, const ui32 lhs) -> bool
{
return static_cast<int>(rhs.first) < lhs;
};
//binary search = fastest
auto it = std::lower_bound(thresholds.begin(), thresholds.end(), r, sorter);
return it->second;
}
}
void TreasurePlacer::createTreasures(ObjectManager & manager)
{
const int maxAttempts = 2;
int mapMonsterStrength = map.getMapGenOptions().getMonsterStrength();
int monsterStrength = (zone.monsterStrength == EMonsterStrength::ZONE_NONE ? 0 : zone.monsterStrength + mapMonsterStrength - 1); //array index from 0 to 4; pick any correct value for ZONE_NONE, minGuardedValue won't be used in this case anyway
static int minGuardedValues[] = { 6500, 4167, 3000, 1833, 1333 };
minGuardedValue = minGuardedValues[monsterStrength];
auto valueComparator = [](const CTreasureInfo & lhs, const CTreasureInfo & rhs) -> bool
{
return lhs.max > rhs.max;
};
auto restoreZoneLimits = [](const std::vector<ObjectInfo*> & treasurePile)
{
for(auto * oi : treasurePile)
{
oi->maxPerZone++;
}
};
//place biggest treasures first at large distance, place smaller ones inbetween
auto treasureInfo = zone.getTreasureInfo();
boost::sort(treasureInfo, valueComparator);
//sort treasures by ascending value so we can stop checking treasures with too high value
boost::sort(possibleObjects, [](const ObjectInfo& oi1, const ObjectInfo& oi2) -> bool
{
return oi1.value < oi2.value;
});
int totalDensity = 0;
for (auto t : treasureInfo)
{
//discard objects with too high value to be ever placed
vstd::erase_if(possibleObjects, [t](const ObjectInfo& oi) -> bool
{
return oi.value > t.max;
});
totalDensity += t.density;
//treasure density is inversely proportional to zone size but must be scaled back to map size
//also, normalize it to zone count - higher count means relatively smaller zones
//this is squared distance for optimization purposes
const float minDistance = std::max<float>((125.f / totalDensity), 2.0f);
//distance lower than 2 causes objects to overlap and crash
for(int attempt = 0; attempt <= maxAttempts;)
{
auto treasurePileInfos = prepareTreasurePile(t);
if(treasurePileInfos.empty())
{
++attempt;
continue;
}
int value = std::accumulate(treasurePileInfos.begin(), treasurePileInfos.end(), 0, [](int v, const ObjectInfo * oi){return v + oi->value;});
auto rmgObject = constructTreasurePile(treasurePileInfos, attempt == maxAttempts);
if(rmgObject.instances().empty()) //handle incorrect placement
{
restoreZoneLimits(treasurePileInfos);
continue;
}
//guard treasure pile
bool guarded = isGuardNeededForTreasure(value);
if(guarded)
guarded = manager.addGuard(rmgObject, value);
Zone::Lock lock(zone.areaMutex); //We are going to subtract this area
//TODO: Don't place
auto possibleArea = zone.areaPossible();
auto path = rmg::Path::invalid();
if(guarded)
{
path = manager.placeAndConnectObject(possibleArea, rmgObject, [this, &rmgObject, &minDistance, &manager](const int3 & tile)
{
auto ti = map.getTileInfo(tile);
if(ti.getNearestObjectDistance() < minDistance)
return -1.f;
for(const auto & t : rmgObject.getArea().getTilesVector())
{
if(map.getTileInfo(t).getNearestObjectDistance() < minDistance)
return -1.f;
}
auto guardedArea = rmgObject.instances().back()->getAccessibleArea();
auto areaToBlock = rmgObject.getAccessibleArea(true);
areaToBlock.subtract(guardedArea);
if(areaToBlock.overlap(zone.freePaths()) || areaToBlock.overlap(manager.getVisitableArea()))
return -1.f;
return ti.getNearestObjectDistance();
}, guarded, false, ObjectManager::OptimizeType::DISTANCE);
}
else
{
path = manager.placeAndConnectObject(possibleArea, rmgObject, minDistance, guarded, false, ObjectManager::OptimizeType::DISTANCE);
}
if(path.valid())
{
//debug purposes
treasureArea.unite(rmgObject.getArea());
if(guarded)
{
guards.unite(rmgObject.instances().back()->getBlockedArea());
auto guardedArea = rmgObject.instances().back()->getAccessibleArea();
auto areaToBlock = rmgObject.getAccessibleArea(true);
areaToBlock.subtract(guardedArea);
treasureBlockArea.unite(areaToBlock);
}
zone.connectPath(path);
manager.placeObject(rmgObject, guarded, true);
attempt = 0;
}
else
{
restoreZoneLimits(treasurePileInfos);
rmgObject.clear();
++attempt;
}
}
}
}
char TreasurePlacer::dump(const int3 & t)
{
if(guards.contains(t))
return '!';
if(treasureArea.contains(t))
return '$';
if(treasureBlockArea.contains(t))
return '*';
return Modificator::dump(t);
}
void ObjectInfo::setTemplate(si32 type, si32 subtype, TerrainId terrainType)
{
auto templHandler = VLC->objtypeh->getHandlerFor(type, subtype);
if(!templHandler)
return;
auto templates = templHandler->getTemplates(terrainType);
if(templates.empty())
return;
templ = templates.front();
}
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,72 @@
/*
* TreasurePlacer.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 "../Zone.h"
#include "../../mapObjects/ObjectTemplate.h"
VCMI_LIB_NAMESPACE_BEGIN
class CGObjectInstance;
class ObjectManager;
class RmgMap;
class CMapGenerator;
class CRandomGenerator;
struct ObjectInfo
{
std::shared_ptr<const ObjectTemplate> templ;
ui32 value = 0;
ui16 probability = 0;
ui32 maxPerZone = 1;
//ui32 maxPerMap; //unused
std::function<CGObjectInstance *()> generateObject;
void setTemplate(si32 type, si32 subtype, TerrainId terrain);
bool operator==(const ObjectInfo& oi) const { return (templ == oi.templ); }
};
class TreasurePlacer: public Modificator
{
public:
MODIFICATOR(TreasurePlacer);
void process() override;
void init() override;
char dump(const int3 &) override;
void createTreasures(ObjectManager & manager);
void addObjectToRandomPool(const ObjectInfo& oi);
void addAllPossibleObjects(); //add objects, including zone-specific, to possibleObjects
size_t getPossibleObjectsSize() const;
void setMaxPrisons(size_t count);
size_t getMaxPrisons() const;
protected:
bool isGuardNeededForTreasure(int value);
ObjectInfo * getRandomObject(ui32 desiredValue, ui32 currentValue, ui32 maxValue, bool allowLargeObjects);
std::vector<ObjectInfo*> prepareTreasurePile(const CTreasureInfo & treasureInfo);
rmg::Object constructTreasurePile(const std::vector<ObjectInfo*> & treasureInfos, bool densePlacement = false);
protected:
std::vector<ObjectInfo> possibleObjects;
int minGuardedValue = 0;
rmg::Area treasureArea;
rmg::Area treasureBlockArea;
rmg::Area guards;
size_t maxPrisons;
};
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,270 @@
/*
* WaterAdopter.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 "WaterAdopter.h"
#include "../CMapGenerator.h"
#include "../RmgMap.h"
#include "../../mapping/CMap.h"
#include "../../mapping/CMapEditManager.h"
#include "../../mapObjects/CObjectClassesHandler.h"
#include "../RmgPath.h"
#include "../RmgObject.h"
#include "ObjectManager.h"
#include "../Functions.h"
#include "RoadPlacer.h"
#include "TreasurePlacer.h"
#include "TownPlacer.h"
#include "ConnectionsPlacer.h"
#include "../TileInfo.h"
VCMI_LIB_NAMESPACE_BEGIN
void WaterAdopter::process()
{
createWater(map.getMapGenOptions().getWaterContent());
}
void WaterAdopter::init()
{
//make dependencies
DEPENDENCY(TownPlacer);
POSTFUNCTION(ConnectionsPlacer);
POSTFUNCTION(TreasurePlacer);
}
void WaterAdopter::createWater(EWaterContent::EWaterContent waterContent)
{
if(waterContent == EWaterContent::NONE || zone.isUnderground() || zone.getType() == ETemplateZoneType::WATER)
return; //do nothing
distanceMap = zone.area().computeDistanceMap(reverseDistanceMap);
//add border tiles as water for ISLANDS
if(waterContent == EWaterContent::ISLANDS)
{
waterArea.unite(collectDistantTiles(zone, zone.getSize() + 1));
waterArea.unite(zone.area().getBorder());
}
//protect some parts from water for NORMAL
if(waterContent == EWaterContent::NORMAL)
{
waterArea.unite(collectDistantTiles(zone, zone.getSize() - 1));
auto sliceStart = RandomGeneratorUtil::nextItem(reverseDistanceMap[0], zone.getRand());
auto sliceEnd = RandomGeneratorUtil::nextItem(reverseDistanceMap[0], zone.getRand());
//at least 25% without water
bool endPassed = false;
for(int counter = 0; counter < reverseDistanceMap[0].size() / 4 || !endPassed; ++sliceStart, ++counter)
{
if(sliceStart == reverseDistanceMap[0].end())
sliceStart = reverseDistanceMap[0].begin();
if(sliceStart == sliceEnd)
endPassed = true;
noWaterArea.add(*sliceStart);
}
rmg::Area noWaterSlice;
for(int i = 1; i < reverseDistanceMap.size(); ++i)
{
for(const auto & t : reverseDistanceMap[i])
{
if(noWaterArea.distanceSqr(t) < 3)
noWaterSlice.add(t);
}
noWaterArea.unite(noWaterSlice);
}
}
//generating some irregularity of coast
int coastIdMax = sqrt(reverseDistanceMap.size()); //size of coastTilesMap shows the most distant tile from water
assert(coastIdMax > 0);
std::list<int3> tilesQueue;
rmg::Tileset tilesChecked;
for(int coastId = coastIdMax; coastId >= 0; --coastId)
{
//amount of iterations shall be proportion of coast perimeter
const int coastLength = reverseDistanceMap[coastId].size() / (coastId + 3);
for(int coastIter = 0; coastIter < coastLength; ++coastIter)
{
int3 tile = *RandomGeneratorUtil::nextItem(reverseDistanceMap[coastId], zone.getRand());
if(tilesChecked.find(tile) != tilesChecked.end())
continue;
if(map.isUsed(tile) || map.isFree(tile)) //prevent placing water nearby town
continue;
tilesQueue.push_back(tile);
tilesChecked.insert(tile);
}
}
//if tile is marked as water - connect it with "big" water
while(!tilesQueue.empty())
{
int3 src = tilesQueue.front();
tilesQueue.pop_front();
if(waterArea.contains(src))
continue;
waterArea.add(src);
map.foreach_neighbour(src, [&src, this, &tilesChecked, &tilesQueue](const int3 & dst)
{
if(tilesChecked.count(dst))
return;
if(distanceMap[dst] >= 0 && distanceMap[src] - distanceMap[dst] == 1)
{
tilesQueue.push_back(dst);
tilesChecked.insert(dst);
}
});
}
waterArea.subtract(noWaterArea);
//start filtering of narrow places and coast atrifacts
rmg::Area waterAdd;
for(int coastId = 1; coastId <= coastIdMax; ++coastId)
{
for(const auto & tile : reverseDistanceMap[coastId])
{
//collect neighbout water tiles
auto collectionLambda = [this](const int3 & t, std::set<int3> & outCollection)
{
if(waterArea.contains(t))
{
reverseDistanceMap[0].insert(t);
outCollection.insert(t);
}
};
std::set<int3> waterCoastDirect;
std::set<int3> waterCoastDiag;
map.foreachDirectNeighbour(tile, std::bind(collectionLambda, std::placeholders::_1, std::ref(waterCoastDirect)));
map.foreachDiagonalNeighbour(tile, std::bind(collectionLambda, std::placeholders::_1, std::ref(waterCoastDiag)));
int waterCoastDirectNum = waterCoastDirect.size();
int waterCoastDiagNum = waterCoastDiag.size();
//remove tiles which are mostly covered by water
if(waterCoastDirectNum >= 3)
{
waterAdd.add(tile);
continue;
}
if(waterCoastDiagNum == 4 && waterCoastDirectNum == 2)
{
waterAdd.add(tile);
continue;
}
if(waterCoastDirectNum == 2 && waterCoastDiagNum >= 2)
{
int3 diagSum;
int3 dirSum;
for(const auto & i : waterCoastDiag)
diagSum += i - tile;
for(const auto & i : waterCoastDirect)
dirSum += i - tile;
if(diagSum == int3() || dirSum == int3())
{
waterAdd.add(tile);
continue;
}
if(waterCoastDiagNum == 3 && diagSum != dirSum)
{
waterAdd.add(tile);
continue;
}
}
}
}
waterArea.unite(waterAdd);
//filtering tiny "lakes"
for(const auto & tile : reverseDistanceMap[0]) //now it's only coast-water tiles
{
if(!waterArea.contains(tile)) //for ground tiles
continue;
std::vector<int3> groundCoast;
map.foreachDirectNeighbour(tile, [this, &groundCoast](const int3 & t)
{
if(!waterArea.contains(t) && zone.area().contains(t)) //for ground tiles of same zone
{
groundCoast.push_back(t);
}
});
if(groundCoast.size() >= 3)
{
waterArea.erase(tile);
}
else
{
if(groundCoast.size() == 2)
{
if(groundCoast[0] + groundCoast[1] == int3())
{
waterArea.erase(tile);
}
}
}
}
{
Zone::Lock waterLock(map.getZones()[waterZoneId]->areaMutex);
map.getZones()[waterZoneId]->area().unite(waterArea);
}
Zone::Lock lock(zone.areaMutex);
zone.area().subtract(waterArea);
zone.areaPossible().subtract(waterArea);
distanceMap = zone.area().computeDistanceMap(reverseDistanceMap);
}
void WaterAdopter::setWaterZone(TRmgTemplateZoneId water)
{
waterZoneId = water;
}
rmg::Area WaterAdopter::getCoastTiles() const
{
if(reverseDistanceMap.empty())
return rmg::Area();
return rmg::Area(reverseDistanceMap.at(0));
}
char WaterAdopter::dump(const int3 & t)
{
if(noWaterArea.contains(t))
return 'X';
if(waterArea.contains(t))
return '~';
auto distanceMapIter = distanceMap.find(t);
if(distanceMapIter != distanceMap.end())
{
if(distanceMapIter->second > 9)
return '%';
auto distStr = std::to_string(distanceMapIter->second);
if(distStr.length() > 0)
return distStr[0];
}
return Modificator::dump(t);
}
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,40 @@
/*
* WaterAdopter.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 "../Zone.h"
VCMI_LIB_NAMESPACE_BEGIN
class WaterAdopter: public Modificator
{
public:
MODIFICATOR(WaterAdopter);
void process() override;
void init() override;
char dump(const int3 &) override;
void setWaterZone(TRmgTemplateZoneId water);
rmg::Area getCoastTiles() const;
protected:
void createWater(EWaterContent::EWaterContent waterContent);
protected:
rmg::Area noWaterArea, waterArea;
TRmgTemplateZoneId waterZoneId;
std::map<int3, int> distanceMap;
std::map<int, rmg::Tileset> reverseDistanceMap;
};
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,400 @@
/*
* WaterProxy.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 "WaterProxy.h"
#include "../CMapGenerator.h"
#include "../RmgMap.h"
#include "../../TerrainHandler.h"
#include "../../mapping/CMap.h"
#include "../../mapping/CMapEditManager.h"
#include "../../mapObjects/CObjectClassesHandler.h"
#include "../RmgPath.h"
#include "../RmgObject.h"
#include "ObjectManager.h"
#include "../Functions.h"
#include "RoadPlacer.h"
#include "TreasurePlacer.h"
#include "TownPlacer.h"
#include "ConnectionsPlacer.h"
#include "../TileInfo.h"
#include "WaterAdopter.h"
#include "../RmgArea.h"
VCMI_LIB_NAMESPACE_BEGIN
void WaterProxy::process()
{
for(const auto & t : zone.area().getTilesVector())
{
map.setZoneID(t, zone.getId());
map.setOccupied(t, ETileType::POSSIBLE);
}
auto v = zone.getArea().getTilesVector();
mapProxy->drawTerrain(zone.getRand(), v, zone.getTerrainType());
//check terrain type
for([[maybe_unused]] const auto & t : zone.area().getTilesVector())
{
assert(map.isOnMap(t));
assert(map.map().getTile(t).terType->getId() == zone.getTerrainType());
}
for(const auto & z : map.getZones())
{
if(z.second->getId() == zone.getId())
continue;
Zone::Lock lock(z.second->areaMutex);
for(const auto & t : z.second->area().getTilesVector())
{
if(map.getTile(t).terType->getId() == zone.getTerrainType())
{
z.second->areaPossible().erase(t);
z.second->area().erase(t);
zone.area().add(t);
zone.areaPossible().add(t);
map.setZoneID(t, zone.getId());
map.setOccupied(t, ETileType::POSSIBLE);
}
}
}
if(!zone.area().contains(zone.getPos()))
{
zone.setPos(zone.area().getTilesVector().front());
}
zone.initFreeTiles();
collectLakes();
}
void WaterProxy::init()
{
for(auto & z : map.getZones())
{
dependency(z.second->getModificator<TownPlacer>());
dependency(z.second->getModificator<WaterAdopter>());
postfunction(z.second->getModificator<ConnectionsPlacer>());
postfunction(z.second->getModificator<ObjectManager>());
}
POSTFUNCTION(TreasurePlacer);
}
const std::vector<WaterProxy::Lake> & WaterProxy::getLakes() const
{
RecursiveLock lock(externalAccessMutex);
return lakes;
}
void WaterProxy::collectLakes()
{
RecursiveLock lock(externalAccessMutex);
int lakeId = 0;
for(const auto & lake : connectedAreas(zone.getArea(), true))
{
lakes.push_back(Lake{});
lakes.back().area = lake;
lakes.back().distanceMap = lake.computeDistanceMap(lakes.back().reverseDistanceMap);
for(const auto & t : lake.getBorderOutside())
if(map.isOnMap(t))
lakes.back().neighbourZones[map.getZoneID(t)].add(t);
for(const auto & t : lake.getTiles())
lakeMap[t] = lakeId;
//each lake must have at least one free tile
if(!lake.overlap(zone.freePaths()))
zone.freePaths().add(*lakes.back().reverseDistanceMap[lakes.back().reverseDistanceMap.size() - 1].begin());
++lakeId;
}
}
RouteInfo WaterProxy::waterRoute(Zone & dst)
{
RouteInfo result;
auto * adopter = dst.getModificator<WaterAdopter>();
if(!adopter)
return result;
if(adopter->getCoastTiles().empty())
return result;
//block zones are not connected by template
for(auto& lake : lakes)
{
if(lake.neighbourZones.count(dst.getId()))
{
if(!lake.keepConnections.count(dst.getId()))
{
for(const auto & ct : lake.neighbourZones[dst.getId()].getTiles())
{
if(map.isPossible(ct))
map.setOccupied(ct, ETileType::BLOCKED);
}
Zone::Lock lock(dst.areaMutex);
dst.areaPossible().subtract(lake.neighbourZones[dst.getId()]);
continue;
}
//Don't place shipyard or boats on the very small lake
if (lake.area.getTiles().size() < 25)
{
logGlobal->info("Skipping very small lake at zone %d", dst.getId());
continue;
}
int zoneTowns = 0;
if(auto * m = dst.getModificator<TownPlacer>())
zoneTowns = m->getTotalTowns();
if(dst.getType() == ETemplateZoneType::PLAYER_START || dst.getType() == ETemplateZoneType::CPU_START || zoneTowns)
{
if(placeShipyard(dst, lake, generator.getConfig().shipyardGuard, result))
{
logGlobal->info("Shipyard successfully placed at zone %d", dst.getId());
}
else
{
logGlobal->warn("Shipyard placement failed, trying boat at zone %d", dst.getId());
if(placeBoat(dst, lake, result))
{
logGlobal->warn("Boat successfully placed at zone %d", dst.getId());
}
else
{
logGlobal->error("Boat placement failed at zone %d", dst.getId());
}
}
}
else
{
if(placeBoat(dst, lake, result))
{
logGlobal->info("Boat successfully placed at zone %d", dst.getId());
}
else
{
logGlobal->error("Boat placement failed at zone %d", dst.getId());
}
}
}
}
return result;
}
bool WaterProxy::waterKeepConnection(TRmgTemplateZoneId zoneA, TRmgTemplateZoneId zoneB)
{
for(auto & lake : lakes)
{
if(lake.neighbourZones.count(zoneA) && lake.neighbourZones.count(zoneB))
{
lake.keepConnections.insert(zoneA);
lake.keepConnections.insert(zoneB);
return true;
}
}
return false;
}
bool WaterProxy::placeBoat(Zone & land, const Lake & lake, RouteInfo & info)
{
auto * manager = zone.getModificator<ObjectManager>();
if(!manager)
return false;
auto subObjects = VLC->objtypeh->knownSubObjects(Obj::BOAT);
std::set<si32> sailingBoatTypes; //RMG shall place only sailing boats on water
for(auto subObj : subObjects)
{
//making a temporary object
std::unique_ptr<CGObjectInstance> obj(VLC->objtypeh->getHandlerFor(Obj::BOAT, subObj)->create());
if(auto * testBoat = dynamic_cast<CGBoat *>(obj.get()))
{
if(testBoat->layer == EPathfindingLayer::SAIL)
sailingBoatTypes.insert(subObj);
}
}
if(sailingBoatTypes.empty())
return false;
auto * boat = dynamic_cast<CGBoat *>(VLC->objtypeh->getHandlerFor(Obj::BOAT, *RandomGeneratorUtil::nextItem(sailingBoatTypes, zone.getRand()))->create());
rmg::Object rmgObject(*boat);
rmgObject.setTemplate(zone.getTerrainType());
auto waterAvailable = zone.areaPossible() + zone.freePaths();
rmg::Area coast = lake.neighbourZones.at(land.getId()); //having land tiles
coast.intersect(land.areaPossible() + land.freePaths()); //having only available land tiles
auto boardingPositions = coast.getSubarea([&waterAvailable, this](const int3 & tile) //tiles where boarding is possible
{
//We don't want place boat right to any land object, especiallly the zone guard
if (map.getTileInfo(tile).getNearestObjectDistance() <= 3)
return false;
rmg::Area a({tile});
a = a.getBorderOutside();
a.intersect(waterAvailable);
return !a.empty();
});
while(!boardingPositions.empty())
{
auto boardingPosition = *boardingPositions.getTiles().begin();
rmg::Area shipPositions({boardingPosition});
auto boutside = shipPositions.getBorderOutside();
shipPositions.assign(boutside);
shipPositions.intersect(waterAvailable);
if(shipPositions.empty())
{
boardingPositions.erase(boardingPosition);
continue;
}
//try to place boat at water, create paths on water and land
auto path = manager->placeAndConnectObject(shipPositions, rmgObject, 4, false, true, ObjectManager::OptimizeType::NONE);
auto landPath = land.searchPath(boardingPosition, false);
if(!path.valid() || !landPath.valid())
{
boardingPositions.erase(boardingPosition);
continue;
}
info.blocked = rmgObject.getArea();
info.visitable = rmgObject.getVisitablePosition();
info.boarding = boardingPosition;
info.water = shipPositions;
zone.connectPath(path);
land.connectPath(landPath);
manager->placeObject(rmgObject, false, true);
land.getModificator<ObjectManager>()->updateDistances(rmgObject); //Keep land objects away from the boat
break;
}
return !boardingPositions.empty();
}
bool WaterProxy::placeShipyard(Zone & land, const Lake & lake, si32 guard, RouteInfo & info)
{
auto * manager = land.getModificator<ObjectManager>();
if(!manager)
return false;
int subtype = chooseRandomAppearance(zone.getRand(), Obj::SHIPYARD, land.getTerrainType());
auto * shipyard = dynamic_cast<CGShipyard *>(VLC->objtypeh->getHandlerFor(Obj::SHIPYARD, subtype)->create());
shipyard->tempOwner = PlayerColor::NEUTRAL;
rmg::Object rmgObject(*shipyard);
rmgObject.setTemplate(land.getTerrainType());
bool guarded = manager->addGuard(rmgObject, guard);
auto waterAvailable = zone.areaPossible() + zone.freePaths();
waterAvailable.intersect(lake.area);
rmg::Area coast = lake.neighbourZones.at(land.getId()); //having land tiles
coast.intersect(land.areaPossible() + land.freePaths()); //having only available land tiles
auto boardingPositions = coast.getSubarea([&waterAvailable](const int3 & tile) //tiles where boarding is possible
{
rmg::Area a({tile});
a = a.getBorderOutside();
a.intersect(waterAvailable);
return !a.empty();
});
while(!boardingPositions.empty())
{
auto boardingPosition = *boardingPositions.getTiles().begin();
rmg::Area shipPositions({boardingPosition});
auto boutside = shipPositions.getBorderOutside();
shipPositions.assign(boutside);
shipPositions.intersect(waterAvailable);
if(shipPositions.empty())
{
boardingPositions.erase(boardingPosition);
continue;
}
//try to place shipyard close to boarding position and appropriate water access
auto path = manager->placeAndConnectObject(land.areaPossible(), rmgObject, [&rmgObject, &shipPositions, &boardingPosition](const int3 & tile)
{
//Must only check the border of shipyard and not the added guard
rmg::Area shipyardOut = rmgObject.instances().front()->getBlockedArea().getBorderOutside();
if(!shipyardOut.contains(boardingPosition) || (shipyardOut * shipPositions).empty())
return -1.f;
return 1.0f;
}, guarded, true, ObjectManager::OptimizeType::NONE);
//search path to boarding position
auto searchArea = land.areaPossible() - rmgObject.getArea();
rmg::Path pathToBoarding(searchArea);
pathToBoarding.connect(land.freePaths());
pathToBoarding.connect(path);
pathToBoarding = pathToBoarding.search(boardingPosition, false);
//make sure shipyard places ship at position we defined
rmg::Area shipyardOutToBlock(rmgObject.getArea().getBorderOutside());
shipyardOutToBlock.intersect(waterAvailable);
shipyardOutToBlock.subtract(shipPositions);
shipPositions.subtract(shipyardOutToBlock);
auto pathToBoat = zone.searchPath(shipPositions, true);
if(!path.valid() || !pathToBoarding.valid() || !pathToBoat.valid())
{
boardingPositions.erase(boardingPosition);
continue;
}
land.connectPath(path);
land.connectPath(pathToBoarding);
zone.connectPath(pathToBoat);
info.blocked = rmgObject.getArea();
info.visitable = rmgObject.getVisitablePosition();
info.boarding = boardingPosition;
info.water = shipPositions;
manager->placeObject(rmgObject, guarded, true);
zone.areaPossible().subtract(shipyardOutToBlock);
for(const auto & i : shipyardOutToBlock.getTilesVector())
if(map.isOnMap(i) && map.isPossible(i))
map.setOccupied(i, ETileType::BLOCKED);
break;
}
return !boardingPositions.empty();
}
char WaterProxy::dump(const int3 & t)
{
auto lakeIter = lakeMap.find(t);
if(lakeIter == lakeMap.end())
return '?';
Lake & lake = lakes[lakeMap.at(t)];
for(const auto & i : lake.neighbourZones)
{
if(i.second.contains(t))
return lake.keepConnections.count(i.first) ? std::to_string(i.first)[0] : '=';
}
return '~';
}
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,59 @@
/*
* WaterProxy.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 "../Zone.h"
VCMI_LIB_NAMESPACE_BEGIN
struct RouteInfo
{
rmg::Area blocked;
int3 visitable;
int3 boarding;
rmg::Area water;
};
class WaterProxy: public Modificator
{
public:
MODIFICATOR(WaterProxy);
//subclass to store disconnected parts of water zone
struct Lake
{
rmg::Area area; //water tiles
std::map<int3, int> distanceMap; //distance map for lake
std::map<int, rmg::Tileset> reverseDistanceMap;
std::map<TRmgTemplateZoneId, rmg::Area> neighbourZones; //zones boardered. Area - part of land
std::set<TRmgTemplateZoneId> keepConnections;
};
bool waterKeepConnection(TRmgTemplateZoneId zoneA, TRmgTemplateZoneId zoneB);
RouteInfo waterRoute(Zone & dst);
void process() override;
void init() override;
char dump(const int3 &) override;
const std::vector<Lake> & getLakes() const;
protected:
void collectLakes();
bool placeShipyard(Zone & land, const Lake & lake, si32 guard, RouteInfo & info);
bool placeBoat(Zone & land, const Lake & lake, RouteInfo & info);
protected:
std::vector<Lake> lakes; //disconnected parts of zone. Used to work with water zones
std::map<int3, int> lakeMap; //map tile on lakeId which is position of lake in lakes array +1
};
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,125 @@
/*
* WaterRoutes.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 "WaterRoutes.h"
#include "WaterProxy.h"
#include "../CMapGenerator.h"
#include "../RmgMap.h"
#include "../../mapping/CMap.h"
#include "../../mapping/CMapEditManager.h"
#include "../../mapObjects/CObjectClassesHandler.h"
#include "../RmgPath.h"
#include "../RmgObject.h"
#include "ObjectManager.h"
#include "../Functions.h"
#include "RoadPlacer.h"
#include "TreasurePlacer.h"
#include "TownPlacer.h"
#include "ConnectionsPlacer.h"
#include "../TileInfo.h"
#include "WaterAdopter.h"
#include "../RmgArea.h"
VCMI_LIB_NAMESPACE_BEGIN
void WaterRoutes::process()
{
auto * wproxy = zone.getModificator<WaterProxy>();
if(!wproxy)
return;
if(auto * manager = zone.getModificator<ObjectManager>())
manager->createDistancesPriorityQueue();
for(auto & z : map.getZones())
{
if(z.first != zone.getId())
result.push_back(wproxy->waterRoute(*z.second));
}
Zone::Lock lock(zone.areaMutex);
//prohibit to place objects on sealed off lakes
for(const auto & lake : wproxy->getLakes())
{
if((lake.area * zone.freePaths()).getTilesVector().size() == 1)
{
zone.freePaths().subtract(lake.area);
zone.areaPossible().subtract(lake.area);
}
}
//prohibit to place objects on the borders
for(const auto & t : zone.area().getBorder())
{
if(zone.areaPossible().contains(t))
{
std::vector<int3> landTiles;
map.foreachDirectNeighbour(t, [this, &landTiles, &t](const int3 & c)
{
if(map.isOnMap(c) && map.getZoneID(c) != zone.getId())
{
landTiles.push_back(c - t);
}
});
if(landTiles.size() == 2)
{
int3 o = landTiles[0] + landTiles[1];
if(o.x * o.x * o.y * o.y == 1)
{
zone.areaPossible().erase(t);
zone.areaUsed().add(t);
}
}
}
}
}
void WaterRoutes::init()
{
for(auto & z : map.getZones())
{
dependency(z.second->getModificator<ConnectionsPlacer>());
postfunction(z.second->getModificator<ObjectManager>());
postfunction(z.second->getModificator<TreasurePlacer>());
}
dependency(zone.getModificator<WaterProxy>());
postfunction(zone.getModificator<TreasurePlacer>());
}
char WaterRoutes::dump(const int3 & t)
{
for(auto & i : result)
{
if(t == i.boarding)
return 'B';
if(t == i.visitable)
return '@';
if(i.blocked.contains(t))
return '#';
if(i.water.contains(t))
{
if(zone.freePaths().contains(t))
return '+';
else
return '-';
}
}
if(zone.freePaths().contains(t))
return '.';
if(zone.area().contains(t))
return '~';
return ' ';
}
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,31 @@
/*
* WaterRoutes.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 "../Zone.h"
VCMI_LIB_NAMESPACE_BEGIN
struct RouteInfo;
class WaterRoutes: public Modificator
{
public:
MODIFICATOR(WaterRoutes);
void process() override;
void init() override;
char dump(const int3 &) override;
private:
std::vector<RouteInfo> result;
};
VCMI_LIB_NAMESPACE_END