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:
346
lib/rmg/modificators/ConnectionsPlacer.cpp
Normal file
346
lib/rmg/modificators/ConnectionsPlacer.cpp
Normal 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
|
||||
40
lib/rmg/modificators/ConnectionsPlacer.h
Normal file
40
lib/rmg/modificators/ConnectionsPlacer.h
Normal 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
|
||||
98
lib/rmg/modificators/MinePlacer.cpp
Normal file
98
lib/rmg/modificators/MinePlacer.cpp
Normal 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
|
||||
30
lib/rmg/modificators/MinePlacer.h
Normal file
30
lib/rmg/modificators/MinePlacer.h
Normal 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
|
||||
159
lib/rmg/modificators/Modificator.cpp
Normal file
159
lib/rmg/modificators/Modificator.cpp
Normal 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
|
||||
81
lib/rmg/modificators/Modificator.h
Normal file
81
lib/rmg/modificators/Modificator.h
Normal 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
|
||||
171
lib/rmg/modificators/ObjectDistributor.cpp
Normal file
171
lib/rmg/modificators/ObjectDistributor.cpp
Normal 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
|
||||
34
lib/rmg/modificators/ObjectDistributor.h
Normal file
34
lib/rmg/modificators/ObjectDistributor.h
Normal 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
|
||||
545
lib/rmg/modificators/ObjectManager.cpp
Normal file
545
lib/rmg/modificators/ObjectManager.cpp
Normal 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
|
||||
84
lib/rmg/modificators/ObjectManager.h
Normal file
84
lib/rmg/modificators/ObjectManager.h
Normal 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
|
||||
101
lib/rmg/modificators/ObstaclePlacer.cpp
Normal file
101
lib/rmg/modificators/ObstaclePlacer.cpp
Normal 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
|
||||
46
lib/rmg/modificators/ObstaclePlacer.h
Normal file
46
lib/rmg/modificators/ObstaclePlacer.h
Normal 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
|
||||
149
lib/rmg/modificators/QuestArtifactPlacer.cpp
Normal file
149
lib/rmg/modificators/QuestArtifactPlacer.cpp
Normal 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
|
||||
51
lib/rmg/modificators/QuestArtifactPlacer.h
Normal file
51
lib/rmg/modificators/QuestArtifactPlacer.h
Normal 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
|
||||
405
lib/rmg/modificators/RiverPlacer.cpp
Normal file
405
lib/rmg/modificators/RiverPlacer.cpp
Normal 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
|
||||
52
lib/rmg/modificators/RiverPlacer.h
Normal file
52
lib/rmg/modificators/RiverPlacer.h
Normal 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
|
||||
147
lib/rmg/modificators/RoadPlacer.cpp
Normal file
147
lib/rmg/modificators/RoadPlacer.cpp
Normal 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
|
||||
41
lib/rmg/modificators/RoadPlacer.h
Normal file
41
lib/rmg/modificators/RoadPlacer.h
Normal 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
|
||||
78
lib/rmg/modificators/RockFiller.cpp
Normal file
78
lib/rmg/modificators/RockFiller.cpp
Normal 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
|
||||
28
lib/rmg/modificators/RockFiller.h
Normal file
28
lib/rmg/modificators/RockFiller.h
Normal 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
|
||||
82
lib/rmg/modificators/RockPlacer.cpp
Normal file
82
lib/rmg/modificators/RockPlacer.cpp
Normal 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
|
||||
35
lib/rmg/modificators/RockPlacer.h
Normal file
35
lib/rmg/modificators/RockPlacer.h
Normal 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
|
||||
105
lib/rmg/modificators/TerrainPainter.cpp
Normal file
105
lib/rmg/modificators/TerrainPainter.cpp
Normal 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
|
||||
27
lib/rmg/modificators/TerrainPainter.h
Normal file
27
lib/rmg/modificators/TerrainPainter.h
Normal 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
|
||||
246
lib/rmg/modificators/TownPlacer.cpp
Normal file
246
lib/rmg/modificators/TownPlacer.cpp
Normal 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
|
||||
40
lib/rmg/modificators/TownPlacer.h
Normal file
40
lib/rmg/modificators/TownPlacer.h
Normal 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
|
||||
858
lib/rmg/modificators/TreasurePlacer.cpp
Normal file
858
lib/rmg/modificators/TreasurePlacer.cpp
Normal 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
|
||||
72
lib/rmg/modificators/TreasurePlacer.h
Normal file
72
lib/rmg/modificators/TreasurePlacer.h
Normal 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
|
||||
270
lib/rmg/modificators/WaterAdopter.cpp
Normal file
270
lib/rmg/modificators/WaterAdopter.cpp
Normal 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
|
||||
40
lib/rmg/modificators/WaterAdopter.h
Normal file
40
lib/rmg/modificators/WaterAdopter.h
Normal 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
|
||||
400
lib/rmg/modificators/WaterProxy.cpp
Normal file
400
lib/rmg/modificators/WaterProxy.cpp
Normal 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
|
||||
59
lib/rmg/modificators/WaterProxy.h
Normal file
59
lib/rmg/modificators/WaterProxy.h
Normal 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
|
||||
125
lib/rmg/modificators/WaterRoutes.cpp
Normal file
125
lib/rmg/modificators/WaterRoutes.cpp
Normal 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
|
||||
31
lib/rmg/modificators/WaterRoutes.h
Normal file
31
lib/rmg/modificators/WaterRoutes.h
Normal 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
|
||||
Reference in New Issue
Block a user