1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-08-13 19:54:17 +02:00

Random map generator refactoring (#762)

random map generator refactoring and improvements
This commit is contained in:
Nordsoft91
2022-08-09 09:54:32 +04:00
committed by Andrii Danylchenko
parent 804f1bf3f2
commit 4bd0ff680a
60 changed files with 6721 additions and 4552 deletions

View File

@@ -11,12 +11,12 @@
#include "../lib/CConfigHandler.h"
#include "../lib/CSoundBase.h"
#include "../lib/Terrain.h"
struct _Mix_Music;
struct SDL_RWops;
typedef struct _Mix_Music Mix_Music;
struct Mix_Chunk;
class Terrain;
class CAudioBase {
protected:

View File

@@ -11,6 +11,7 @@
#include "ObjectLists.h"
#include "../../lib/FunctionList.h"
#include "Terrain.h"
class CArmedInstance;
class CAnimation;
@@ -30,7 +31,6 @@ struct InfoAboutTown;
class CHeroTooltip;
class CTownTooltip;
class CTextBox;
class Terrain;
/// Base UI Element for hero\town lists
class CList : public CIntObject

View File

@@ -18,16 +18,6 @@
},
"mines" :
{
"value" :
{
"wood" : 1500,
"ore" : 1500,
"gems" : 3500,
"crystal" : 3500,
"mercury" : 3500,
"sulfur" : 3500,
"gold" : 7000
},
"extraResourcesLimit" : 3
},
"minGuardStrength" : 2000,

View File

@@ -7,6 +7,7 @@
"music" : "Dirt.mp3",
"tiles" : "DIRTTL",
"code" : "dt",
"river" : "rm",
"battleFields" : ["dirt_birches", "dirt_hills", "dirt_pines"],
"terrainViewPatterns" : "dirt"
},
@@ -18,6 +19,7 @@
"music" : "Sand.mp3",
"tiles" : "SANDTL",
"code" : "sa",
"river" : "rm",
"battleFields" : ["sand_mesas"],
"transitionRequired" : true,
"terrainViewPatterns" : "sand"
@@ -30,6 +32,7 @@
"music" : "Grass.mp3",
"tiles" : "GRASTL",
"code" : "gr",
"river" : "rw",
"battleFields" : ["grass_hills", "grass_pines"]
},
"snow" :
@@ -40,6 +43,7 @@
"music" : "Snow.mp3",
"tiles" : "SNOWTL",
"code" : "sn",
"river" : "ri",
"battleFields" : ["snow_mountains", "snow_trees"]
},
"swamp" :
@@ -50,6 +54,7 @@
"music" : "Swamp.mp3",
"tiles" : "SWMPTL",
"code" : "sw",
"river" : "rw",
"battleFields" : ["swamp_trees"]
},
"rough" :
@@ -60,6 +65,7 @@
"music" : "Rough.mp3",
"tiles" : "ROUGTL",
"code" : "rg",
"river" : "rm",
"battleFields" : ["rough"]
},
"subterra" :
@@ -71,7 +77,9 @@
"tiles" : "SUBBTL",
"type" : "SUB",
"code" : "sb",
"battleFields" : ["subterranean"]
"river" : "rw",
"battleFields" : ["subterranean"],
"rockTerrain" : "rock"
},
"lava" :
{
@@ -81,7 +89,9 @@
"music" : "Lava.mp3",
"tiles" : "LAVATL",
"code" : "lv",
"battleFields" : ["lava"]
"river" : "rl",
"battleFields" : ["lava"],
"rockTerrain" : "rock"
},
"water" :
{

View File

@@ -19,12 +19,12 @@
#include "JsonNode.h"
#include "IHandlerBase.h"
#include "CRandomGenerator.h"
#include "Terrain.h"
class CLegacyConfigParser;
class CCreatureHandler;
class CCreature;
class JsonSerializeFormat;
class Terrain;
class DLL_LINKAGE CCreature : public Creature, public CBonusSystemNode
{

View File

@@ -18,6 +18,7 @@
#include "GameConstants.h"
#include "HeroBonus.h"
#include "IHandlerBase.h"
#include "Terrain.h"
class CHeroClass;
class CGameInfo;
@@ -26,7 +27,6 @@ struct BattleHex;
class JsonNode;
class CRandomGenerator;
class JsonSerializeFormat;
class Terrain;
class BattleField;
struct SSpecialtyInfo

View File

@@ -81,12 +81,30 @@ set(lib_SRCS
registerTypes/TypesLobbyPacks.cpp
registerTypes/TypesServerPacks.cpp
rmg/RmgArea.cpp
rmg/RmgObject.cpp
rmg/RmgPath.cpp
rmg/CMapGenerator.cpp
rmg/CMapGenOptions.cpp
rmg/CRmgTemplate.cpp
rmg/CRmgTemplateStorage.cpp
rmg/CRmgTemplateZone.cpp
rmg/CZonePlacer.cpp
rmg/TileInfo.cpp
rmg/Zone.cpp
rmg/Functions.cpp
rmg/ObjectManager.cpp
rmg/RoadPlacer.cpp
rmg/TreasurePlacer.cpp
rmg/RmgMap.cpp
rmg/ConnectionsPlacer.cpp
rmg/WaterAdopter.cpp
rmg/TownPlacer.cpp
rmg/WaterProxy.cpp
rmg/WaterRoutes.cpp
rmg/RockPlacer.cpp
rmg/ObstaclePlacer.cpp
rmg/RiverPlacer.cpp
rmg/TerrainPainter.cpp
serializer/BinaryDeserializer.cpp
serializer/BinarySerializer.cpp
@@ -288,12 +306,30 @@ set(lib_HEADERS
registerTypes/RegisterTypes.h
rmg/RmgArea.h
rmg/RmgObject.h
rmg/RmgPath.h
rmg/CMapGenerator.h
rmg/CMapGenOptions.h
rmg/CRmgTemplate.h
rmg/CRmgTemplateStorage.h
rmg/CRmgTemplateZone.h
rmg/CZonePlacer.h
rmg/TileInfo.h
rmg/Zone.h
rmg/Functions.h
rmg/ObjectManager.h
rmg/RoadPlacer.h
rmg/TreasurePlacer.h
rmg/RmgMap.h
rmg/ConnectionsPlacer.h
rmg/WaterAdopter.h
rmg/TownPlacer.h
rmg/WaterProxy.h
rmg/WaterRoutes.h
rmg/RockPlacer.h
rmg/ObstaclePlacer.h
rmg/RiverPlacer.h
rmg/TerrainPainter.h
rmg/float3.h
serializer/BinaryDeserializer.h

View File

@@ -8,6 +8,7 @@
*
*/
#include "StdInc.h"
#include "Terrain.h"
#include "VCMI_Lib.h"
#include "CModHandler.h"
@@ -78,8 +79,26 @@ Terrain::Manager::Manager()
auto s = terr.second["type"].String();
if(s == "LAND") info.type = Terrain::Info::Type::Land;
if(s == "WATER") info.type = Terrain::Info::Type::Water;
if(s == "SUB") info.type = Terrain::Info::Type::Subterranean;
if(s == "ROCK") info.type = Terrain::Info::Type::Rock;
if(s == "SUB") info.type = Terrain::Info::Type::Subterranean;
}
if(terr.second["rockTerrain"].isNull())
{
info.rockTerrain = "rock";
}
else
{
info.rockTerrain = terr.second["rockTerrain"].String();
}
if(terr.second["river"].isNull())
{
info.river = RIVER_NAMES[0];
}
else
{
info.river = terr.second["river"].String();
}
if(terr.second["horseSoundId"].isNull())
@@ -114,6 +133,14 @@ Terrain::Manager::Manager()
}
}
if(!terr.second["prohibitTransitions"].isNull())
{
for(auto & t : terr.second["prohibitTransitions"].Vector())
{
info.prohibitTransitions.emplace_back(t.String());
}
}
info.transitionRequired = false;
if(!terr.second["transitionRequired"].isNull())
{
@@ -126,7 +153,7 @@ Terrain::Manager::Manager()
info.terrainViewPatterns = terr.second["terrainViewPatterns"].String();
}
terrainInfo[Terrain(terr.first)] = info;
terrainInfo[terr.first] = info;
}
}
}
@@ -139,15 +166,15 @@ Terrain::Manager & Terrain::Manager::get()
std::vector<Terrain> Terrain::Manager::terrains()
{
std::vector<Terrain> _terrains;
std::set<Terrain> _terrains; //have to use std::set to have ordered container. Othervise de-sync is possible
for(const auto & info : Terrain::Manager::get().terrainInfo)
_terrains.push_back(info.first);
return _terrains;
_terrains.insert(info.first);
return std::vector<Terrain>(_terrains.begin(), _terrains.end());
}
const Terrain::Info & Terrain::Manager::getInfo(const Terrain & terrain)
{
return Terrain::Manager::get().terrainInfo.at(terrain);
return Terrain::Manager::get().terrainInfo.at(static_cast<std::string>(terrain));
}
std::ostream & operator<<(std::ostream & os, const Terrain terrainType)

View File

@@ -37,9 +37,12 @@ public:
std::string terrainText;
std::string typeCode;
std::string terrainViewPatterns;
std::string rockTerrain;
std::string river;
int horseSoundId;
Type type;
std::vector<std::string> battleFields;
std::vector<Terrain> prohibitTransitions;
};
class DLL_LINKAGE Manager
@@ -52,7 +55,7 @@ public:
static Manager & get();
Manager();
std::map<Terrain, Info> terrainInfo;
std::unordered_map<std::string, Info> terrainInfo;
};
/*enum EETerrainType

View File

@@ -12,7 +12,7 @@
#include "CDrawRoadsOperation.h"
#include "CMap.h"
const std::vector<CDrawRoadsOperation::RoadPattern> CDrawRoadsOperation::patterns =
const std::vector<CDrawLinesOperation::LinePattern> CDrawLinesOperation::patterns =
{
//single tile. fall-back pattern
{
@@ -147,21 +147,31 @@ static bool ruleIsAny(const std::string & rule)
}
#endif
///CDrawRoadsOperation
CDrawRoadsOperation::CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, const std::string & roadType, CRandomGenerator * gen):
CMapOperation(map),terrainSel(terrainSel), roadType(roadType), gen(gen)
///CDrawLinesOperation
CDrawLinesOperation::CDrawLinesOperation(CMap * map, const CTerrainSelection & terrainSel, CRandomGenerator * gen):
CMapOperation(map), terrainSel(terrainSel), gen(gen)
{
}
void CDrawRoadsOperation::execute()
///CDrawRoadsOperation
CDrawRoadsOperation::CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, const std::string & roadType, CRandomGenerator * gen):
CDrawLinesOperation(map, terrainSel, gen), roadType(roadType)
{
}
///CDrawRiversOperation
CDrawRiversOperation::CDrawRiversOperation(CMap * map, const CTerrainSelection & terrainSel, const std::string & riverType, CRandomGenerator * gen):
CDrawLinesOperation(map, terrainSel, gen), riverType(riverType)
{
}
void CDrawLinesOperation::execute()
{
std::set<int3> invalidated;
for(const auto & pos : terrainSel.getSelectedItems())
{
auto & tile = map->getTile(pos);
tile.roadType = roadType;
executeTile(map->getTile(pos));
auto rect = extendTileAroundSafely(pos);
rect.forEach([&invalidated](const int3 & pos)
@@ -173,28 +183,17 @@ void CDrawRoadsOperation::execute()
updateTiles(invalidated);
}
void CDrawRoadsOperation::undo()
void CDrawLinesOperation::undo()
{
//TODO
}
void CDrawRoadsOperation::redo()
void CDrawLinesOperation::redo()
{
//TODO
}
std::string CDrawRoadsOperation::getLabel() const
{
return "Draw Roads";
}
bool CDrawRoadsOperation::canApplyPattern(const RoadPattern & pattern) const
{
//TODO: this method should be virtual for river support
return pattern.roadMapping.first >= 0;
}
void CDrawRoadsOperation::flipPattern(RoadPattern& pattern, int flip) const
void CDrawLinesOperation::flipPattern(LinePattern& pattern, int flip) const
{
//todo: use cashing here and also in terrain patterns
@@ -222,13 +221,7 @@ void CDrawRoadsOperation::flipPattern(RoadPattern& pattern, int flip) const
}
}
bool CDrawRoadsOperation::needUpdateTile(const TerrainTile & tile) const
{
return tile.roadType != ROAD_NAMES[0]; //TODO: this method should be virtual for river support
}
void CDrawRoadsOperation::updateTiles(std::set<int3> & invalidated)
void CDrawLinesOperation::updateTiles(std::set<int3> & invalidated)
{
for(int3 coord : invalidated)
{
@@ -259,25 +252,7 @@ void CDrawRoadsOperation::updateTiles(std::set<int3> & invalidated)
}
}
bool CDrawRoadsOperation::tileHasSomething(const int3& pos) const
{
//TODO: this method should be virtual for river support
return map->getTile(pos).roadType != ROAD_NAMES[0];
}
void CDrawRoadsOperation::updateTile(TerrainTile & tile, const RoadPattern & pattern, const int flip)
{
//TODO: this method should be virtual for river support
const std::pair<int, int> & mapping = pattern.roadMapping;
tile.roadDir = gen->nextInt(mapping.first, mapping.second);
tile.extTileFlags = (tile.extTileFlags & 0xCF) | (flip << 4);
}
CDrawRoadsOperation::ValidationResult CDrawRoadsOperation::validateTile(const RoadPattern & pattern, const int3 & pos)
CDrawLinesOperation::ValidationResult CDrawLinesOperation::validateTile(const LinePattern & pattern, const int3 & pos)
{
ValidationResult result(false);
@@ -294,7 +269,7 @@ CDrawRoadsOperation::ValidationResult CDrawRoadsOperation::validateTile(const Ro
if((flip == FLIP_PATTERN_VERTICAL) && !(pattern.hasVFlip))
continue;
RoadPattern flipped = pattern;
LinePattern flipped = pattern;
flipPattern(flipped, flip);
@@ -344,3 +319,69 @@ CDrawRoadsOperation::ValidationResult CDrawRoadsOperation::validateTile(const Ro
return result;
}
std::string CDrawRoadsOperation::getLabel() const
{
return "Draw Roads";
}
std::string CDrawRiversOperation::getLabel() const
{
return "Draw Rivers";
}
void CDrawRoadsOperation::executeTile(TerrainTile & tile)
{
tile.roadType = roadType;
}
void CDrawRiversOperation::executeTile(TerrainTile & tile)
{
tile.riverType = riverType;
}
bool CDrawRoadsOperation::canApplyPattern(const LinePattern & pattern) const
{
return pattern.roadMapping.first >= 0;
}
bool CDrawRiversOperation::canApplyPattern(const LinePattern & pattern) const
{
return pattern.riverMapping.first >= 0;
}
bool CDrawRoadsOperation::needUpdateTile(const TerrainTile & tile) const
{
return tile.roadType != ROAD_NAMES[0];
}
bool CDrawRiversOperation::needUpdateTile(const TerrainTile & tile) const
{
return tile.riverType != RIVER_NAMES[0];
}
bool CDrawRoadsOperation::tileHasSomething(const int3& pos) const
{
return map->getTile(pos).roadType != ROAD_NAMES[0];
}
bool CDrawRiversOperation::tileHasSomething(const int3& pos) const
{
return map->getTile(pos).riverType != RIVER_NAMES[0];
}
void CDrawRoadsOperation::updateTile(TerrainTile & tile, const LinePattern & pattern, const int flip)
{
const std::pair<int, int> & mapping = pattern.roadMapping;
tile.roadDir = gen->nextInt(mapping.first, mapping.second);
tile.extTileFlags = (tile.extTileFlags & 0b11001111) | (flip << 4);
}
void CDrawRiversOperation::updateTile(TerrainTile & tile, const LinePattern & pattern, const int flip)
{
const std::pair<int, int> & mapping = pattern.riverMapping;
tile.riverDir = gen->nextInt(mapping.first, mapping.second);
tile.extTileFlags = (tile.extTileFlags & 0b00111111) | (flip << 2);
}

View File

@@ -15,17 +15,16 @@
struct TerrainTile;
class CDrawRoadsOperation : public CMapOperation
class CDrawLinesOperation : public CMapOperation
{
public:
CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, const std::string & roadType, CRandomGenerator * gen);
void execute() override;
void undo() override;
void redo() override;
std::string getLabel() const override;
private:
struct RoadPattern
protected:
struct LinePattern
{
std::string data[9];
std::pair<int, int> roadMapping, riverMapping;
@@ -39,20 +38,56 @@ private:
int flip;
};
static const std::vector<RoadPattern> patterns;
CDrawLinesOperation(CMap * map, const CTerrainSelection & terrainSel, CRandomGenerator * gen);
void flipPattern(RoadPattern & pattern, int flip) const;
virtual void executeTile(TerrainTile & tile) = 0;
virtual bool canApplyPattern(const CDrawLinesOperation::LinePattern & pattern) const = 0;
virtual bool needUpdateTile(const TerrainTile & tile) const = 0;
virtual bool tileHasSomething(const int3 & pos) const = 0;
virtual void updateTile(TerrainTile & tile, const CDrawLinesOperation::LinePattern & pattern, const int flip) = 0;
static const std::vector<LinePattern> patterns;
void flipPattern(LinePattern & pattern, int flip) const;
void updateTiles(std::set<int3> & invalidated);
ValidationResult validateTile(const RoadPattern & pattern, const int3 & pos);
void updateTile(TerrainTile & tile, const RoadPattern & pattern, const int flip);
bool canApplyPattern(const RoadPattern & pattern) const;
bool needUpdateTile(const TerrainTile & tile) const;
bool tileHasSomething(const int3 & pos) const;
ValidationResult validateTile(const LinePattern & pattern, const int3 & pos);
CTerrainSelection terrainSel;
std::string roadType;
CRandomGenerator * gen;
CRandomGenerator * gen;
};
class CDrawRoadsOperation : public CDrawLinesOperation
{
public:
CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, const std::string & roadType, CRandomGenerator * gen);
std::string getLabel() const override;
protected:
void executeTile(TerrainTile & tile) override;
bool canApplyPattern(const CDrawLinesOperation::LinePattern & pattern) const override;
bool needUpdateTile(const TerrainTile & tile) const override;
bool tileHasSomething(const int3 & pos) const override;
void updateTile(TerrainTile & tile, const CDrawLinesOperation::LinePattern & pattern, const int flip) override;
private:
std::string roadType;
};
class CDrawRiversOperation : public CDrawLinesOperation
{
public:
CDrawRiversOperation(CMap * map, const CTerrainSelection & terrainSel, const std::string & roadType, CRandomGenerator * gen);
std::string getLabel() const override;
protected:
void executeTile(TerrainTile & tile) override;
bool canApplyPattern(const CDrawLinesOperation::LinePattern & pattern) const override;
bool needUpdateTile(const TerrainTile & tile) const override;
bool tileHasSomething(const int3 & pos) const override;
void updateTile(TerrainTile & tile, const CDrawLinesOperation::LinePattern & pattern, const int flip) override;
private:
std::string riverType;
};

View File

@@ -110,7 +110,7 @@ void CTerrainSelection::deselectRange(const MapRect & rect)
});
}
void CTerrainSelection::setSelection(std::vector<int3> & vec)
void CTerrainSelection::setSelection(const std::vector<int3> & vec)
{
for (auto pos : vec)
this->select(pos);
@@ -255,6 +255,13 @@ void CMapEditManager::drawRoad(const std::string & roadType, CRandomGenerator* g
terrainSel.clearSelection();
}
void CMapEditManager::drawRiver(const std::string & riverType, CRandomGenerator* gen)
{
execute(make_unique<CDrawRiversOperation>(map, terrainSel, riverType, gen ? gen : &(this->gen)));
terrainSel.clearSelection();
}
void CMapEditManager::insertObject(CGObjectInstance * obj)
{
@@ -818,7 +825,7 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi
else
{
terType = map->getTile(currentPos).terType;
if(terType != centerTerType)
if(terType != centerTerType && (terType.isPassable() || centerTerType.isPassable()))
{
isAlien = true;
}

View File

@@ -93,7 +93,7 @@ public:
void deselectRange(const MapRect & rect) override;
void selectAll() override;
void clearSelection() override;
void setSelection(std::vector<int3> & vec);
void setSelection(const std::vector<int3> & vec);
};
/// Selection class to select objects.
@@ -173,6 +173,9 @@ public:
/// Draws roads at the current terrain selection. The selection will be cleared automatically.
void drawRoad(const std::string & roadType, CRandomGenerator * gen = nullptr);
/// Draws rivers at the current terrain selection. The selection will be cleared automatically.
void drawRiver(const std::string & riverType, CRandomGenerator * gen = nullptr);
void insertObject(CGObjectInstance * obj);

View File

@@ -208,6 +208,7 @@ void CMapGenOptions::setMapTemplate(const CRmgTemplate * value)
void CMapGenOptions::finalize(CRandomGenerator & rand)
{
logGlobal->info("RMG map: %dx%d, %s underground", getWidth(), getHeight(), getHasTwoLevels() ? "WITH" : "NO");
logGlobal->info("RMG settings: players %d, teams %d, computer players %d, computer teams %d, water %d, monsters %d",
static_cast<int>(getPlayerCount()), static_cast<int>(getTeamCount()), static_cast<int>(getCompOnlyPlayerCount()),
static_cast<int>(getCompOnlyTeamCount()), static_cast<int>(getWaterContent()), static_cast<int>(getMonsterStrength()));
@@ -217,6 +218,8 @@ void CMapGenOptions::finalize(CRandomGenerator & rand)
mapTemplate = getPossibleTemplate(rand);
}
assert(mapTemplate);
logGlobal->info("RMG template name: %s", mapTemplate->getName());
if (getPlayerCount() == RANDOM_SIZE)
{

View File

@@ -18,67 +18,32 @@
#include "../StringConstants.h"
#include "../filesystem/Filesystem.h"
#include "CZonePlacer.h"
#include "CRmgTemplateZone.h"
#include "../mapObjects/CObjectClassesHandler.h"
static const int3 dirs4[] = {int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0)};
static const int3 dirsDiagonal[] = { int3(1,1,0),int3(1,-1,0),int3(-1,1,0),int3(-1,-1,0) };
void CMapGenerator::foreach_neighbour(const int3 &pos, std::function<void(int3& pos)> foo)
{
for(const int3 &dir : int3::getDirs())
{
int3 n = pos + dir;
/*important notice: perform any translation before this function is called,
so the actual map position is checked*/
if(map->isInTheMap(n))
foo(n);
}
}
void CMapGenerator::foreachDirectNeighbour(const int3& pos, std::function<void(int3& pos)> foo)
{
for(const int3 &dir : dirs4)
{
int3 n = pos + dir;
if(map->isInTheMap(n))
foo(n);
}
}
void CMapGenerator::foreachDiagonalNeighbour(const int3& pos, std::function<void(int3& pos)> foo)
{
for (const int3 &dir : dirsDiagonal)
{
int3 n = pos + dir;
if (map->isInTheMap(n))
foo(n);
}
}
#include "TileInfo.h"
#include "Zone.h"
#include "Functions.h"
#include "RmgMap.h"
#include "ObjectManager.h"
#include "TreasurePlacer.h"
#include "RoadPlacer.h"
CMapGenerator::CMapGenerator(CMapGenOptions& mapGenOptions, int RandomSeed) :
mapGenOptions(mapGenOptions), randomSeed(RandomSeed),
zonesTotal(0), tiles(nullptr), prisonsRemaining(0),
monolithIndex(0)
prisonsRemaining(0), monolithIndex(0)
{
loadConfig();
rand.setSeed(this->randomSeed);
mapGenOptions.finalize(rand);
map = std::make_unique<RmgMap>(mapGenOptions);
}
int CMapGenerator::getRandomSeed() const
{
return randomSeed;
}
void CMapGenerator::loadConfig()
{
static const std::map<std::string, Res::ERes> resMap
{
{"wood", Res::ERes::WOOD},
{"ore", Res::ERes::ORE},
{"gems", Res::ERes::GEMS},
{"crystal", Res::ERes::CRYSTAL},
{"mercury", Res::ERes::MERCURY},
{"sulfur", Res::ERes::SULFUR},
{"gold", Res::ERes::GOLD},
};
static const ResourceID path("config/randomMap.json");
JsonNode randomMapJson(path);
for(auto& s : randomMapJson["terrain"]["undergroundAllow"].Vector())
@@ -96,10 +61,6 @@ void CMapGenerator::loadConfig()
{
config.waterTreasure.emplace_back(treasure["min"].Integer(), treasure["max"].Integer(), treasure["density"].Integer());
}
for(auto& s : resMap)
{
config.mineValues[s.second] = randomMapJson["mines"]["value"][s.first].Integer();
}
config.mineExtraResources = randomMapJson["mines"]["extraResourcesLimit"].Integer();
config.minGuardStrength = randomMapJson["minGuardStrength"].Integer();
config.defaultRoadType = randomMapJson["defaultRoadType"].String();
@@ -128,49 +89,19 @@ const CMapGenerator::Config & CMapGenerator::getConfig() const
return config;
}
void CMapGenerator::initTiles()
{
map->initTerrain();
int width = map->width;
int height = map->height;
int level = map->twoLevel ? 2 : 1;
tiles = new CTileInfo**[width];
for (int i = 0; i < width; ++i)
{
tiles[i] = new CTileInfo*[height];
for (int j = 0; j < height; ++j)
{
tiles[i][j] = new CTileInfo[level];
}
}
zoneColouring.resize(boost::extents[map->twoLevel ? 2 : 1][map->width][map->height]);
}
CMapGenerator::~CMapGenerator()
{
if (tiles)
{
int width = mapGenOptions.getWidth();
int height = mapGenOptions.getHeight();
for (int i=0; i < width; i++)
{
for(int j=0; j < height; j++)
{
delete [] tiles[i][j];
}
delete [] tiles[i];
}
delete [] tiles;
}
}
const CMapGenOptions& CMapGenerator::getMapGenOptions() const
{
return mapGenOptions;
}
void CMapGenerator::initPrisonsRemaining()
{
prisonsRemaining = 0;
for (auto isAllowed : map->allowedHeroes)
for (auto isAllowed : map->map().allowedHeroes)
{
if (isAllowed)
prisonsRemaining++;
@@ -187,40 +118,26 @@ void CMapGenerator::initQuestArtsRemaining()
}
}
const CMapGenOptions& CMapGenerator::getMapGenOptions() const
{
return mapGenOptions;
}
CMapEditManager* CMapGenerator::getEditManager() const
{
if(!map)
return nullptr;
return map->getEditManager();
}
std::unique_ptr<CMap> CMapGenerator::generate()
{
map = make_unique<CMap>();
try
{
map->getEditManager()->getUndoManager().setUndoRedoLimit(0);
//FIXME: somehow mapGenOption is nullptr at this point :?
addHeaderInfo();
initTiles();
map->initTiles(*this);
initPrisonsRemaining();
initQuestArtsRemaining();
genZones();
map->calculateGuardingGreaturePositions(); //clear map so that all tiles are unguarded
map->map().calculateGuardingGreaturePositions(); //clear map so that all tiles are unguarded
map->addModificators();
fillZones();
//updated guarded tiles will be calculated in CGameState::initMapObjects()
zones.clear();
map->getZones().clear();
}
catch (rmgException &e)
{
logGlobal->error("Random map generation received exception: %s", e.what());
}
return std::move(map);
return std::move(map->mapInstance);
}
std::string CMapGenerator::getMapDescription() const
@@ -239,7 +156,7 @@ std::string CMapGenerator::getMapDescription() const
std::stringstream ss;
ss << boost::str(boost::format(std::string("Map created by the Random Map Generator.\nTemplate was %s, Random seed was %d, size %dx%d") +
", levels %s, players %d, computers %d, water %s, monster %s, VCMI map") % mapTemplate->getName() %
randomSeed % map->width % map->height % (map->twoLevel ? "2" : "1") % static_cast<int>(mapGenOptions.getPlayerCount()) %
randomSeed % map->map().width % map->map().height % (map->map().twoLevel ? "2" : "1") % static_cast<int>(mapGenOptions.getPlayerCount()) %
static_cast<int>(mapGenOptions.getCompOnlyPlayerCount()) % waterContentStr[mapGenOptions.getWaterContent()] %
monsterStrengthStr[monsterStrengthIndex]);
@@ -329,626 +246,107 @@ void CMapGenerator::addPlayerInfo()
auto itTeam = RandomGeneratorUtil::nextItem(teamNumbers[j], rand);
player.team = TeamID(*itTeam);
teamNumbers[j].erase(itTeam);
map->players[pSettings.getColor().getNum()] = player;
map->map().players[pSettings.getColor().getNum()] = player;
}
map->howManyTeams = (mapGenOptions.getTeamCount() == 0 ? mapGenOptions.getPlayerCount() : mapGenOptions.getTeamCount())
map->map().howManyTeams = (mapGenOptions.getTeamCount() == 0 ? mapGenOptions.getPlayerCount() : mapGenOptions.getTeamCount())
+ (mapGenOptions.getCompOnlyTeamCount() == 0 ? mapGenOptions.getCompOnlyPlayerCount() : mapGenOptions.getCompOnlyTeamCount());
}
void CMapGenerator::genZones()
{
getEditManager()->clearTerrain(&rand);
getEditManager()->getTerrainSelection().selectRange(MapRect(int3(0, 0, 0), mapGenOptions.getWidth(), mapGenOptions.getHeight()));
getEditManager()->drawTerrain(Terrain("grass"), &rand);
auto tmpl = mapGenOptions.getMapTemplate();
zones.clear();
for(const auto & option : tmpl->getZones())
{
auto zone = std::make_shared<CRmgTemplateZone>(this);
zone->setOptions(*option.second.get());
zones[zone->getId()] = zone;
}
CZonePlacer placer(this);
CZonePlacer placer(*map);
placer.placeZones(&rand);
placer.assignZones();
//add special zone for water
zoneWater.first = zones.size() + 1;
zoneWater.second = std::make_shared<CRmgTemplateZone>(this);
{
rmg::ZoneOptions options;
options.setId(zoneWater.first);
options.setType(ETemplateZoneType::WATER);
zoneWater.second->setOptions(options);
}
placer.assignZones(&rand);
logGlobal->info("Zones generated successfully");
}
void CMapGenerator::createWaterTreasures()
{
if(!getZoneWater())
return;
//add treasures on water
for(auto & treasureInfo : getConfig().waterTreasure)
{
getZoneWater().second->addTreasureInfo(treasureInfo);
getZoneWater()->addTreasureInfo(treasureInfo);
}
}
void CMapGenerator::prepareWaterTiles()
{
for(auto & t : zoneWater.second->getTileInfo())
if(shouldBeBlocked(t))
setOccupied(t, ETileType::POSSIBLE);
}
void CMapGenerator::fillZones()
{
//init native town count with 0
for (auto faction : VLC->townh->getAllowedFactions())
zonesPerFaction[faction] = 0;
findZonesForQuestArts();
createWaterTreasures();
logGlobal->info("Started filling zones");
//we need info about all town types to evaluate dwellings and pandoras with creatures properly
//place main town in the middle
for(auto it : zones)
it.second->initTownType();
//make sure there are some free tiles in the zone
for(auto it : zones)
it.second->initFreeTiles();
for(auto it : zones)
it.second->createBorder(); //once direct connections are done
#ifdef _BETA
dump(false);
#endif
for(auto it : zones)
it.second->createWater(getMapGenOptions().getWaterContent());
zoneWater.second->waterInitFreeTiles();
#ifdef _BETA
dump(false);
#endif
createDirectConnections(); //direct
createConnections2(); //subterranean gates and monoliths
for(auto it : zones)
zoneWater.second->waterConnection(*it.second);
createWaterTreasures();
zoneWater.second->initFreeTiles();
zoneWater.second->fill();
std::vector<std::shared_ptr<CRmgTemplateZone>> treasureZones;
for(auto it : zones)
for(auto it : map->getZones())
{
it.second->fill();
it.second->initModificators();
it.second->initFreeTiles();
}
std::vector<std::shared_ptr<Zone>> treasureZones;
for(auto it : map->getZones())
{
it.second->processModificators();
if (it.second->getType() == ETemplateZoneType::TREASURE)
treasureZones.push_back(it.second);
}
#ifdef _BETA
dump(false);
#endif
//set apriopriate free/occupied tiles, including blocked underground rock
createObstaclesCommon1();
//set back original terrain for underground zones
for(auto it : zones)
it.second->createObstacles1();
createObstaclesCommon2();
//place actual obstacles matching zone terrain
for(auto it : zones)
it.second->createObstacles2();
zoneWater.second->createObstacles2();
#ifdef _BETA
dump(false);
#endif
#define PRINT_MAP_BEFORE_ROADS false
if (PRINT_MAP_BEFORE_ROADS) //enable to debug
{
std::ofstream out("road_debug.txt");
int levels = map->twoLevel ? 2 : 1;
int width = map->width;
int height = map->height;
for (int k = 0; k < levels; k++)
{
for (int j = 0; j<height; j++)
{
for (int i = 0; i<width; i++)
{
char t = '?';
switch (getTile(int3(i, j, k)).getTileType())
{
case ETileType::FREE:
t = ' '; break;
case ETileType::BLOCKED:
t = '#'; break;
case ETileType::POSSIBLE:
t = '-'; break;
case ETileType::USED:
t = 'O'; break;
}
out << t;
}
out << std::endl;
}
out << std::endl;
}
out << std::endl;
}
for(auto it : zones)
{
it.second->connectRoads(); //draw roads after everything else has been placed
}
//find place for Grail
if(treasureZones.empty())
{
for(auto it : zones)
treasureZones.push_back(it.second);
for(auto it : map->getZones())
if(it.second->getType() != ETemplateZoneType::WATER)
treasureZones.push_back(it.second);
}
auto grailZone = *RandomGeneratorUtil::nextItem(treasureZones, rand);
map->grailPos = *RandomGeneratorUtil::nextItem(*grailZone->getFreePaths(), rand);
map->map().grailPos = *RandomGeneratorUtil::nextItem(grailZone->freePaths().getTiles(), rand);
logGlobal->info("Zones filled successfully");
}
void CMapGenerator::createObstaclesCommon1()
{
if(map->twoLevel) //underground
{
//negative approach - create rock tiles first, then make sure all accessible tiles have no rock
std::vector<int3> rockTiles;
for(int x = 0; x < map->width; x++)
{
for(int y = 0; y < map->height; y++)
{
int3 tile(x, y, 1);
if (shouldBeBlocked(tile))
{
rockTiles.push_back(tile);
}
}
}
getEditManager()->getTerrainSelection().setSelection(rockTiles);
//collect all rock terrain types
std::vector<Terrain> rockTerrains;
for(auto & terrain : Terrain::Manager::terrains())
if(!terrain.isPassable())
rockTerrains.push_back(terrain);
auto rockTerrain = *RandomGeneratorUtil::nextItem(rockTerrains, rand);
getEditManager()->drawTerrain(rockTerrain, &rand);
}
}
void CMapGenerator::createObstaclesCommon2()
{
if(map->twoLevel)
{
//finally mark rock tiles as occupied, spawn no obstacles there
for(int x = 0; x < map->width; x++)
{
for(int y = 0; y < map->height; y++)
{
int3 tile(x, y, 1);
if(!map->getTile(tile).terType.isPassable())
{
setOccupied(tile, ETileType::USED);
}
}
}
}
//tighten obstacles to improve visuals
for (int i = 0; i < 3; ++i)
{
int blockedTiles = 0;
int freeTiles = 0;
for (int z = 0; z < (map->twoLevel ? 2 : 1); z++)
{
for (int x = 0; x < map->width; x++)
{
for (int y = 0; y < map->height; y++)
{
int3 tile(x, y, z);
if (!isPossible(tile)) //only possible tiles can change
continue;
int blockedNeighbours = 0;
int freeNeighbours = 0;
foreach_neighbour(tile, [this, &blockedNeighbours, &freeNeighbours](int3 &pos)
{
if (this->isBlocked(pos))
blockedNeighbours++;
if (this->isFree(pos))
freeNeighbours++;
});
if (blockedNeighbours > 4)
{
setOccupied(tile, ETileType::BLOCKED);
blockedTiles++;
}
else if (freeNeighbours > 4)
{
setOccupied(tile, ETileType::FREE);
freeTiles++;
}
}
}
}
logGlobal->trace("Set %d tiles to BLOCKED and %d tiles to FREE", blockedTiles, freeTiles);
}
}
void CMapGenerator::findZonesForQuestArts()
{
//we want to place arties in zones that were not yet filled (higher index)
for (auto connection : mapGenOptions.getMapTemplate()->getConnections())
{
auto zoneA = zones[connection.getZoneA()];
auto zoneB = zones[connection.getZoneB()];
auto zoneA = map->getZones()[connection.getZoneA()];
auto zoneB = map->getZones()[connection.getZoneB()];
if (zoneA->getId() > zoneB->getId())
{
zoneB->setQuestArtZone(zoneA);
if(auto * m = zoneB->getModificator<TreasurePlacer>())
zoneB->getModificator<TreasurePlacer>()->setQuestArtZone(zoneA.get());
}
else if (zoneA->getId() < zoneB->getId())
{
zoneA->setQuestArtZone(zoneB);
}
}
}
void CMapGenerator::createDirectConnections()
{
bool waterMode = getMapGenOptions().getWaterContent() != EWaterContent::NONE;
for (auto connection : mapGenOptions.getMapTemplate()->getConnections())
{
auto zoneA = zones[connection.getZoneA()];
auto zoneB = zones[connection.getZoneB()];
//rearrange tiles in random order
const auto & tiles = zoneA->getTileInfo();
int3 guardPos(-1,-1,-1);
int3 posA = zoneA->getPos();
int3 posB = zoneB->getPos();
// auto zoneAid = zoneA->getId();
auto zoneBid = zoneB->getId();
if (posA.z == posB.z)
{
std::vector<int3> middleTiles;
for (const auto& tile : tiles)
{
if (isUsed(tile) || getZoneID(tile)==zoneWater.first) //tiles may be occupied by towns or water
continue;
foreachDirectNeighbour(tile, [tile, &middleTiles, this, zoneBid](int3 & pos) //must be direct since paths also also generated between direct neighbours
{
if(getZoneID(pos) == zoneBid)
middleTiles.push_back(tile);
});
}
//find tiles with minimum manhattan distance from center of the mass of zone border
size_t tilesCount = middleTiles.size() ? middleTiles.size() : 1;
int3 middleTile = std::accumulate(middleTiles.begin(), middleTiles.end(), int3(0, 0, 0));
middleTile.x /= (si32)tilesCount;
middleTile.y /= (si32)tilesCount;
middleTile.z /= (si32)tilesCount; //TODO: implement division operator for int3?
boost::sort(middleTiles, [middleTile](const int3 &lhs, const int3 &rhs) -> bool
{
//choose tiles with both corrdinates in the middle
return lhs.mandist2d(middleTile) < rhs.mandist2d(middleTile);
});
//remove 1/4 tiles from each side - path should cross zone borders at smooth angle
size_t removedCount = tilesCount / 4; //rounded down
middleTiles.erase(middleTiles.end() - removedCount, middleTiles.end());
middleTiles.erase(middleTiles.begin(), middleTiles.begin() + removedCount);
RandomGeneratorUtil::randomShuffle(middleTiles, rand);
for (auto tile : middleTiles)
{
guardPos = tile;
if (guardPos.valid())
{
//zones can make paths only in their own area
zoneA->connectWithCenter(guardPos, true, true);
zoneB->connectWithCenter(guardPos, true, true);
bool monsterPresent = zoneA->addMonster(guardPos, connection.getGuardStrength(), false, true);
zoneB->updateDistances(guardPos); //place next objects away from guard in both zones
//set free tile only after connection is made to the center of the zone
if (!monsterPresent)
setOccupied(guardPos, ETileType::FREE); //just in case monster is too weak to spawn
zoneA->addRoadNode(guardPos);
zoneB->addRoadNode(guardPos);
break; //we're done with this connection
}
}
}
if (!guardPos.valid())
{
if(!waterMode || posA.z != posB.z || !zoneWater.second->waterKeepConnection(connection.getZoneA(), connection.getZoneB()))
connectionsLeft.push_back(connection);
}
}
}
void CMapGenerator::createConnections2()
{
for (auto & connection : connectionsLeft)
{
auto zoneA = zones[connection.getZoneA()];
auto zoneB = zones[connection.getZoneB()];
int3 guardPos(-1, -1, -1);
int3 posA = zoneA->getPos();
int3 posB = zoneB->getPos();
auto strength = connection.getGuardStrength();
if (posA.z != posB.z) //try to place subterranean gates
{
auto sgt = VLC->objtypeh->getHandlerFor(Obj::SUBTERRANEAN_GATE, 0)->getTemplates().front();
auto tilesBlockedByObject = sgt.getBlockedOffsets();
auto factory = VLC->objtypeh->getHandlerFor(Obj::SUBTERRANEAN_GATE, 0);
auto gate1 = factory->create(ObjectTemplate());
auto gate2 = factory->create(ObjectTemplate());
while (!guardPos.valid())
{
bool continueOuterLoop = false;
//find common tiles for both zones
auto tileSetA = zoneA->getPossibleTiles(),
tileSetB = zoneB->getPossibleTiles();
std::vector<int3> tilesA(tileSetA.begin(), tileSetA.end()),
tilesB(tileSetB.begin(), tileSetB.end());
std::vector<int3> commonTiles;
auto lambda = [](const int3 & lhs, const int3 & rhs) -> bool
{
//https://stackoverflow.com/questions/45966807/c-invalid-comparator-assert
return std::tie(lhs.x, lhs.y) < std::tie(rhs.x, rhs.y); //ignore z coordinate
};
//required for set_intersection
boost::sort(tilesA, lambda);
boost::sort(tilesB, lambda);
boost::set_intersection(tilesA, tilesB, std::back_inserter(commonTiles), lambda);
vstd::erase_if(commonTiles, [](const int3 &tile) -> bool
{
return (!tile.x) || (!tile.y); //gates shouldn't go outside map (x = 0) and look bad at the very top (y = 0)
});
if (commonTiles.empty())
break; //nothing more to do
boost::sort(commonTiles, [posA, posB](const int3 &lhs, const int3 &rhs) -> bool
{
//choose tiles which are equidistant to zone centers
return (std::abs<double>(posA.dist2dSQ(lhs) - posB.dist2dSQ(lhs)) < std::abs<double>((posA.dist2dSQ(rhs) - posB.dist2dSQ(rhs))));
});
for (auto tile : commonTiles)
{
tile.z = posA.z;
int3 otherTile = tile;
otherTile.z = posB.z;
float distanceFromA = static_cast<float>(posA.dist2d(tile));
float distanceFromB = static_cast<float>(posB.dist2d(otherTile));
if (distanceFromA > 5 && distanceFromB > 5)
{
if (zoneA->areAllTilesAvailable(gate1, tile, tilesBlockedByObject) &&
zoneB->areAllTilesAvailable(gate2, otherTile, tilesBlockedByObject))
{
if (zoneA->getAccessibleOffset(sgt, tile).valid() && zoneB->getAccessibleOffset(sgt, otherTile).valid())
{
EObjectPlacingResult::EObjectPlacingResult result1 = zoneA->tryToPlaceObjectAndConnectToPath(gate1, tile);
EObjectPlacingResult::EObjectPlacingResult result2 = zoneB->tryToPlaceObjectAndConnectToPath(gate2, otherTile);
if ((result1 == EObjectPlacingResult::SUCCESS) && (result2 == EObjectPlacingResult::SUCCESS))
{
zoneA->placeObject(gate1, tile);
zoneA->guardObject(gate1, strength, true, true);
zoneB->placeObject(gate2, otherTile);
zoneB->guardObject(gate2, strength, true, true);
guardPos = tile; //set to break the loop
break;
}
else if ((result1 == EObjectPlacingResult::SEALED_OFF) || (result2 == EObjectPlacingResult::SEALED_OFF))
{
//sealed-off tiles were blocked, exit inner loop and get another tile set
continueOuterLoop = true;
break;
}
else
continue; //try with another position
}
}
}
}
if (!continueOuterLoop) //we didn't find ANY tile - break outer loop
break;
}
if (!guardPos.valid()) //cleanup? is this safe / enough?
{
delete gate1;
delete gate2;
}
}
if (!guardPos.valid())
{
auto factory = VLC->objtypeh->getHandlerFor(Obj::MONOLITH_TWO_WAY, getNextMonlithIndex());
auto teleport1 = factory->create(ObjectTemplate());
auto teleport2 = factory->create(ObjectTemplate());
zoneA->addRequiredObject(teleport1, strength);
zoneB->addRequiredObject(teleport2, strength);
if(auto * m = zoneA->getModificator<TreasurePlacer>())
zoneA->getModificator<TreasurePlacer>()->setQuestArtZone(zoneB.get());
}
}
}
void CMapGenerator::addHeaderInfo()
{
map->version = EMapFormat::VCMI;
map->width = mapGenOptions.getWidth();
map->height = mapGenOptions.getHeight();
map->twoLevel = mapGenOptions.getHasTwoLevels();
map->name = VLC->generaltexth->allTexts[740];
map->description = getMapDescription();
map->difficulty = 1;
map->map().version = EMapFormat::VCMI;
map->map().width = mapGenOptions.getWidth();
map->map().height = mapGenOptions.getHeight();
map->map().twoLevel = mapGenOptions.getHasTwoLevels();
map->map().name = VLC->generaltexth->allTexts[740];
map->map().description = getMapDescription();
map->map().difficulty = 1;
addPlayerInfo();
}
void CMapGenerator::checkIsOnMap(const int3& tile) const
{
if (!map->isInTheMap(tile))
throw rmgException(boost::to_string(boost::format("Tile %s is outside the map") % tile.toString()));
}
CMapGenerator::Zones & CMapGenerator::getZones()
{
return zones;
}
bool CMapGenerator::isBlocked(const int3 &tile) const
{
checkIsOnMap(tile);
return tiles[tile.x][tile.y][tile.z].isBlocked();
}
bool CMapGenerator::shouldBeBlocked(const int3 &tile) const
{
checkIsOnMap(tile);
return tiles[tile.x][tile.y][tile.z].shouldBeBlocked();
}
bool CMapGenerator::isPossible(const int3 &tile) const
{
checkIsOnMap(tile);
return tiles[tile.x][tile.y][tile.z].isPossible();
}
bool CMapGenerator::isFree(const int3 &tile) const
{
checkIsOnMap(tile);
return tiles[tile.x][tile.y][tile.z].isFree();
}
bool CMapGenerator::isUsed(const int3 &tile) const
{
checkIsOnMap(tile);
return tiles[tile.x][tile.y][tile.z].isUsed();
}
bool CMapGenerator::isRoad(const int3& tile) const
{
checkIsOnMap(tile);
return tiles[tile.x][tile.y][tile.z].isRoad();
}
void CMapGenerator::setOccupied(const int3 &tile, ETileType::ETileType state)
{
checkIsOnMap(tile);
tiles[tile.x][tile.y][tile.z].setOccupied(state);
}
void CMapGenerator::setRoad(const int3& tile, const std::string & roadType)
{
checkIsOnMap(tile);
tiles[tile.x][tile.y][tile.z].setRoadType(roadType);
}
CTileInfo CMapGenerator::getTile(const int3& tile) const
{
checkIsOnMap(tile);
return tiles[tile.x][tile.y][tile.z];
}
TRmgTemplateZoneId CMapGenerator::getZoneID(const int3& tile) const
{
checkIsOnMap(tile);
return zoneColouring[tile.z][tile.x][tile.y];
}
void CMapGenerator::setZoneID(const int3& tile, TRmgTemplateZoneId zid)
{
checkIsOnMap(tile);
zoneColouring[tile.z][tile.x][tile.y] = zid;
}
bool CMapGenerator::isAllowedSpell(SpellID sid) const
{
assert(sid >= 0);
if (sid < map->allowedSpell.size())
{
return map->allowedSpell[sid];
}
else
return false;
}
void CMapGenerator::setNearestObjectDistance(int3 &tile, float value)
{
checkIsOnMap(tile);
tiles[tile.x][tile.y][tile.z].setNearestObjectDistance(value);
}
float CMapGenerator::getNearestObjectDistance(const int3 &tile) const
{
checkIsOnMap(tile);
return tiles[tile.x][tile.y][tile.z].getNearestObjectDistance();
}
int CMapGenerator::getNextMonlithIndex()
{
if (monolithIndex >= VLC->objtypeh->knownSubObjects(Obj::MONOLITH_TWO_WAY).size())
@@ -961,76 +359,27 @@ int CMapGenerator::getPrisonsRemaning() const
{
return prisonsRemaining;
}
void CMapGenerator::decreasePrisonsRemaining()
{
prisonsRemaining = std::max (0, prisonsRemaining - 1);
}
std::vector<ArtifactID> CMapGenerator::getQuestArtsRemaning() const
const std::vector<ArtifactID> & CMapGenerator::getQuestArtsRemaning() const
{
return questArtifacts;
}
void CMapGenerator::banQuestArt(ArtifactID id)
{
map->allowedArtifact[id] = false;
vstd::erase_if_present (questArtifacts, id);
map->map().allowedArtifact[id] = false;
vstd::erase_if_present(questArtifacts, id);
}
void CMapGenerator::registerZone (TFaction faction)
Zone * CMapGenerator::getZoneWater() const
{
zonesPerFaction[faction]++;
zonesTotal++;
}
ui32 CMapGenerator::getZoneCount(TFaction faction)
{
return zonesPerFaction[faction];
}
ui32 CMapGenerator::getTotalZoneCount() const
{
return zonesTotal;
}
CMapGenerator::Zones::value_type CMapGenerator::getZoneWater() const
{
return zoneWater;
}
void CMapGenerator::dump(bool zoneId)
{
static int id = 0;
std::ofstream out(boost::to_string(boost::format("zone_%d.txt") % id++));
int levels = map->twoLevel ? 2 : 1;
int width = map->width;
int height = map->height;
for (int k = 0; k < levels; k++)
{
for(int j=0; j<height; j++)
{
for (int i=0; i<width; i++)
{
if(zoneId)
{
out << getZoneID(int3(i, j, k));
}
else
{
char t = '?';
switch (getTile(int3(i, j, k)).getTileType())
{
case ETileType::FREE:
t = ' '; break;
case ETileType::BLOCKED:
t = '#'; break;
case ETileType::POSSIBLE:
t = '-'; break;
case ETileType::USED:
t = 'O'; break;
}
out << t;
}
}
out << std::endl;
}
out << std::endl;
}
out << std::endl;
for(auto & z : map->getZones())
if(z.second->getType() == ETemplateZoneType::WATER)
return z.second.get();
return nullptr;
}

View File

@@ -16,36 +16,15 @@
#include "../int3.h"
#include "CRmgTemplate.h"
class CMap;
class CRmgTemplate;
class CRmgTemplateZone;
class CMapGenOptions;
class CTerrainViewPatternConfig;
class CMapEditManager;
class JsonNode;
class CMapGenerator;
class CTileInfo;
class RmgMap;
class CMap;
class Zone;
typedef std::vector<JsonNode> JsonVector;
class rmgException : public std::exception
{
std::string msg;
public:
explicit rmgException(const std::string& _Message) : msg(_Message)
{
}
virtual ~rmgException() throw ()
{
};
const char *what() const throw () override
{
return msg.c_str();
}
};
/// The map generator creates a map randomly.
class DLL_LINKAGE CMapGenerator
{
@@ -57,7 +36,6 @@ public:
std::vector<CTreasureInfo> waterTreasure;
int shipyardGuard;
int mineExtraResources;
std::map<Res::ERes, int> mineValues;
int minGuardStrength;
std::string defaultRoadType;
int treasureValueLimit;
@@ -68,84 +46,44 @@ public:
std::vector<int> questValues, questRewardValues;
};
using Zones = std::map<TRmgTemplateZoneId, std::shared_ptr<CRmgTemplateZone>>;
explicit CMapGenerator(CMapGenOptions& mapGenOptions, int RandomSeed = std::time(nullptr));
~CMapGenerator(); // required due to std::unique_ptr
const Config & getConfig() const;
mutable std::unique_ptr<CMap> map;
CRandomGenerator rand;
CMapEditManager* getEditManager() const;
const CMapGenOptions& getMapGenOptions() const;
std::unique_ptr<CMap> generate();
Zones & getZones();
void createDirectConnections();
void createConnections2();
void findZonesForQuestArts();
void foreach_neighbour(const int3 &pos, std::function<void(int3& pos)> foo);
void foreachDirectNeighbour(const int3 &pos, std::function<void(int3& pos)> foo);
void foreachDiagonalNeighbour(const int3& pos, std::function<void(int3& pos)> foo);
bool isBlocked(const int3 &tile) const;
bool shouldBeBlocked(const int3 &tile) const;
bool isPossible(const int3 &tile) const;
bool isFree(const int3 &tile) const;
bool isUsed(const int3 &tile) const;
bool isRoad(const int3 &tile) const;
void setOccupied(const int3 &tile, ETileType::ETileType state);
void setRoad(const int3 &tile, const std::string & roadType);
CTileInfo getTile(const int3 & tile) const;
bool isAllowedSpell(SpellID sid) const;
float getNearestObjectDistance(const int3 &tile) const;
void setNearestObjectDistance(int3 &tile, float value);
int getNextMonlithIndex();
int getPrisonsRemaning() const;
void decreasePrisonsRemaining();
std::vector<ArtifactID> getQuestArtsRemaning() const;
const std::vector<ArtifactID> & getQuestArtsRemaning() const;
void banQuestArt(ArtifactID id);
void registerZone (TFaction faction);
ui32 getZoneCount(TFaction faction);
ui32 getTotalZoneCount() const;
Zones::value_type getZoneWater() const;
Zone * getZoneWater() const;
void createWaterTreasures();
void prepareWaterTiles();
TRmgTemplateZoneId getZoneID(const int3& tile) const;
void setZoneID(const int3& tile, TRmgTemplateZoneId zid);
int getRandomSeed() const;
void dump(bool zoneId);
private:
int randomSeed;
CMapGenOptions& mapGenOptions;
Config config;
std::unique_ptr<RmgMap> map;
std::vector<rmg::ZoneConnection> connectionsLeft;
Zones zones;
std::map<TFaction, ui32> zonesPerFaction;
ui32 zonesTotal; //zones that have their main town only
std::pair<Zones::key_type, Zones::mapped_type> zoneWater;
CTileInfo*** tiles;
boost::multi_array<TRmgTemplateZoneId, 3> zoneColouring; //[z][x][y]
//std::pair<Zones::key_type, Zones::mapped_type> zoneWater;
int prisonsRemaining;
//int questArtsRemaining;
int monolithIndex;
std::vector<ArtifactID> questArtifacts;
void checkIsOnMap(const int3 &tile) const; //throws
/// Generation methods
void loadConfig();
@@ -156,10 +94,7 @@ private:
void initQuestArtsRemaining();
void addPlayerInfo();
void addHeaderInfo();
void initTiles();
void genZones();
void fillZones();
void createObstaclesCommon1();
void createObstaclesCommon2();
};

View File

@@ -252,6 +252,11 @@ void ZoneOptions::setMonsterTypes(const std::set<TFaction> & value)
monsterTypes = value;
}
const std::set<TFaction> & ZoneOptions::getMonsterTypes() const
{
return monsterTypes;
}
void ZoneOptions::setMinesInfo(const std::map<TResource, ui16> & value)
{
mines = value;
@@ -302,6 +307,26 @@ std::vector<TRmgTemplateZoneId> ZoneOptions::getConnections() const
return connections;
}
bool ZoneOptions::areTownsSameType() const
{
return townsAreSameType;
}
bool ZoneOptions::isMatchTerrainToTown() const
{
return matchTerrainToTown;
}
const ZoneOptions::CTownInfo & ZoneOptions::getPlayerTowns() const
{
return playerTowns;
}
const ZoneOptions::CTownInfo & ZoneOptions::getNeutralTowns() const
{
return neutralTowns;
}
void ZoneOptions::serializeJson(JsonSerializeFormat & handler)
{
static const std::vector<std::string> zoneTypes =
@@ -421,6 +446,11 @@ int ZoneConnection::getGuardStrength() const
{
return guardStrength;
}
bool operator==(const ZoneConnection & l, const ZoneConnection & r)
{
return l.zoneA == r.zoneA && l.zoneB == r.zoneB && l.guardStrength == r.guardStrength;
}
void ZoneConnection::serializeJson(JsonSerializeFormat & handler)
{

View File

@@ -17,7 +17,6 @@
#include "CMapGenOptions.h"
class JsonSerializeFormat;
class Terrain;
namespace ETemplateZoneType
{
@@ -58,6 +57,8 @@ public:
int getGuardStrength() const;
void serializeJson(JsonSerializeFormat & handler);
friend bool operator==(const ZoneConnection &, const ZoneConnection &);
private:
TRmgTemplateZoneId zoneA;
TRmgTemplateZoneId zoneB;
@@ -105,8 +106,11 @@ public:
const std::set<Terrain> & getTerrainTypes() const;
void setTerrainTypes(const std::set<Terrain> & value);
const CTownInfo & getPlayerTowns() const;
const CTownInfo & getNeutralTowns() const;
std::set<TFaction> getDefaultTownTypes() const;
const std::set<TFaction> & getTownTypes() const;
const std::set<TFaction> & getMonsterTypes() const;
void setTownTypes(const std::set<TFaction> & value);
void setMonsterTypes(const std::set<TFaction> & value);
@@ -126,6 +130,11 @@ public:
std::vector<TRmgTemplateZoneId> getConnections() const;
void serializeJson(JsonSerializeFormat & handler);
EMonsterStrength::EMonsterStrength zoneMonsterStrength;
bool areTownsSameType() const;
bool isMatchTerrainToTown() const;
protected:
TRmgTemplateZoneId id;
@@ -141,8 +150,6 @@ protected:
std::set<TFaction> townTypes;
std::set<TFaction> monsterTypes;
EMonsterStrength::EMonsterStrength zoneMonsterStrength;
std::map<TResource, ui16> mines; //obligatory mines to spawn in this zone
std::vector<CTreasureInfo> treasureInfo;

File diff suppressed because it is too large Load Diff

View File

@@ -1,237 +0,0 @@
/*
* CRmgTemplateZone.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 "CMapGenerator.h"
#include "float3.h"
#include "../int3.h"
#include "CRmgTemplate.h"
#include "../mapObjects/ObjectTemplate.h"
#include <boost/heap/priority_queue.hpp> //A*
#include "Terrain.h"
class CMapGenerator;
class CTileInfo;
class int3;
class CGObjectInstance;
class ObjectTemplate;
namespace EObjectPlacingResult
{
enum EObjectPlacingResult
{
SUCCESS,
CANNOT_FIT,
SEALED_OFF
};
}
class DLL_LINKAGE CTileInfo
{
public:
CTileInfo();
float getNearestObjectDistance() const;
void setNearestObjectDistance(float value);
bool isBlocked() const;
bool shouldBeBlocked() const;
bool isPossible() const;
bool isFree() const;
bool isUsed() const;
bool isRoad() const;
void setOccupied(ETileType::ETileType value);
Terrain getTerrainType() const;
ETileType::ETileType getTileType() const;
void setTerrainType(Terrain value);
void setRoadType(const std::string & value);
private:
float nearestObjectDistance;
ETileType::ETileType occupied;
Terrain terrain;
std::string roadType;
};
struct DLL_LINKAGE ObjectInfo
{
ObjectTemplate templ;
ui32 value;
ui16 probability;
ui32 maxPerZone;
//ui32 maxPerMap; //unused
std::function<CGObjectInstance *()> generateObject;
void setTemplate (si32 type, si32 subtype, Terrain terrain);
ObjectInfo();
bool operator==(const ObjectInfo& oi) const { return (templ == oi.templ); }
};
struct DLL_LINKAGE CTreasurePileInfo
{
std::set<int3> visitableFromBottomPositions; //can be visited only from bottom or side
std::set<int3> visitableFromTopPositions; //they can be visited from any direction
std::set<int3> blockedPositions;
std::set<int3> occupiedPositions; //blocked + visitable
int3 nextTreasurePos;
};
/// The CRmgTemplateZone describes a zone in a template.
class DLL_LINKAGE CRmgTemplateZone : public rmg::ZoneOptions
{
public:
CRmgTemplateZone(CMapGenerator * Gen);
void setOptions(const rmg::ZoneOptions & options);
bool isUnderground() const;
float3 getCenter() const;
void setCenter(const float3 &f);
int3 getPos() const;
void setPos(const int3 &pos);
bool isAccessibleFromSomewhere(ObjectTemplate & appearance, const int3 & tile) const;
int3 getAccessibleOffset(ObjectTemplate & appearance, const int3 & tile) const;
void addTile (const int3 & pos);
void removeTile(const int3 & pos);
void initFreeTiles ();
std::set<int3> getTileInfo() const;
std::set<int3> getPossibleTiles() const;
std::set<int3> collectDistantTiles (float distance) const;
void clearTiles();
void addRequiredObject(CGObjectInstance * obj, si32 guardStrength=0);
void addCloseObject(CGObjectInstance * obj, si32 guardStrength = 0);
void addNearbyObject(CGObjectInstance * obj, CGObjectInstance * nearbyTarget);
void addObjectAtPosition(CGObjectInstance * obj, const int3 & position, si32 guardStrength=0);
void addToConnectLater(const int3& src);
bool addMonster(int3 &pos, si32 strength, bool clearSurroundingTiles = true, bool zoneGuard = false);
bool createTreasurePile(int3 &pos, float minDistance, const CTreasureInfo& treasureInfo);
bool fill ();
bool placeMines ();
void initTownType ();
void paintZoneTerrain (Terrain terrainType);
void randomizeTownType(bool matchUndergroundType = false); //helper function
void initTerrainType ();
void createBorder();
void fractalize();
void connectLater();
EObjectPlacingResult::EObjectPlacingResult tryToPlaceObjectAndConnectToPath(CGObjectInstance * obj, const int3 & pos); //return true if the position can be connected
bool createRequiredObjects();
bool createShipyard(const int3 & pos, si32 guardStrength=0);
int3 createShipyard(const std::set<int3> & lake, si32 guardStrength=0);
bool makeBoat(TRmgTemplateZoneId land, const int3 & coast);
int3 makeBoat(TRmgTemplateZoneId land, const std::set<int3> & lake);
void createTreasures();
void createWater(EWaterContent::EWaterContent waterContent, bool debug=false);
void waterInitFreeTiles();
void waterConnection(CRmgTemplateZone& dst);
bool waterKeepConnection(TRmgTemplateZoneId zoneA, TRmgTemplateZoneId zoneB);
const std::set<int3>& getCoastTiles() const;
bool isWaterConnected(TRmgTemplateZoneId zone, const int3 & tile) const;
//void computeCoastTiles();
void createObstacles1();
void createObstacles2();
bool crunchPath(const int3 &src, const int3 &dst, bool onlyStraight, std::set<int3>* clearedTiles = nullptr);
bool connectPath(const int3& src, bool onlyStraight);
bool connectWithCenter(const int3& src, bool onlyStraight, bool passTroughBlocked = false);
void updateDistances(const int3 & pos);
std::vector<int3> getAccessibleOffsets (const CGObjectInstance* object);
bool areAllTilesAvailable(CGObjectInstance* obj, int3& tile, const std::set<int3>& tilesBlockedByObject) const;
void setQuestArtZone(std::shared_ptr<CRmgTemplateZone> otherZone);
std::set<int3>* getFreePaths();
void addFreePath(const int3 &);
ObjectInfo getRandomObject (CTreasurePileInfo &info, ui32 desiredValue, ui32 maxValue, ui32 currentValue);
void placeSubterraneanGate(int3 pos, si32 guardStrength);
void placeObject(CGObjectInstance* object, const int3 &pos, bool updateDistance = true);
bool guardObject(CGObjectInstance* object, si32 str, bool zoneGuard = false, bool addToFreePaths = false);
void placeAndGuardObject(CGObjectInstance* object, const int3 &pos, si32 str, bool zoneGuard = false);
void addRoadNode(const int3 & node);
void connectRoads(); //fills "roads" according to "roadNodes"
//A* priority queue
typedef std::pair<int3, float> TDistance;
struct NodeComparer
{
bool operator()(const TDistance & lhs, const TDistance & rhs) const
{
return (rhs.second < lhs.second);
}
};
boost::heap::priority_queue<TDistance, boost::heap::compare<NodeComparer>> createPriorityQueue();
private:
//subclass to store disconnected parts of water zone
struct Lake
{
std::set<int3> tiles;
std::set<int3> coast;
std::map<int3, int> distance;
std::set<TRmgTemplateZoneId> connectedZones;
std::set<TRmgTemplateZoneId> keepConnections;
};
CMapGenerator * gen;
//template info
si32 townType;
Terrain terrainType;
std::weak_ptr<CRmgTemplateZone> questArtZone; //artifacts required for Seer Huts will be placed here - or not if null
std::vector<ObjectInfo> possibleObjects;
int minGuardedValue;
//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;
std::map<CGObjectInstance*, int3> requestedPositions;
//placement info
int3 pos;
float3 center;
std::set<int3> tileinfo; //irregular area assined to zone
std::set<int3> possibleTiles; //optimization purposes for treasure generation
std::set<int3> freePaths; //core paths of free tiles that all other objects will be linked to
std::set<int3> coastTiles; //tiles bordered to water
std::set<int3> roadNodes; //tiles to be connected with roads
std::set<int3> roads; //all tiles with roads
std::set<int3> tilesToConnectLater; //will be connected after paths are fractalized
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
bool createRoad(const int3 &src, const int3 &dst);
void drawRoads(); //actually updates tiles
bool pointIsIn(int x, int y);
void addAllPossibleObjects (); //add objects, including zone-specific, to possibleObjects
bool findPlaceForObject(CGObjectInstance* obj, si32 min_dist, int3 &pos);
bool findPlaceForTreasurePile(float min_dist, int3 &pos, int value);
bool canObstacleBePlacedHere(ObjectTemplate &temp, int3 &pos);
void setTemplateForObject(CGObjectInstance* obj);
void checkAndPlaceObject(CGObjectInstance* object, const int3 &pos);
int chooseRandomAppearance(si32 ObjID) const;
bool isGuardNeededForTreasure(int value);
};

View File

@@ -11,15 +11,17 @@
#include "StdInc.h"
#include "../CRandomGenerator.h"
#include "CZonePlacer.h"
#include "CRmgTemplateZone.h"
#include "../mapping/CMap.h"
#include "../mapping/CMapEditManager.h"
#include "RmgMap.h"
#include "Zone.h"
#include "Functions.h"
class CRandomGenerator;
CZonePlacer::CZonePlacer(CMapGenerator * Gen)
CZonePlacer::CZonePlacer(RmgMap & map)
: width(0), height(0), scaleX(0), scaleY(0), mapSize(0), gravityConstant(0), stiffnessConstant(0),
gen(Gen)
map(map)
{
}
@@ -31,7 +33,7 @@ CZonePlacer::~CZonePlacer()
int3 CZonePlacer::cords (const float3 f) const
{
return int3((si32)std::max(0.f, (f.x * gen->map->width)-1), (si32)std::max(0.f, (f.y * gen->map->height-1)), f.z);
return int3((si32)std::max(0.f, (f.x * map.map().width)-1), (si32)std::max(0.f, (f.y * map.map().height-1)), f.z);
}
float CZonePlacer::getDistance (float distance) const
@@ -43,11 +45,15 @@ void CZonePlacer::placeZones(CRandomGenerator * rand)
{
logGlobal->info("Starting zone placement");
width = gen->getMapGenOptions().getWidth();
height = gen->getMapGenOptions().getHeight();
width = map.getMapGenOptions().getWidth();
height = map.getMapGenOptions().getHeight();
auto zones = gen->getZones();
bool underground = gen->getMapGenOptions().getHasTwoLevels();
auto zones = map.getZones();
vstd::erase_if(zones, [](const std::pair<TRmgTemplateZoneId, std::shared_ptr<Zone>> & pr)
{
return pr.second->getType() == ETemplateZoneType::WATER;
});
bool underground = map.getMapGenOptions().getHasTwoLevels();
/*
gravity-based algorithm
@@ -72,7 +78,7 @@ void CZonePlacer::placeZones(CRandomGenerator * rand)
float bestTotalDistance = 1e10;
float bestTotalOverlap = 1e10;
std::map<std::shared_ptr<CRmgTemplateZone>, float3> bestSolution;
std::map<std::shared_ptr<Zone>, float3> bestSolution;
TForceVector forces;
TForceVector totalForces; // both attraction and pushback, overcomplicated?
@@ -173,7 +179,7 @@ void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const
if (boost::optional<int> owner = zone.second->getOwner())
{
auto player = PlayerColor(*owner - 1);
auto playerSettings = gen->getMapGenOptions().getPlayersSettings();
auto playerSettings = map.getMapGenOptions().getPlayersSettings();
si32 faction = CMapGenOptions::CPlayerSettings::RANDOM_TOWN;
if (vstd::contains(playerSettings, player))
faction = playerSettings[player].getStartingTown();
@@ -351,7 +357,7 @@ void CZonePlacer::moveOneZone(TZoneMap &zones, TForceVector &totalForces, TDista
{
float maxRatio = 0;
const int maxDistanceMovementRatio = static_cast<int>(zones.size() * zones.size()); //experimental - the more zones, the greater total distance expected
std::shared_ptr<CRmgTemplateZone> misplacedZone;
std::shared_ptr<Zone> misplacedZone;
float totalDistance = 0;
float totalOverlap = 0;
@@ -371,7 +377,7 @@ void CZonePlacer::moveOneZone(TZoneMap &zones, TForceVector &totalForces, TDista
if (maxRatio > maxDistanceMovementRatio && misplacedZone)
{
std::shared_ptr<CRmgTemplateZone> targetZone;
std::shared_ptr<Zone> targetZone;
float3 ourCenter = misplacedZone->getCenter();
if (totalDistance > totalOverlap)
@@ -450,20 +456,24 @@ d = 0.01 * dx^3 - 0.1618 * dx^2 + 1 * dx + ...
return dx * (1.0f + dx * (0.1f + dx * 0.01f)) + dy * (1.618f + dy * (-0.1618f + dy * 0.01618f));
}
void CZonePlacer::assignZones()
void CZonePlacer::assignZones(CRandomGenerator * rand)
{
logGlobal->info("Starting zone colouring");
auto width = gen->getMapGenOptions().getWidth();
auto height = gen->getMapGenOptions().getHeight();
auto width = map.getMapGenOptions().getWidth();
auto height = map.getMapGenOptions().getHeight();
//scale to Medium map to ensure smooth results
scaleX = 72.f / width;
scaleY = 72.f / height;
auto zones = gen->getZones();
auto zones = map.getZones();
vstd::erase_if(zones, [](const std::pair<TRmgTemplateZoneId, std::shared_ptr<Zone>> & pr)
{
return pr.second->getType() == ETemplateZoneType::WATER;
});
typedef std::pair<std::shared_ptr<CRmgTemplateZone>, float> Dpair;
typedef std::pair<std::shared_ptr<Zone>, float> Dpair;
std::vector <Dpair> distances;
distances.reserve(zones.size());
@@ -475,10 +485,10 @@ void CZonePlacer::assignZones()
return lhs.second / lhs.first->getSize() < rhs.second / rhs.first->getSize();
};
auto moveZoneToCenterOfMass = [](std::shared_ptr<CRmgTemplateZone> zone) -> void
auto moveZoneToCenterOfMass = [](std::shared_ptr<Zone> zone) -> void
{
int3 total(0, 0, 0);
auto tiles = zone->getTileInfo();
auto tiles = zone->area().getTiles();
for (auto tile : tiles)
{
total += tile;
@@ -488,7 +498,7 @@ void CZonePlacer::assignZones()
zone->setPos(int3(total.x / size, total.y / size, total.z / size));
};
int levels = gen->map->twoLevel ? 2 : 1;
int levels = map.map().twoLevel ? 2 : 1;
/*
1. Create Voronoi diagram
@@ -510,7 +520,7 @@ void CZonePlacer::assignZones()
else
distances.push_back(std::make_pair(zone.second, std::numeric_limits<float>::max()));
}
boost::min_element(distances, compareByDistance)->first->addTile(pos); //closest tile belongs to zone
boost::min_element(distances, compareByDistance)->first->area().add(pos); //closest tile belongs to zone
}
}
}
@@ -539,8 +549,8 @@ void CZonePlacer::assignZones()
distances.push_back (std::make_pair(zone.second, std::numeric_limits<float>::max()));
}
auto zone = boost::min_element(distances, compareByDistance)->first; //closest tile belongs to zone
zone->addTile(pos);
gen->setZoneID(pos, zone->getId());
zone->area().add(pos);
map.setZoneID(pos, zone->getId());
}
}
}
@@ -555,14 +565,14 @@ void CZonePlacer::assignZones()
{
if (!CREATE_FULL_UNDERGROUND)
{
auto discardTile = zone.second->collectDistantTiles((float)(zone.second->getSize() + 1));
for(auto& t : discardTile)
zone.second->removeTile(t);
auto discardTiles = collectDistantTiles(*zone.second, zone.second->getSize() + 1.f);
for(auto& t : discardTiles)
zone.second->area().erase(t);
}
//make sure that terrain inside zone is not a rock
//FIXME: reorder actions?
zone.second->paintZoneTerrain (Terrain("subterra"));
paintZoneTerrain(*zone.second, *rand, map, Terrain("subterra"));
}
}
logGlobal->info("Finished zone colouring");

View File

@@ -10,32 +10,32 @@
#pragma once
#include "CMapGenerator.h"
#include "float3.h"
#include "../int3.h"
#include "../GameConstants.h"
class CZoneGraph;
class CMap;
class CRandomGenerator;
class CRmgTemplateZone;
class CMapGenerator;
class RmgMap;
class Zone;
typedef std::vector<std::pair<TRmgTemplateZoneId, std::shared_ptr<CRmgTemplateZone>>> TZoneVector;
typedef std::map <TRmgTemplateZoneId, std::shared_ptr<CRmgTemplateZone>> TZoneMap;
typedef std::map <std::shared_ptr<CRmgTemplateZone>, float3> TForceVector;
typedef std::map <std::shared_ptr<CRmgTemplateZone>, float> TDistanceVector;
typedef std::vector<std::pair<TRmgTemplateZoneId, std::shared_ptr<Zone>>> TZoneVector;
typedef std::map<TRmgTemplateZoneId, std::shared_ptr<Zone>> TZoneMap;
typedef std::map<std::shared_ptr<Zone>, float3> TForceVector;
typedef std::map<std::shared_ptr<Zone>, float> TDistanceVector;
class CZonePlacer
{
public:
explicit CZonePlacer(CMapGenerator * gen);
explicit CZonePlacer(RmgMap & map);
int3 cords (const float3 f) const;
float metric (const int3 &a, const int3 &b) const;
float getDistance(float distance) const; //additional scaling without 0 divison
~CZonePlacer();
void placeZones(CRandomGenerator * rand);
void assignZones();
void assignZones(CRandomGenerator * rand);
private:
void prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const bool underground, CRandomGenerator * rand);
@@ -56,5 +56,5 @@ private:
//float a1, b1, c1, a2, b2, c2;
//CMap * map;
//std::unique_ptr<CZoneGraph> graph;
CMapGenerator * gen;
RmgMap & map;
};

View File

@@ -0,0 +1,280 @@
/*
* 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 "../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"
void ConnectionsPlacer::process()
{
collectNeighbourZones();
for(auto & c : dConnections)
{
if(c.getZoneA() != zone.getId() && c.getZoneB() != zone.getId())
continue;
if(vstd::contains(dCompleted, c))
continue;
selfSideDirectConnection(c);
}
createBorder(map, zone);
for(auto & c : dConnections)
{
if(c.getZoneA() != zone.getId() && c.getZoneB() != zone.getId())
continue;
if(vstd::contains(dCompleted, c))
continue;
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
bool directProhibited = vstd::contains(Terrain::Manager::getInfo(zone.getTerrainType()).prohibitTransitions, otherZone->getTerrainType())
|| vstd::contains(Terrain::Manager::getInfo(otherZone->getTerrainType()).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, generator.rand);
guardPos = zone.areaPossible().nearest(borderPos);
assert(borderPos != guardPos);
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), 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);
}
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(ObjectTemplate());
auto gate2 = factory->create(ObjectTemplate());
rmg::Object rmgGate1(*gate1), 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;
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.getTile(tile);
float dist = ti.getNearestObjectDistance();
if(dist < minDist)
return -1.f;
rmg::Area toPlace(rmgGate1.getArea() + rmgGate1.getAccessibleArea());
toPlace.translate(-zShift);
path2 = managerOther.placeAndConnectObject(toPlace, rmgGate2, minDist, guarded2, true, false);
return path2.valid() ? 1.f : -1.f;
}, guarded1, true, false);
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(ObjectTemplate());
auto teleport2 = factory->create(ObjectTemplate());
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(auto & i : border)
{
if(!map.isOnMap(i))
continue;
auto zid = map.getZoneID(i);
assert(zid != zone.getId());
dNeighbourZones[zid].insert(i);
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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"
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);
protected:
void collectNeighbourZones();
protected:
std::vector<rmg::ZoneConnection> dConnections, dCompleted;
std::map<TRmgTemplateZoneId, rmg::Tileset> dNeighbourZones;
};

220
lib/rmg/Functions.cpp Normal file
View File

@@ -0,0 +1,220 @@
/*
* Functions.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 "Functions.h"
#include "CMapGenerator.h"
#include "ObjectManager.h"
#include "RoadPlacer.h"
#include "TreasurePlacer.h"
#include "ConnectionsPlacer.h"
#include "TownPlacer.h"
#include "WaterProxy.h"
#include "WaterRoutes.h"
#include "RmgMap.h"
#include "TileInfo.h"
#include "RmgPath.h"
#include "../CTownHandler.h"
#include "../mapping/CMapEditManager.h"
#include "../mapping/CMap.h"
#include "../mapObjects/CommonConstructors.h"
#include "../mapObjects/MapObjects.h" //needed to resolve templates for CommonConstructors.h
#include "../VCMI_Lib.h"
void createModificators(RmgMap & map)
{
for(auto & z : map.getZones())
{
auto & zone = *z.second;
switch(zone.getType())
{
case ETemplateZoneType::WATER:
zone.addModificator<ObjectManager>();
zone.addModificator<TreasurePlacer>();
zone.addModificator<WaterProxy>();
zone.addModificator<WaterRoutes>();
break;
default:
zone.addModificator<TownPlacer>();
zone.addModificator<ObjectManager>();
zone.addModificator<ConnectionsPlacer>();
zone.addModificator<TreasurePlacer>();
zone.addModificator<RoadPlacer>();
break;
}
}
}
rmg::Tileset collectDistantTiles(const Zone& zone, int distance)
{
int distanceSq = distance * distance;
auto subarea = zone.getArea().getSubarea([&zone, distanceSq](const int3 & t)
{
return t.dist2dSQ(zone.getPos()) > distanceSq;
});
return subarea.getTiles();
}
void createBorder(RmgMap & gen, Zone & zone)
{
rmg::Area borderArea(zone.getArea().getBorder());
rmg::Area borderOutsideArea(zone.getArea().getBorderOutside());
auto blockBorder = borderArea.getSubarea([&gen, &borderOutsideArea](const int3 & t)
{
auto tile = borderOutsideArea.nearest(t);
return gen.isOnMap(tile) && gen.getZones()[gen.getZoneID(tile)]->getType() != ETemplateZoneType::WATER;
});
for(auto & tile : blockBorder.getTilesVector())
{
if(gen.isPossible(tile))
{
gen.setOccupied(tile, ETileType::BLOCKED);
zone.areaPossible().erase(tile);
}
gen.foreachDirectNeighbour(tile, [&gen, &zone](int3 &nearbyPos)
{
if(gen.isPossible(nearbyPos) && gen.getZoneID(nearbyPos) == zone.getId())
{
gen.setOccupied(nearbyPos, ETileType::BLOCKED);
zone.areaPossible().erase(nearbyPos);
}
});
}
}
void paintZoneTerrain(const Zone & zone, CRandomGenerator & generator, RmgMap & map, const Terrain & terrainType)
{
auto v = zone.getArea().getTilesVector();
map.getEditManager()->getTerrainSelection().setSelection(v);
map.getEditManager()->drawTerrain(terrainType, &generator);
}
int chooseRandomAppearance(CRandomGenerator & generator, si32 ObjID, const Terrain & terrain)
{
auto factories = VLC->objtypeh->knownSubObjects(ObjID);
vstd::erase_if(factories, [ObjID, &terrain](si32 f)
{
return VLC->objtypeh->getHandlerFor(ObjID, f)->getTemplates(terrain).empty();
});
return *RandomGeneratorUtil::nextItem(factories, generator);
}
void initTerrainType(Zone & zone, CMapGenerator & gen)
{
if(zone.getType()==ETemplateZoneType::WATER)
{
//collect all water terrain types
std::vector<Terrain> waterTerrains;
for(auto & terrain : Terrain::Manager::terrains())
if(terrain.isWater())
waterTerrains.push_back(terrain);
zone.setTerrainType(*RandomGeneratorUtil::nextItem(waterTerrains, gen.rand));
}
else
{
if(zone.isMatchTerrainToTown() && zone.getTownType() != ETownType::NEUTRAL)
{
zone.setTerrainType((*VLC->townh)[zone.getTownType()]->nativeTerrain);
}
else
{
zone.setTerrainType(*RandomGeneratorUtil::nextItem(zone.getTerrainTypes(), gen.rand));
}
//TODO: allow new types of terrain?
{
if(zone.isUnderground())
{
if(!vstd::contains(gen.getConfig().terrainUndergroundAllowed, zone.getTerrainType()))
{
//collect all underground terrain types
std::vector<Terrain> undegroundTerrains;
for(auto & terrain : Terrain::Manager::terrains())
if(terrain.isUnderground())
undegroundTerrains.push_back(terrain);
zone.setTerrainType(*RandomGeneratorUtil::nextItem(undegroundTerrains, gen.rand));
}
}
else
{
if(vstd::contains(gen.getConfig().terrainGroundProhibit, zone.getTerrainType()) || zone.getTerrainType().isUnderground())
zone.setTerrainType(Terrain("dirt"));
}
}
}
}
void createObstaclesCommon2(RmgMap & map, CRandomGenerator & generator)
{
if(map.map().twoLevel)
{
//finally mark rock tiles as occupied, spawn no obstacles there
for(int x = 0; x < map.map().width; x++)
{
for(int y = 0; y < map.map().height; y++)
{
int3 tile(x, y, 1);
if(!map.map().getTile(tile).terType.isPassable())
{
map.setOccupied(tile, ETileType::USED);
}
}
}
}
//tighten obstacles to improve visuals
/*for (int i = 0; i < 3; ++i)
{
int blockedTiles = 0;
int freeTiles = 0;
for (int z = 0; z < (map.map().twoLevel ? 2 : 1); z++)
{
for (int x = 0; x < map.map().width; x++)
{
for (int y = 0; y < map.map().height; y++)
{
int3 tile(x, y, z);
if (!map.isPossible(tile)) //only possible tiles can change
continue;
int blockedNeighbours = 0;
int freeNeighbours = 0;
map.foreach_neighbour(tile, [&map, &blockedNeighbours, &freeNeighbours](int3 &pos)
{
if (map.isBlocked(pos))
blockedNeighbours++;
if (map.isFree(pos))
freeNeighbours++;
});
if (blockedNeighbours > 4)
{
map.setOccupied(tile, ETileType::BLOCKED);
blockedTiles++;
}
else if (freeNeighbours > 4)
{
map.setOccupied(tile, ETileType::FREE);
freeTiles++;
}
}
}
}
logGlobal->trace("Set %d tiles to BLOCKED and %d tiles to FREE", blockedTiles, freeTiles);
}*/
}

48
lib/rmg/Functions.h Normal file
View File

@@ -0,0 +1,48 @@
/*
* Functions.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 <boost/heap/priority_queue.hpp> //A*
class RmgMap;
class ObjectManager;
class ObjectTemplate;
class CMapGenerator;
class rmgException : public std::exception
{
std::string msg;
public:
explicit rmgException(const std::string& _Message) : msg(_Message)
{
}
virtual ~rmgException() throw ()
{
};
const char *what() const throw () override
{
return msg.c_str();
}
};
rmg::Tileset collectDistantTiles(const Zone & zone, int distance);
void createBorder(RmgMap & gen, Zone & zone);
void paintZoneTerrain(const Zone & zone, CRandomGenerator & generator, RmgMap & map, const Terrain & terrainType);
void initTerrainType(Zone & zone, CMapGenerator & gen);
int chooseRandomAppearance(CRandomGenerator & generator, si32 ObjID, const Terrain & terrain);

417
lib/rmg/ObjectManager.cpp Normal file
View File

@@ -0,0 +1,417 @@
/*
* 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 "../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"
void ObjectManager::process()
{
zone.fractalize();
createRequiredObjects();
}
void ObjectManager::init()
{
DEPENDENCY(WaterAdopter);
POSTFUNCTION(RoadPlacer);
}
void ObjectManager::addRequiredObject(CGObjectInstance * obj, si32 strength)
{
requiredObjects.push_back(std::make_pair(obj, strength));
}
void ObjectManager::addCloseObject(CGObjectInstance * obj, si32 strength)
{
closeObjects.push_back(std::make_pair(obj, strength));
}
void ObjectManager::addNearbyObject(CGObjectInstance * obj, CGObjectInstance * nearbyTarget)
{
nearbyObjects.push_back(std::make_pair(obj, nearbyTarget));
}
void ObjectManager::updateDistances(const rmg::Object & obj)
{
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((float)d, map.getNearestObjectDistance(tile)));
}
}
const rmg::Area & ObjectManager::getVisitableArea() const
{
return objectsVisitableArea;
}
int3 ObjectManager::findPlaceForObject(const rmg::Area & searchArea, rmg::Object & obj, std::function<float(const int3)> weightFunction, bool optimizer) const
{
float bestWeight = 0.f;
int3 result(-1, -1, -1);
for(const auto & tile : searchArea.getTiles())
{
obj.setPosition(tile);
if(!searchArea.contains(obj.getArea()) || !searchArea.overlap(obj.getAccessibleArea()))
continue;
float weight = weightFunction(tile);
if(weight > bestWeight)
{
bestWeight = weight;
result = tile;
if(!optimizer)
break;
}
}
if(result.valid())
obj.setPosition(result);
return result;
}
int3 ObjectManager::findPlaceForObject(const rmg::Area & searchArea, rmg::Object & obj, si32 min_dist, bool optimizer) const
{
return findPlaceForObject(searchArea, obj, [this, min_dist](const int3 & tile)
{
auto ti = map.getTile(tile);
float dist = ti.getNearestObjectDistance();
if(dist < 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, bool optimizer) const
{
return placeAndConnectObject(searchArea, obj, [this, min_dist](const int3 & tile)
{
auto ti = map.getTile(tile);
float dist = ti.getNearestObjectDistance();
if(dist < min_dist)
return -1.f;
return dist;
}, isGuarded, onlyStraight, optimizer);
}
rmg::Path ObjectManager::placeAndConnectObject(const rmg::Area & searchArea, rmg::Object & obj, std::function<float(const int3)> weightFunction, bool isGuarded, bool onlyStraight, bool 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)
{
auto & guardedArea = obj.instances().back()->getAccessibleArea();
accessibleArea.intersect(guardedArea);
}
auto path = zone.searchPath(accessibleArea, onlyStraight, [&obj, isGuarded](const int3 & t)
{
if(isGuarded)
{
auto & guardedArea = obj.instances().back()->getAccessibleArea();
auto & unguardedArea = obj.getAccessibleArea(isGuarded);
if(unguardedArea.contains(t) && !guardedArea.contains(t))
return false;
}
return !obj.getArea().contains(t);
});
if(path.valid())
{
return path;
}
}
}
bool ObjectManager::createRequiredObjects()
{
logGlobal->trace("Creating required objects");
for(const auto & object : requiredObjects)
{
auto * obj = object.first;
int3 pos;
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, 3, guarded, false, true);
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(), generator.rand));
placeObject(rmgNearObject, false, false);
}
}
for(const auto & object : closeObjects)
{
auto * obj = object.first;
int3 pos;
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, true);
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(), generator.rand));
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.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(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(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
int mapMonsterStrength = map.getMapGenOptions().getMonsterStrength();
int monsterStrength = (zoneGuard ? 0 : zone.zoneMonsterStrength) + 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->AIValue) //bug #2681
continue;
if(!vstd::contains(zone.getMonsterTypes(), cre->faction))
continue;
if(((si32)(cre->AIValue * (cre->ammMin + cre->ammMax) / 2) < strength) && (strength < (si32)cre->AIValue * 100)) //at least one full monster. size between average size of given stack and 100
{
possibleCreatures.push_back(cre->idNumber);
}
}
if(possibleCreatures.size())
{
creId = *RandomGeneratorUtil::nextItem(possibleCreatures, generator.rand);
amount = strength / VLC->creh->objects[creId]->AIValue;
if (amount >= 4)
amount = static_cast<int>(amount * generator.rand.nextDouble(0.75, 1.25));
}
else //just pick any available creature
{
creId = CreatureID(132); //Azure Dragon
amount = strength / VLC->creh->objects[creId]->AIValue;
}
auto guardFactory = VLC->objtypeh->getHandlerFor(Obj::MONSTER, creId);
auto guard = (CGCreature *) guardFactory->create(ObjectTemplate());
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());
return true;
}

56
lib/rmg/ObjectManager.h Normal file
View File

@@ -0,0 +1,56 @@
/*
* 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"
class CGObjectInstance;
class ObjectTemplate;
class CGCreature;
class ObjectManager: public Modificator
{
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, bool optimizer) const;
int3 findPlaceForObject(const rmg::Area & searchArea, rmg::Object & obj, std::function<float(const int3)> weightFunction, bool optimizer) const;
rmg::Path placeAndConnectObject(const rmg::Area & searchArea, rmg::Object & obj, si32 min_dist, bool isGuarded, bool onlyStraight, bool optimizer) const;
rmg::Path placeAndConnectObject(const rmg::Area & searchArea, rmg::Object & obj, std::function<float(const int3)> weightFunction, bool isGuarded, bool onlyStraight, bool 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);
const rmg::Area & getVisitableArea() 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;
};

188
lib/rmg/ObstaclePlacer.cpp Normal file
View File

@@ -0,0 +1,188 @@
/*
* 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 "../mapObjects/CObjectClassesHandler.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"
void ObstaclePlacer::process()
{
auto * manager = zone.getModificator<ObjectManager>();
if(!manager)
return;
auto * riverManager = zone.getModificator<RiverPlacer>();
typedef std::vector<ObjectTemplate> ObstacleVector;
//obstacleVector possibleObstacles;
std::map<int, ObstacleVector> obstaclesBySize;
typedef std::pair<int, ObstacleVector> ObstaclePair;
std::vector<ObstaclePair> possibleObstacles;
//get all possible obstacles for this terrain
for(auto primaryID : VLC->objtypeh->knownObjects())
{
for(auto secondaryID : VLC->objtypeh->knownSubObjects(primaryID))
{
auto handler = VLC->objtypeh->getHandlerFor(primaryID, secondaryID);
if(handler->isStaticObject())
{
for(auto temp : handler->getTemplates())
{
if(temp.canBePlacedAt(zone.getTerrainType()) && temp.getBlockMapOffset().valid())
obstaclesBySize[temp.getBlockedOffsets().size()].push_back(temp);
}
}
}
}
for(auto o : obstaclesBySize)
{
possibleObstacles.push_back(o);
}
boost::sort(possibleObstacles, [](const ObstaclePair &p1, const ObstaclePair &p2) -> bool
{
return p1.first > p2.first; //bigger obstacles first
});
auto blockedArea = zone.area().getSubarea([this](const int3 & t)
{
return map.shouldBeBlocked(t);
});
blockedArea.subtract(zone.areaUsed());
zone.areaPossible().subtract(blockedArea);
auto prohibitedArea = zone.freePaths() + zone.areaUsed() + manager->getVisitableArea();
//reverse order, since obstacles begin in bottom-right corner, while the map coordinates begin in top-left
auto blockedTiles = blockedArea.getTilesVector();
int tilePos = 0;
while(!blockedArea.empty() && tilePos < blockedArea.getTilesVector().size())
{
auto tile = blockedArea.getTilesVector()[tilePos];
std::list<rmg::Object> allObjects;
std::vector<std::pair<rmg::Object*, int3>> weightedObjects; //obj + position
int maxWeight = std::numeric_limits<int>::min();
for(int i = 0; i < possibleObstacles.size(); ++i)
{
if(!possibleObstacles[i].first)
continue;
for(auto & temp : possibleObstacles[i].second)
{
auto handler = VLC->objtypeh->getHandlerFor(temp.id, temp.subid);
auto obj = handler->create(temp);
allObjects.emplace_back(*obj);
rmg::Object * rmgObject = &allObjects.back();
for(auto & offset : obj->getBlockedOffsets())
{
rmgObject->setPosition(tile - offset);
if(!map.isOnMap(rmgObject->getPosition()))
continue;
if(!rmgObject->getArea().getSubarea([this](const int3 & t)
{
return !map.isOnMap(t);
}).empty())
continue;
if(prohibitedArea.overlap(rmgObject->getArea()))
continue;
if(!zone.area().contains(rmgObject->getArea()))
continue;
int coverageBlocked = 0;
int coveragePossible = 0;
//do not use area intersection in optimization purposes
for(auto & t : rmgObject->getArea().getTilesVector())
{
if(map.shouldBeBlocked(t))
++coverageBlocked;
if(zone.areaPossible().contains(t))
++coveragePossible;
}
int coverageOverlap = possibleObstacles[i].first - coverageBlocked - coveragePossible;
int weight = possibleObstacles[i].first + coverageBlocked - coverageOverlap * possibleObstacles[i].first;
assert(coverageOverlap >= 0);
if(weight > maxWeight)
{
weightedObjects.clear();
maxWeight = weight;
weightedObjects.emplace_back(rmgObject, rmgObject->getPosition());
if(weight > 0)
break;
}
else if(weight == maxWeight)
weightedObjects.emplace_back(rmgObject, rmgObject->getPosition());
}
}
if(maxWeight > 0)
break;
}
if(weightedObjects.empty())
{
tilePos += 1;
continue;
}
auto objIter = RandomGeneratorUtil::nextItem(weightedObjects, generator.rand);
objIter->first->setPosition(objIter->second);
manager->placeObject(*objIter->first, false, false);
blockedArea.subtract(objIter->first->getArea());
tilePos = 0;
//river processing
if(riverManager)
{
if(objIter->first->instances().front()->object().typeName == "mountain")
riverManager->riverSource().unite(objIter->first->getArea());
if(objIter->first->instances().front()->object().typeName == "lake")
riverManager->riverSink().unite(objIter->first->getArea());
}
if(maxWeight < 0)
logGlobal->warn("Placed obstacle with negative weight at %s", objIter->second.toString());
for(auto & o : allObjects)
{
if(&o != objIter->first)
o.clear();
}
}
}
void ObstaclePlacer::init()
{
DEPENDENCY(ObjectManager);
DEPENDENCY(TreasurePlacer);
DEPENDENCY(WaterRoutes);
DEPENDENCY(WaterProxy);
DEPENDENCY(RoadPlacer);
DEPENDENCY_ALL(RockPlacer);
}

21
lib/rmg/ObstaclePlacer.h Normal file
View File

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

404
lib/rmg/RiverPlacer.cpp Normal file
View File

@@ -0,0 +1,404 @@
/*
* 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 "../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"
const int RIVER_DELTA_ID = 143;
const int RIVER_DELTA_SUBTYPE = 0;
const std::map<std::string, std::string> RIVER_DELTA_TEMPLATE_NAME
{
{RIVER_NAMES[1], "clrdelt"},
{RIVER_NAMES[2], "icedelt"},
{RIVER_NAMES[3], "muddelt"},
{RIVER_NAMES[4], "lavdelt"}
};
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(auto & t : riverNodes)
connectRiver(t);
if(!rivers.empty())
drawRivers();
}
void RiverPlacer::init()
{
DEPENDENCY_ALL(WaterProxy);
DEPENDENCY(ObjectManager);
DEPENDENCY(ObstaclePlacer);
}
void RiverPlacer::drawRivers()
{
map.getEditManager()->getTerrainSelection().setSelection(rivers.getTilesVector());
map.getEditManager()->drawRiver(Terrain::Manager::getInfo(zone.getTerrainType()).river, &generator.rand);
}
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(auto & t : zone.area().getTilesVector())
{
heightMap[t] = generator.rand.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.map().height; j += 2)
{
for(int i = 0; i < map.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(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(), generator.rand);
source.add(elem);
outOfMapInternal.erase(elem);
}
if(!outOfMapInternal.empty())
{
auto elem = *RandomGeneratorUtil::nextItem(outOfMapInternal.getTilesVector(), generator.rand);
sink.add(elem);
outOfMapInternal.erase(elem);
}
//calculate delta positions
if(connectedToWaterZoneId > -1)
{
auto river = Terrain::Manager::getInfo(zone.getTerrainType()).river;
auto & a = neighbourZonesTiles[connectedToWaterZoneId];
auto availableArea = zone.areaPossible() + zone.freePaths();
for(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_NAMES[2])
{
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_NAMES[3])
{
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(), generator.rand));
}
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 river = Terrain::Manager::getInfo(zone.getTerrainType()).river;
if(river.empty() || river == RIVER_NAMES[0])
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<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));
std::string targetTemplateName = RIVER_DELTA_TEMPLATE_NAME.at(river) + 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());
}

48
lib/rmg/RiverPlacer.h Normal file
View File

@@ -0,0 +1,48 @@
/*
* 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"
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;
};

399
lib/rmg/RmgArea.cpp Normal file
View File

@@ -0,0 +1,399 @@
/*
* RmgArea.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 "RmgArea.h"
#include "CMapGenerator.h"
namespace rmg
{
void toAbsolute(Tileset & tiles, const int3 & position)
{
Tileset temp;
for(auto & tile : tiles)
{
temp.insert(tile + position);
}
tiles = std::move(temp);
}
void toRelative(Tileset & tiles, const int3 & position)
{
toAbsolute(tiles, -position);
}
Area::Area(const Area & area): dTiles(area.dTiles), dTotalShiftCache(area.dTotalShiftCache)
{
}
Area::Area(const Area && area): dTiles(std::move(area.dTiles)), dTotalShiftCache(std::move(area.dTotalShiftCache))
{
}
Area & Area::operator=(const Area & area)
{
clear();
dTiles = area.dTiles;
dTotalShiftCache = area.dTotalShiftCache;
return *this;
}
Area::Area(const Tileset & tiles): dTiles(tiles)
{
}
Area::Area(const Tileset & relative, const int3 & position): dTiles(relative), dTotalShiftCache(position)
{
}
void Area::invalidate()
{
getTiles();
dTilesVectorCache.clear();
dBorderCache.clear();
dBorderOutsideCache.clear();
}
bool Area::connected() const
{
std::list<int3> queue({*dTiles.begin()});
Tileset connected = dTiles; //use invalidated cache - ok
while(!queue.empty())
{
auto t = queue.front();
connected.erase(t);
queue.pop_front();
for(auto & i : int3::getDirs())
{
if(connected.count(t + i))
{
queue.push_back(t + i);
}
}
}
return connected.empty();
}
std::list<Area> connectedAreas(const Area & area)
{
std::list<Area> result;
Tileset connected = area.getTiles();
while(!connected.empty())
{
result.emplace_back();
std::list<int3> queue({*connected.begin()});
std::set<int3> queueSet({*connected.begin()});
while(!queue.empty())
{
auto t = queue.front();
connected.erase(t);
result.back().add(t);
queue.pop_front();
for(auto & i : int3::getDirs())
{
auto tile = t + i;
if(!queueSet.count(tile) && connected.count(tile) && !result.back().contains(tile))
{
queueSet.insert(tile);
queue.push_back(tile);
}
}
}
}
return result;
}
const Tileset & Area::getTiles() const
{
if(dTotalShiftCache != int3())
{
toAbsolute(dTiles, dTotalShiftCache);
dTotalShiftCache = int3();
}
return dTiles;
}
const std::vector<int3> & Area::getTilesVector() const
{
if(dTilesVectorCache.empty())
{
getTiles();
dTilesVectorCache.assign(dTiles.begin(), dTiles.end());
}
return dTilesVectorCache;
}
const Tileset & Area::getBorder() const
{
if(!dBorderCache.empty())
return dBorderCache;
//compute border cache
for(auto & t : dTiles)
{
for(auto & i : int3::getDirs())
{
if(!dTiles.count(t + i))
{
dBorderCache.insert(t + dTotalShiftCache);
break;
}
}
}
return dBorderCache;
}
const Tileset & Area::getBorderOutside() const
{
if(!dBorderOutsideCache.empty())
return dBorderOutsideCache;
//compute outside border cache
for(auto & t : dTiles)
{
for(auto & i : int3::getDirs())
{
if(!dTiles.count(t + i))
dBorderOutsideCache.insert(t + i + dTotalShiftCache);
}
}
return dBorderOutsideCache;
}
DistanceMap Area::computeDistanceMap(std::map<int, Tileset> & reverseDistanceMap) const
{
reverseDistanceMap.clear();
DistanceMap result;
auto area = *this;
int distance = 0;
while(!area.empty())
{
for(auto & tile : area.getBorder())
result[tile] = distance;
reverseDistanceMap[distance++] = area.getBorder();
area.subtract(area.getBorder());
}
return result;
}
bool Area::empty() const
{
return dTiles.empty();
}
bool Area::contains(const int3 & tile) const
{
return dTiles.count(tile - dTotalShiftCache);
}
bool Area::contains(const std::vector<int3> & tiles) const
{
for(auto & t : tiles)
{
if(!contains(t))
return false;
}
return true;
}
bool Area::contains(const Area & area) const
{
return contains(area.getTilesVector());
}
bool Area::overlap(const std::vector<int3> & tiles) const
{
for(auto & t : tiles)
{
if(contains(t))
return true;
}
return false;
}
bool Area::overlap(const Area & area) const
{
return overlap(area.getTilesVector());
}
int Area::distanceSqr(const int3 & tile) const
{
return nearest(tile).dist2dSQ(tile);
}
int Area::distanceSqr(const Area & area) const
{
int dist = std::numeric_limits<int>::max();
int3 nearTile = *getTilesVector().begin();
int3 otherNearTile = area.nearest(nearTile);
while(dist != otherNearTile.dist2dSQ(nearTile))
{
dist = otherNearTile.dist2dSQ(nearTile);
nearTile = nearest(otherNearTile);
otherNearTile = area.nearest(nearTile);
}
return dist;
}
int3 Area::nearest(const int3 & tile) const
{
return findClosestTile(getTilesVector(), tile);
}
int3 Area::nearest(const Area & area) const
{
int dist = std::numeric_limits<int>::max();
int3 nearTile = *getTilesVector().begin();
int3 otherNearTile = area.nearest(nearTile);
while(dist != otherNearTile.dist2dSQ(nearTile))
{
dist = otherNearTile.dist2dSQ(nearTile);
nearTile = nearest(otherNearTile);
otherNearTile = area.nearest(nearTile);
}
return nearTile;
}
Area Area::getSubarea(std::function<bool(const int3 &)> filter) const
{
Area subset;
for(auto & t : getTilesVector())
if(filter(t))
subset.add(t);
return subset;
}
void Area::clear()
{
dTiles.clear();
dTotalShiftCache = int3();
invalidate();
}
void Area::assign(const Tileset tiles)
{
clear();
dTiles = tiles;
}
void Area::add(const int3 & tile)
{
invalidate();
dTiles.insert(tile);
}
void Area::erase(const int3 & tile)
{
invalidate();
dTiles.erase(tile);
}
void Area::unite(const Area & area)
{
invalidate();
for(auto & t : area.getTilesVector())
{
dTiles.insert(t);
}
}
void Area::intersect(const Area & area)
{
invalidate();
Tileset result;
for(auto & t : area.getTilesVector())
{
if(dTiles.count(t))
result.insert(t);
}
dTiles = result;
}
void Area::subtract(const Area & area)
{
invalidate();
for(auto & t : area.getTilesVector())
{
dTiles.erase(t);
}
}
void Area::translate(const int3 & shift)
{
dBorderCache.clear();
dBorderOutsideCache.clear();
if(dTilesVectorCache.empty())
{
getTiles();
getTilesVector();
}
//avoid recomputation within std::set, use vector instead
dTotalShiftCache += shift;
for(auto & t : dTilesVectorCache)
{
t += shift;
}
//toAbsolute(dTiles, shift);
}
Area operator- (const Area & l, const int3 & r)
{
Area result(l);
result.translate(-r);
return result;
}
Area operator+ (const Area & l, const int3 & r)
{
Area result(l);
result.translate(r);
return result;
}
Area operator+ (const Area & l, const Area & r)
{
Area result(l);
result.unite(r);
return result;
}
Area operator- (const Area & l, const Area & r)
{
Area result(l);
result.subtract(r);
return result;
}
Area operator* (const Area & l, const Area & r)
{
Area result(l);
result.intersect(r);
return result;
}
bool operator== (const Area & l, const Area & r)
{
return l.getTiles() == r.getTiles();
}
}

83
lib/rmg/RmgArea.h Normal file
View File

@@ -0,0 +1,83 @@
/*
* RmgArea.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"
namespace rmg
{
using Tileset = std::set<int3>;
using DistanceMap = std::map<int3, int>;
void toAbsolute(Tileset & tiles, const int3 & position);
void toRelative(Tileset & tiles, const int3 & position);
class Area
{
public:
Area() = default;
Area(const Area &);
Area(const Area &&);
Area(const Tileset & tiles);
Area(const Tileset & relative, const int3 & position); //create from relative positions
Area & operator= (const Area &);
const Tileset & getTiles() const;
const std::vector<int3> & getTilesVector() const;
const Tileset & getBorder() const; //lazy cache invalidation
const Tileset & getBorderOutside() const; //lazy cache invalidation
DistanceMap computeDistanceMap(std::map<int, Tileset> & reverseDistanceMap) const;
Area getSubarea(std::function<bool(const int3 &)> filter) const;
bool connected() const; //is connected
bool empty() const;
bool contains(const int3 & tile) const;
bool contains(const std::vector<int3> & tiles) const;
bool contains(const Area & area) const;
bool overlap(const Area & area) const;
bool overlap(const std::vector<int3> & tiles) const;
int distanceSqr(const int3 & tile) const;
int distanceSqr(const Area & area) const;
int3 nearest(const int3 & tile) const;
int3 nearest(const Area & area) const;
void clear();
void assign(const Tileset tiles); //do not use reference to allow assigment of cached data
void add(const int3 & tile);
void erase(const int3 & tile);
void unite(const Area & area);
void intersect(const Area & area);
void subtract(const Area & area);
void translate(const int3 & shift);
friend Area operator+ (const Area & l, const int3 & r); //translation
friend Area operator- (const Area & l, const int3 & r); //translation
friend Area operator+ (const Area & l, const Area & r); //union
friend Area operator* (const Area & l, const Area & r); //intersection
friend Area operator- (const Area & l, const Area & r); //AreaL reduced by tiles from AreaR
friend bool operator== (const Area & l, const Area & r);
friend std::list<Area> connectedAreas(const Area & area);
private:
void invalidate();
void computeBorderCache();
void computeBorderOutsideCache();
mutable Tileset dTiles;
mutable std::vector<int3> dTilesVectorCache;
mutable Tileset dBorderCache;
mutable Tileset dBorderOutsideCache;
mutable int3 dTotalShiftCache;
};
}

343
lib/rmg/RmgMap.cpp Normal file
View File

@@ -0,0 +1,343 @@
/*
* RmgMap.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 "RmgMap.h"
#include "TileInfo.h"
#include "CMapGenOptions.h"
#include "Zone.h"
#include "../mapping/CMapEditManager.h"
#include "../CTownHandler.h"
#include "ObjectManager.h"
#include "RoadPlacer.h"
#include "TreasurePlacer.h"
#include "ConnectionsPlacer.h"
#include "TownPlacer.h"
#include "WaterAdopter.h"
#include "WaterProxy.h"
#include "WaterRoutes.h"
#include "RockPlacer.h"
#include "ObstaclePlacer.h"
#include "RiverPlacer.h"
#include "TerrainPainter.h"
#include "Functions.h"
#include "CMapGenerator.h"
static const int3 dirs4[] = {int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0)};
static const int3 dirsDiagonal[] = { int3(1,1,0),int3(1,-1,0),int3(-1,1,0),int3(-1,-1,0) };
RmgMap::RmgMap(const CMapGenOptions& mapGenOptions) :
mapGenOptions(mapGenOptions), zonesTotal(0)
{
mapInstance = std::make_unique<CMap>();
getEditManager()->getUndoManager().setUndoRedoLimit(0);
}
void RmgMap::foreach_neighbour(const int3 &pos, std::function<void(int3& pos)> foo)
{
for(const int3 &dir : int3::getDirs())
{
int3 n = pos + dir;
/*important notice: perform any translation before this function is called,
so the actual mapInstance->position is checked*/
if(mapInstance->isInTheMap(n))
foo(n);
}
}
void RmgMap::foreachDirectNeighbour(const int3& pos, std::function<void(int3& pos)> foo)
{
for(const int3 &dir : dirs4)
{
int3 n = pos + dir;
if(mapInstance->isInTheMap(n))
foo(n);
}
}
void RmgMap::foreachDiagonalNeighbour(const int3& pos, std::function<void(int3& pos)> foo)
{
for (const int3 &dir : dirsDiagonal)
{
int3 n = pos + dir;
if (mapInstance->isInTheMap(n))
foo(n);
}
}
void RmgMap::initTiles(CMapGenerator & generator)
{
mapInstance->initTerrain();
tiles.resize(boost::extents[mapInstance->width][mapInstance->height][mapInstance->twoLevel ? 2 : 1]);
zoneColouring.resize(boost::extents[mapInstance->width][mapInstance->height][mapInstance->twoLevel ? 2 : 1]);
//init native town count with 0
for (auto faction : VLC->townh->getAllowedFactions())
zonesPerFaction[faction] = 0;
getEditManager()->clearTerrain(&generator.rand);
getEditManager()->getTerrainSelection().selectRange(MapRect(int3(0, 0, 0), mapGenOptions.getWidth(), mapGenOptions.getHeight()));
getEditManager()->drawTerrain(Terrain("grass"), &generator.rand);
auto tmpl = mapGenOptions.getMapTemplate();
zones.clear();
for(const auto & option : tmpl->getZones())
{
auto zone = std::make_shared<Zone>(*this, generator);
zone->setOptions(*option.second.get());
zones[zone->getId()] = zone;
}
switch(mapGenOptions.getWaterContent())
{
case EWaterContent::NORMAL:
case EWaterContent::ISLANDS:
TRmgTemplateZoneId waterId = zones.size() + 1;
rmg::ZoneOptions options;
options.setId(waterId);
options.setType(ETemplateZoneType::WATER);
auto zone = std::make_shared<Zone>(*this, generator);
zone->setOptions(options);
zones[zone->getId()] = zone;
break;
}
}
void RmgMap::addModificators()
{
for(auto & z : getZones())
{
auto zone = z.second;
zone->addModificator<ObjectManager>();
zone->addModificator<TreasurePlacer>();
zone->addModificator<ObstaclePlacer>();
zone->addModificator<TerrainPainter>();
if(zone->getType() == ETemplateZoneType::WATER)
{
for(auto & z1 : getZones())
{
z1.second->addModificator<WaterAdopter>();
z1.second->getModificator<WaterAdopter>()->setWaterZone(zone->getId());
}
zone->addModificator<WaterProxy>();
zone->addModificator<WaterRoutes>();
}
else
{
zone->addModificator<TownPlacer>();
zone->addModificator<ConnectionsPlacer>();
zone->addModificator<RoadPlacer>();
zone->addModificator<RiverPlacer>();
}
if(zone->isUnderground())
{
zone->addModificator<RockPlacer>();
}
}
}
RmgMap::~RmgMap()
{
}
CMap & RmgMap::map() const
{
return *mapInstance;
}
CMapEditManager* RmgMap::getEditManager() const
{
return mapInstance->getEditManager();
}
bool RmgMap::isOnMap(const int3 & tile) const
{
return mapInstance->isInTheMap(tile);
}
const CMapGenOptions& RmgMap::getMapGenOptions() const
{
return mapGenOptions;
}
void RmgMap::assertOnMap(const int3& tile) const
{
if (!mapInstance->isInTheMap(tile))
throw rmgException(boost::to_string(boost::format("Tile %s is outside the map") % tile.toString()));
}
RmgMap::Zones & RmgMap::getZones()
{
return zones;
}
bool RmgMap::isBlocked(const int3 &tile) const
{
assertOnMap(tile);
return tiles[tile.x][tile.y][tile.z].isBlocked();
}
bool RmgMap::shouldBeBlocked(const int3 &tile) const
{
assertOnMap(tile);
return tiles[tile.x][tile.y][tile.z].shouldBeBlocked();
}
bool RmgMap::isPossible(const int3 &tile) const
{
assertOnMap(tile);
return tiles[tile.x][tile.y][tile.z].isPossible();
}
bool RmgMap::isFree(const int3 &tile) const
{
assertOnMap(tile);
return tiles[tile.x][tile.y][tile.z].isFree();
}
bool RmgMap::isUsed(const int3 &tile) const
{
assertOnMap(tile);
return tiles[tile.x][tile.y][tile.z].isUsed();
}
bool RmgMap::isRoad(const int3& tile) const
{
assertOnMap(tile);
return tiles[tile.x][tile.y][tile.z].isRoad();
}
void RmgMap::setOccupied(const int3 &tile, ETileType::ETileType state)
{
assertOnMap(tile);
tiles[tile.x][tile.y][tile.z].setOccupied(state);
}
void RmgMap::setRoad(const int3& tile, const std::string & roadType)
{
assertOnMap(tile);
tiles[tile.x][tile.y][tile.z].setRoadType(roadType);
}
TileInfo RmgMap::getTile(const int3& tile) const
{
assertOnMap(tile);
return tiles[tile.x][tile.y][tile.z];
}
TRmgTemplateZoneId RmgMap::getZoneID(const int3& tile) const
{
assertOnMap(tile);
return zoneColouring[tile.x][tile.y][tile.z];
}
void RmgMap::setZoneID(const int3& tile, TRmgTemplateZoneId zid)
{
assertOnMap(tile);
zoneColouring[tile.x][tile.y][tile.z] = zid;
}
void RmgMap::setNearestObjectDistance(int3 &tile, float value)
{
assertOnMap(tile);
tiles[tile.x][tile.y][tile.z].setNearestObjectDistance(value);
}
float RmgMap::getNearestObjectDistance(const int3 &tile) const
{
assertOnMap(tile);
return tiles[tile.x][tile.y][tile.z].getNearestObjectDistance();
}
void RmgMap::registerZone(TFaction faction)
{
zonesPerFaction[faction]++;
zonesTotal++;
}
ui32 RmgMap::getZoneCount(TFaction faction)
{
return zonesPerFaction[faction];
}
ui32 RmgMap::getTotalZoneCount() const
{
return zonesTotal;
}
bool RmgMap::isAllowedSpell(SpellID sid) const
{
assert(sid >= 0);
if (sid < mapInstance->allowedSpell.size())
{
return mapInstance->allowedSpell[sid];
}
else
return false;
}
void RmgMap::dump(bool zoneId) const
{
static int id = 0;
std::ofstream out(boost::to_string(boost::format("zone_%d.txt") % id++));
int levels = mapInstance->twoLevel ? 2 : 1;
int width = mapInstance->width;
int height = mapInstance->height;
for (int k = 0; k < levels; k++)
{
for(int j = 0; j < height; j++)
{
for (int i = 0; i < width; i++)
{
if(zoneId)
{
out << getZoneID(int3(i, j, k));
}
else
{
char t = '?';
switch (getTile(int3(i, j, k)).getTileType())
{
case ETileType::FREE:
t = ' '; break;
case ETileType::BLOCKED:
t = '#'; break;
case ETileType::POSSIBLE:
t = '-'; break;
case ETileType::USED:
t = 'O'; break;
}
out << t;
}
}
out << std::endl;
}
out << std::endl;
}
out << std::endl;
}

81
lib/rmg/RmgMap.h Normal file
View File

@@ -0,0 +1,81 @@
/*
* RmgMap.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
*
*/
#pragma once
#include "../int3.h"
#include "../GameConstants.h"
#include "../mapping/CMap.h"
class CMapEditManager;
class TileInfo;
class CMapGenOptions;
class Zone;
class CMapGenerator;
class RmgMap
{
public:
mutable std::unique_ptr<CMap> mapInstance;
CMap & map() const;
RmgMap(const CMapGenOptions& mapGenOptions);
~RmgMap();
CMapEditManager* getEditManager() const;
const CMapGenOptions& getMapGenOptions() const;
void foreach_neighbour(const int3 &pos, std::function<void(int3& pos)> foo);
void foreachDirectNeighbour(const int3 &pos, std::function<void(int3& pos)> foo);
void foreachDiagonalNeighbour(const int3& pos, std::function<void(int3& pos)> foo);
bool isBlocked(const int3 &tile) const;
bool shouldBeBlocked(const int3 &tile) const;
bool isPossible(const int3 &tile) const;
bool isFree(const int3 &tile) const;
bool isUsed(const int3 &tile) const;
bool isRoad(const int3 &tile) const;
bool isOnMap(const int3 & tile) const;
void setOccupied(const int3 &tile, ETileType::ETileType state);
void setRoad(const int3 &tile, const std::string & roadType);
TileInfo getTile(const int3 & tile) const;
float getNearestObjectDistance(const int3 &tile) const;
void setNearestObjectDistance(int3 &tile, float value);
TRmgTemplateZoneId getZoneID(const int3& tile) const;
void setZoneID(const int3& tile, TRmgTemplateZoneId zid);
using Zones = std::map<TRmgTemplateZoneId, std::shared_ptr<Zone>>;
Zones & getZones();
void registerZone(TFaction faction);
ui32 getZoneCount(TFaction faction);
ui32 getTotalZoneCount() const;
void initTiles(CMapGenerator & generator);
void addModificators();
bool isAllowedSpell(SpellID sid) const;
void dump(bool zoneId) const;
private:
void assertOnMap(const int3 &tile) const; //throws
private:
Zones zones;
std::map<TFaction, ui32> zonesPerFaction;
ui32 zonesTotal; //zones that have their main town only
const CMapGenOptions& mapGenOptions;
boost::multi_array<TileInfo, 3> tiles; //[x][y][z]
boost::multi_array<TRmgTemplateZoneId, 3> zoneColouring; //[x][y][z]
};

327
lib/rmg/RmgObject.cpp Normal file
View File

@@ -0,0 +1,327 @@
/*
* RmgObject.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 "RmgObject.h"
#include "RmgMap.h"
#include "../mapObjects/CObjectHandler.h"
#include "../mapping/CMapEditManager.h"
#include "../mapping/CMap.h"
#include "../VCMI_Lib.h"
#include "../mapObjects/CommonConstructors.h"
#include "../mapObjects/MapObjects.h" //needed to resolve templates for CommonConstructors.h
#include "Functions.h"
using namespace rmg;
Object::Instance::Instance(const Object& parent, CGObjectInstance & object): dParent(parent), dObject(object)
{
setPosition(dPosition);
}
Object::Instance::Instance(const Object& parent, CGObjectInstance & object, const int3 & position): Instance(parent, object)
{
setPosition(position);
}
const Area & Object::Instance::getBlockedArea() const
{
if(dBlockedAreaCache.empty())
{
dBlockedAreaCache.assign(dObject.getBlockedPos());
if(dObject.isVisitable() || dBlockedAreaCache.empty())
dBlockedAreaCache.add(dObject.visitablePos());
}
return dBlockedAreaCache;
}
int3 Object::Instance::getPosition(bool isAbsolute) const
{
if(isAbsolute)
return dPosition + dParent.getPosition();
else
return dPosition;
}
int3 Object::Instance::getVisitablePosition() const
{
return dObject.visitablePos();
}
const rmg::Area & Object::Instance::getAccessibleArea() const
{
if(dAccessibleAreaCache.empty())
{
auto neighbours = rmg::Area({getVisitablePosition()}).getBorderOutside();
rmg::Area visitable = rmg::Area(neighbours) - getBlockedArea();
for(auto & from : visitable.getTiles())
{
if(isVisitableFrom(from))
dAccessibleAreaCache.add(from);
}
}
return dAccessibleAreaCache;
}
void Object::Instance::setPosition(const int3 & position)
{
dPosition = position;
dObject.pos = dPosition + dParent.getPosition();
dBlockedAreaCache.clear();
dAccessibleAreaCache.clear();
dParent.dAccessibleAreaCache.clear();
dParent.dAccessibleAreaFullCache.clear();
dParent.dFullAreaCache.clear();
}
void Object::Instance::setPositionRaw(const int3 & position)
{
if(!dObject.pos.valid())
{
dObject.pos = dPosition + dParent.getPosition();
dBlockedAreaCache.clear();
dAccessibleAreaCache.clear();
dParent.dAccessibleAreaCache.clear();
dParent.dAccessibleAreaFullCache.clear();
dParent.dFullAreaCache.clear();
}
auto shift = position + dParent.getPosition() - dObject.pos;
dAccessibleAreaCache.translate(shift);
dBlockedAreaCache.translate(shift);
dPosition = position;
dObject.pos = dPosition + dParent.getPosition();
}
void Object::Instance::setTemplate(const Terrain & terrain)
{
if(dObject.appearance.id == Obj::NO_OBJ)
{
auto templates = VLC->objtypeh->getHandlerFor(dObject.ID, dObject.subID)->getTemplates(terrain);
if(templates.empty())
throw rmgException(boost::to_string(boost::format("Did not find graphics for object (%d,%d) at %s") % dObject.ID % dObject.subID % static_cast<std::string>(terrain)));
dObject.appearance = templates.front();
}
dAccessibleAreaCache.clear();
setPosition(getPosition(false));
}
void Object::Instance::clear()
{
delete &dObject;
dBlockedAreaCache.clear();
dAccessibleAreaCache.clear();
dParent.dAccessibleAreaCache.clear();
dParent.dAccessibleAreaFullCache.clear();
dParent.dFullAreaCache.clear();
}
bool Object::Instance::isVisitableFrom(const int3 & position) const
{
auto relPosition = position - getPosition(true);
return dObject.appearance.isVisitableFrom(relPosition.x, relPosition.y);
}
CGObjectInstance & Object::Instance::object()
{
return dObject;
}
const CGObjectInstance & Object::Instance::object() const
{
return dObject;
}
Object::Object(CGObjectInstance & object, const int3 & position)
{
addInstance(object, position);
}
Object::Object(CGObjectInstance & object)
{
addInstance(object);
}
Object::Object(const Object & object)
{
dStrenght = object.dStrenght;
for(auto & i : object.dInstances)
addInstance(const_cast<CGObjectInstance &>(i.object()), i.getPosition());
setPosition(object.getPosition());
}
std::list<Object::Instance*> Object::instances()
{
std::list<Object::Instance*> result;
for(auto & i : dInstances)
result.push_back(&i);
return result;
}
std::list<const Object::Instance*> Object::instances() const
{
std::list<const Object::Instance*> result;
for(const auto & i : dInstances)
result.push_back(&i);
return result;
}
void Object::addInstance(Instance & object)
{
//assert(object.dParent == *this);
dInstances.push_back(object);
dFullAreaCache.clear();
dAccessibleAreaCache.clear();
dAccessibleAreaFullCache.clear();
}
Object::Instance & Object::addInstance(CGObjectInstance & object)
{
dInstances.emplace_back(*this, object);
dFullAreaCache.clear();
dAccessibleAreaCache.clear();
dAccessibleAreaFullCache.clear();
return dInstances.back();
}
Object::Instance & Object::addInstance(CGObjectInstance & object, const int3 & position)
{
dInstances.emplace_back(*this, object, position);
dFullAreaCache.clear();
dAccessibleAreaCache.clear();
dAccessibleAreaFullCache.clear();
return dInstances.back();
}
const int3 & Object::getPosition() const
{
return dPosition;
}
int3 Object::getVisitablePosition() const
{
assert(!dInstances.empty());
for(auto & instance : dInstances)
if(!getArea().contains(instance.getVisitablePosition()))
return instance.getVisitablePosition();
return dInstances.back().getVisitablePosition(); //fallback - return position of last object
}
const rmg::Area & Object::getAccessibleArea(bool exceptLast) const
{
if(dInstances.empty())
return dAccessibleAreaFullCache;
if(exceptLast && !dAccessibleAreaCache.empty())
return dAccessibleAreaCache;
if(!exceptLast && !dAccessibleAreaFullCache.empty())
return dAccessibleAreaFullCache;
for(auto i = dInstances.begin(); i != std::prev(dInstances.end()); ++i)
dAccessibleAreaCache.unite(i->getAccessibleArea());
dAccessibleAreaFullCache = dAccessibleAreaCache;
dAccessibleAreaFullCache.unite(dInstances.back().getAccessibleArea());
dAccessibleAreaCache.subtract(getArea());
dAccessibleAreaFullCache.subtract(getArea());
if(exceptLast)
return dAccessibleAreaCache;
else
return dAccessibleAreaFullCache;
}
void Object::setPosition(const int3 & position)
{
dAccessibleAreaCache.translate(position - dPosition);
dAccessibleAreaFullCache.translate(position - dPosition);
dFullAreaCache.translate(position - dPosition);
dPosition = position;
for(auto& i : dInstances)
i.setPositionRaw(i.getPosition());
}
void Object::setTemplate(const Terrain & terrain)
{
for(auto& i : dInstances)
i.setTemplate(terrain);
}
const Area & Object::getArea() const
{
if(!dFullAreaCache.empty() || dInstances.empty())
return dFullAreaCache;
for(const auto & instance : dInstances)
{
dFullAreaCache.unite(instance.getBlockedArea());
}
return dFullAreaCache;
}
void Object::Instance::finalize(RmgMap & map)
{
if(!map.isOnMap(getPosition(true)))
throw rmgException(boost::to_string(boost::format("Position of object %d at %s is outside the map") % dObject.id % getPosition(true).toString()));
if (dObject.isVisitable() && !map.isOnMap(dObject.visitablePos()))
throw rmgException(boost::to_string(boost::format("Visitable tile %s of object %d at %s is outside the map") % dObject.visitablePos().toString() % dObject.id % dObject.pos.toString()));
for (auto & tile : dObject.getBlockedPos())
{
if(!map.isOnMap(tile))
throw rmgException(boost::to_string(boost::format("Tile %s of object %d at %s is outside the map") % tile.toString() % dObject.id % dObject.pos.toString()));
}
if (dObject.appearance.id == Obj::NO_OBJ)
{
auto terrainType = map.map().getTile(getPosition(true)).terType;
auto templates = VLC->objtypeh->getHandlerFor(dObject.ID, dObject.subID)->getTemplates(terrainType);
if (templates.empty())
throw rmgException(boost::to_string(boost::format("Did not find graphics for object (%d,%d) at %s (terrain %d)") % dObject.ID % dObject.subID % getPosition(true).toString() % terrainType));
setTemplate(terrainType);
}
for(auto & tile : getBlockedArea().getTilesVector())
{
map.setOccupied(tile, ETileType::ETileType::USED);
}
map.getEditManager()->insertObject(&dObject);
}
void Object::finalize(RmgMap & map)
{
if(dInstances.empty())
throw rmgException("Cannot finalize object without instances");
for(auto iter = dInstances.begin(); iter != dInstances.end(); ++iter)
{
iter->finalize(map);
}
}
void Object::clear()
{
for(auto & instance : dInstances)
instance.clear();
dInstances.clear();
dFullAreaCache.clear();
dAccessibleAreaCache.clear();
dAccessibleAreaFullCache.clear();
}

87
lib/rmg/RmgObject.h Normal file
View File

@@ -0,0 +1,87 @@
/*
* RmgObject.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 "RmgArea.h"
class CGObjectInstance;
class RmgMap;
class Terrain;
namespace rmg {
class Object
{
public:
class Instance
{
public:
Instance(const Object& parent, CGObjectInstance & object);
Instance(const Object& parent, CGObjectInstance & object, const int3 & position);
const Area & getBlockedArea() const;
int3 getVisitablePosition() const;
bool isVisitableFrom(const int3 & tile) const;
const Area & getAccessibleArea() const;
void setTemplate(const Terrain & terrain); //cache invalidation
int3 getPosition(bool isAbsolute = false) const;
void setPosition(const int3 & position); //cache invalidation
void setPositionRaw(const int3 & position); //no cache invalidation
const CGObjectInstance & object() const;
CGObjectInstance & object();
void finalize(RmgMap & map); //cache invalidation
void clear();
private:
mutable Area dBlockedAreaCache;
int3 dPosition;
mutable Area dAccessibleAreaCache;
CGObjectInstance & dObject;
const Object & dParent;
};
Object() = default;
Object(const Object & object);
Object(CGObjectInstance & object);
Object(CGObjectInstance & object, const int3 & position);
void addInstance(Instance & object);
Instance & addInstance(CGObjectInstance & object);
Instance & addInstance(CGObjectInstance & object, const int3 & position);
std::list<Instance*> instances();
std::list<const Instance*> instances() const;
int3 getVisitablePosition() const;
const Area & getAccessibleArea(bool exceptLast = false) const;
const int3 & getPosition() const;
void setPosition(const int3 & position);
void setTemplate(const Terrain & terrain);
const Area & getArea() const; //lazy cache invalidation
void finalize(RmgMap & map);
void clear();
private:
std::list<Instance> dInstances;
mutable Area dFullAreaCache;
mutable Area dAccessibleAreaCache, dAccessibleAreaFullCache;
int3 dPosition;
ui32 dStrenght;
};
}

193
lib/rmg/RmgPath.cpp Normal file
View File

@@ -0,0 +1,193 @@
/*
* RmgPath.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 "RmgPath.h"
#include <boost/heap/priority_queue.hpp> //A*
using namespace rmg;
const std::function<float(const int3 &, const int3 &)> Path::DEFAULT_MOVEMENT_FUNCTION =
[](const int3 & src, const int3 & dst)
{
return 1.f;
};
//A* priority queue
typedef std::pair<int3, float> TDistance;
struct NodeComparer
{
bool operator()(const TDistance & lhs, const TDistance & rhs) const
{
return (rhs.second < lhs.second);
}
};
boost::heap::priority_queue<TDistance, boost::heap::compare<NodeComparer>> createPriorityQueue()
{
return boost::heap::priority_queue<TDistance, boost::heap::compare<NodeComparer>>();
}
Path::Path(const Area & area): dArea(&area)
{
}
Path::Path(const Area & area, const int3 & src): dArea(&area)
{
dPath.add(src);
}
Path::Path(const Path & path): dArea(path.dArea), dPath(path.dPath)
{
}
Path & Path::operator= (const Path & path)
{
//do not modify area
dPath = path.dPath;
return *this;
}
bool Path::valid() const
{
return !dPath.empty();
}
Path Path::invalid()
{
return Path({});
}
Path Path::search(const Tileset & dst, bool straight, std::function<float(const int3 &, const int3 &)> moveCostFunction) const
{
//A* algorithm taken from Wiki http://en.wikipedia.org/wiki/A*_search_algorithm
if(!dArea)
return Path::invalid();
auto resultArea = *dArea + dst;
Path result(resultArea);
if(dst.empty())
return result;
int3 src = rmg::Area(dst).nearest(dPath);
result.connect(src);
Tileset closed; // The set of nodes already evaluated.
auto open = createPriorityQueue(); // The set of tentative nodes to be evaluated, initially containing the start node
std::map<int3, int3> cameFrom; // The map of navigated nodes.
std::map<int3, float> distances;
cameFrom[src] = int3(-1, -1, -1); //first node points to finish condition
distances[src] = 0;
open.push(std::make_pair(src, 0.f));
// Cost from start along best known path.
while(!open.empty())
{
auto node = open.top();
open.pop();
int3 currentNode = node.first;
closed.insert(currentNode);
if(dPath.contains(currentNode)) //we reached connection, stop
{
// Trace the path using the saved parent information and return path
int3 backTracking = currentNode;
while (cameFrom[backTracking].valid())
{
result.dPath.add(backTracking);
backTracking = cameFrom[backTracking];
}
return result;
}
else
{
auto computeTileScore = [&open, &closed, &cameFrom, &currentNode, &distances, &moveCostFunction, &result](const int3& pos) -> void
{
if(closed.count(pos))
return;
if(!result.dArea->contains(pos))
return;
float movementCost = moveCostFunction(currentNode, pos) + currentNode.dist2d(pos);
float distance = distances[currentNode] + movementCost; //we prefer to use already free paths
int bestDistanceSoFar = std::numeric_limits<int>::max();
auto it = distances.find(pos);
if(it != distances.end())
bestDistanceSoFar = static_cast<int>(it->second);
if(distance < bestDistanceSoFar)
{
cameFrom[pos] = currentNode;
open.push(std::make_pair(pos, distance));
distances[pos] = distance;
}
};
auto dirs = int3::getDirs();
std::vector<int3> neighbors(dirs.begin(), dirs.end());
if(straight)
neighbors = { { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0) } };
for(auto & i : neighbors)
{
computeTileScore(currentNode + i);
}
}
}
result.dPath.clear();
return result;
}
Path Path::search(const int3 & dst, bool straight, std::function<float(const int3 &, const int3 &)> moveCostFunction) const
{
return search(Tileset{dst}, straight, moveCostFunction);
}
Path Path::search(const Area & dst, bool straight, std::function<float(const int3 &, const int3 &)> moveCostFunction) const
{
return search(dst.getTiles(), straight, moveCostFunction);
}
Path Path::search(const Path & dst, bool straight, std::function<float(const int3 &, const int3 &)> moveCostFunction) const
{
assert(dst.dArea == dArea);
return search(dst.dPath, straight, moveCostFunction);
}
void Path::connect(const int3 & path)
{
dPath.add(path);
}
void Path::connect(const Tileset & path)
{
Area a(path);
dPath.unite(a);
}
void Path::connect(const Area & path)
{
dPath.unite(path);
}
void Path::connect(const Path & path)
{
dPath.unite(path.dPath);
}
const Area & Path::getPathArea() const
{
return dPath;
}

49
lib/rmg/RmgPath.h Normal file
View File

@@ -0,0 +1,49 @@
/*
* RmgPath.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 "RmgArea.h"
namespace rmg
{
class Path
{
public:
const static std::function<float(const int3 &, const int3 &)> DEFAULT_MOVEMENT_FUNCTION;
Path(const Area & area);
Path(const Area & area, const int3 & src);
Path(const Path & path);
Path & operator= (const Path & path);
bool valid() const;
Path search(const Tileset & dst, bool straight, std::function<float(const int3 &, const int3 &)> moveCostFunction = DEFAULT_MOVEMENT_FUNCTION) const;
Path search(const int3 & dst, bool straight, std::function<float(const int3 &, const int3 &)> moveCostFunction = DEFAULT_MOVEMENT_FUNCTION) const;
Path search(const Area & dst, bool straight, std::function<float(const int3 &, const int3 &)> moveCostFunction = DEFAULT_MOVEMENT_FUNCTION) const;
Path search(const Path & dst, bool straight, std::function<float(const int3 &, const int3 &)> moveCostFunction = DEFAULT_MOVEMENT_FUNCTION) const;
void connect(const Path & path);
void connect(const int3 & path); //TODO: force connection?
void connect(const Area & path); //TODO: force connection?
void connect(const Tileset & path); //TODO: force connection?
const Area & getPathArea() const;
static Path invalid();
private:
const Area * dArea = nullptr;
Area dPath;
};
}

105
lib/rmg/RoadPlacer.cpp Normal file
View File

@@ -0,0 +1,105 @@
/*
* 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 "Functions.h"
#include "CMapGenerator.h"
#include "RmgMap.h"
#include "../mapping/CMap.h"
#include "../mapping/CMapEditManager.h"
#include "RmgPath.h"
void RoadPlacer::process()
{
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()
{
zone.areaPossible().subtract(roads);
zone.freePaths().unite(roads);
map.getEditManager()->getTerrainSelection().setSelection(roads.getTilesVector());
map.getEditManager()->drawRoad(generator.getConfig().defaultRoadType, &generator.rand);
}
void RoadPlacer::addRoadNode(const int3& node)
{
roadNodes.insert(node);
}
void RoadPlacer::connectRoads()
{
if(roadNodes.empty())
return;
//take any tile from road nodes as destination zone for all other road nodes
if(roads.empty())
roads.add(*roadNodes.begin());
for(auto & node : roadNodes)
{
createRoad(node);
}
drawRoads();
}
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);
}

37
lib/rmg/RoadPlacer.h Normal file
View File

@@ -0,0 +1,37 @@
/*
* 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"
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(); //actually updates tiles
protected:
rmg::Tileset roadNodes; //tiles to be connected with roads
rmg::Area roads; //all tiles with roads
rmg::Area areaRoads, isolated;
};

103
lib/rmg/RockPlacer.cpp Normal file
View File

@@ -0,0 +1,103 @@
/*
* 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 "../CRandomGenerator.h"
#include "../mapping/CMapEditManager.h"
void RockPlacer::process()
{
rockTerrain = Terrain::Manager::getInfo(zone.getTerrainType()).rockTerrain;
assert(!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);
});
for(auto & z : map.getZones())
{
if(auto * m = z.second->getModificator<RockPlacer>())
{
if(m != this && !m->isFinished())
return;
}
}
processMap();
}
void RockPlacer::processMap()
{
//merge all areas
for(auto & z : map.getZones())
{
if(auto * m = z.second->getModificator<RockPlacer>())
{
map.getEditManager()->getTerrainSelection().setSelection(m->rockArea.getTilesVector());
map.getEditManager()->drawTerrain(m->rockTerrain, &generator.rand);
}
}
for(auto & z : map.getZones())
{
if(auto * m = z.second->getModificator<RockPlacer>())
{
//now make sure all accessible tiles have no additional rock on them
map.getEditManager()->getTerrainSelection().setSelection(m->accessibleArea.getTilesVector());
map.getEditManager()->drawTerrain(z.second->getTerrainType(), &generator.rand);
m->postProcess();
}
}
}
void RockPlacer::postProcess()
{
//finally mark rock tiles as occupied, spawn no obstacles there
rockArea = zone.area().getSubarea([this](const int3 & t)
{
return !map.map().getTile(t).terType.isPassable();
});
zone.areaUsed().unite(rockArea);
zone.areaPossible().subtract(rockArea);
if(auto * m = zone.getModificator<RiverPlacer>())
m->riverProhibit().unite(rockArea);
if(auto * m = zone.getModificator<RoadPlacer>())
m->areaIsolated().unite(rockArea);
}
void RockPlacer::init()
{
POSTFUNCTION_ALL(RoadPlacer);
DEPENDENCY(TreasurePlacer);
}
char RockPlacer::dump(const int3 & t)
{
if(!map.map().getTile(t).terType.isPassable())
{
return zone.area().contains(t) ? 'R' : 'E';
}
return Modificator::dump(t);
}

30
lib/rmg/RockPlacer.h Normal file
View File

@@ -0,0 +1,30 @@
/*
* 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"
class RockPlacer: public Modificator
{
public:
MODIFICATOR(RockPlacer);
void process() override;
void init() override;
char dump(const int3 &) override;
void processMap();
void postProcess();
protected:
rmg::Area rockArea, accessibleArea;
Terrain rockTerrain;
};

View File

@@ -0,0 +1,35 @@
/*
* 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"
void TerrainPainter::process()
{
initTerrainType(zone, generator);
paintZoneTerrain(zone, generator.rand, map, zone.getTerrainType());
}
void TerrainPainter::init()
{
DEPENDENCY(TownPlacer);
DEPENDENCY_ALL(WaterAdopter);
POSTFUNCTION_ALL(WaterProxy);
POSTFUNCTION_ALL(ConnectionsPlacer);
POSTFUNCTION(ObjectManager);
}

21
lib/rmg/TerrainPainter.h Normal file
View File

@@ -0,0 +1,21 @@
/*
* 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"
class TerrainPainter: public Modificator
{
public:
MODIFICATOR(TerrainPainter);
void process() override;
void init() override;
};

78
lib/rmg/TileInfo.cpp Normal file
View File

@@ -0,0 +1,78 @@
/*
* TileInfo.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 "TileInfo.h"
TileInfo::TileInfo():nearestObjectDistance(float(INT_MAX)), terrain()
{
occupied = ETileType::POSSIBLE; //all tiles are initially possible to place objects or passages
}
float TileInfo::getNearestObjectDistance() const
{
return nearestObjectDistance;
}
void TileInfo::setNearestObjectDistance(float value)
{
nearestObjectDistance = std::max<float>(0, value); //never negative (or unitialized)
}
bool TileInfo::shouldBeBlocked() const
{
return occupied == ETileType::BLOCKED;
}
bool TileInfo::isBlocked() const
{
return occupied == ETileType::BLOCKED || occupied == ETileType::USED;
}
bool TileInfo::isPossible() const
{
return occupied == ETileType::POSSIBLE;
}
bool TileInfo::isFree() const
{
return occupied == ETileType::FREE;
}
bool TileInfo::isRoad() const
{
return roadType != ROAD_NAMES[0];
}
bool TileInfo::isUsed() const
{
return occupied == ETileType::USED;
}
void TileInfo::setOccupied(ETileType::ETileType value)
{
occupied = value;
}
ETileType::ETileType TileInfo::getTileType() const
{
return occupied;
}
Terrain TileInfo::getTerrainType() const
{
return terrain;
}
void TileInfo::setTerrainType(Terrain value)
{
terrain = value;
}
void TileInfo::setRoadType(const std::string & value)
{
roadType = value;
// setOccupied(ETileType::FREE);
}

41
lib/rmg/TileInfo.h Normal file
View File

@@ -0,0 +1,41 @@
/*
* TileInfo.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 "../Terrain.h"
class TileInfo
{
public:
TileInfo();
float getNearestObjectDistance() const;
void setNearestObjectDistance(float value);
bool isBlocked() const;
bool shouldBeBlocked() const;
bool isPossible() const;
bool isFree() const;
bool isUsed() const;
bool isRoad() const;
void setOccupied(ETileType::ETileType value);
Terrain getTerrainType() const;
ETileType::ETileType getTileType() const;
void setTerrainType(Terrain value);
void setRoadType(const std::string & value);
private:
float nearestObjectDistance;
ETileType::ETileType occupied;
Terrain terrain;
std::string roadType;
};

279
lib/rmg/TownPlacer.cpp Normal file
View File

@@ -0,0 +1,279 @@
/*
* 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 "WaterAdopter.h"
#include "TileInfo.h"
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);
placeMines(*manager);
}
void TownPlacer::init()
{
POSTFUNCTION(ObjectManager);
POSTFUNCTION(RoadPlacer);
}
void TownPlacer::placeTowns(ObjectManager & manager)
{
if((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.map().players[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 = (CGTownInstance *) townFactory->create(ObjectTemplate());
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->subID);
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 (generator.rand.nextInt(1, 100) <= 25)
{
zone.setTownType(ETownType::NEUTRAL);
}
else
{
if(zone.getTownTypes().size())
zone.setTownType(*RandomGeneratorUtil::nextItem(zone.getTownTypes(), generator.rand));
else if(zone.getMonsterTypes().size())
zone.setTownType(*RandomGeneratorUtil::nextItem(zone.getMonsterTypes(), generator.rand)); //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);
auto position = manager.findPlaceForObject(zone.areaPossible(), rmgObject, [this](const int3 & t)
{
float distance = zone.getPos().dist2dSQ(t);
return 100000.f - distance; //some big number
}, true);
rmgObject.setPosition(position);
manager.placeObject(rmgObject, false, true);
cleanupBoundaries(rmgObject);
zone.setPos(rmgObject.getVisitablePosition()); //roads lead to main town
return position;
}
bool TownPlacer::placeMines(ObjectManager & manager)
{
using namespace Res;
std::vector<CGMine*> createdMines;
for(const auto & mineInfo : zone.getMinesInfo())
{
ERes res = (ERes)mineInfo.first;
for(int i = 0; i < mineInfo.second; ++i)
{
auto mineHandler = VLC->objtypeh->getHandlerFor(Obj::MINE, res);
auto & rmginfo = mineHandler->getRMGInfo();
auto mine = (CGMine*)mineHandler->create(ObjectTemplate());
mine->producedResource = res;
mine->tempOwner = PlayerColor::NEUTRAL;
mine->producedQuantity = mine->defaultResProduction();
createdMines.push_back(mine);
if(!i && (res == ERes::WOOD || res == ERes::ORE))
manager.addCloseObject(mine, rmginfo.value); //only first wood&ore mines are close
else
manager.addRequiredObject(mine, rmginfo.value);
}
}
//create extra resources
if(int extraRes = generator.getConfig().mineExtraResources)
{
for(auto * mine : createdMines)
{
for(int rc = generator.rand.nextInt(1, extraRes); rc > 0; --rc)
{
auto resourse = (CGResource*) VLC->objtypeh->getHandlerFor(Obj::RESOURCE, mine->producedResource)->create(ObjectTemplate());
resourse->amount = CGResource::RANDOM_AMOUNT;
manager.addNearbyObject(resourse, mine);
}
}
}
return true;
}
void TownPlacer::cleanupBoundaries(const rmg::Object & rmgObject)
{
for(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, PlayerColor player, ObjectManager & manager)
{
for(int i = 0; i < count; i++)
{
si32 subType = zone.getTownType();
if(totalTowns>0)
{
if(!zone.areTownsSameType())
{
if (zone.getTownTypes().size())
subType = *RandomGeneratorUtil::nextItem(zone.getTownTypes(), generator.rand);
else
subType = *RandomGeneratorUtil::nextItem(zone.getDefaultTownTypes(), generator.rand); //it is possible to have zone with no towns allowed
}
}
auto townFactory = VLC->objtypeh->getHandlerFor(Obj::TOWN, subType);
auto town = (CGTownInstance *) townFactory->create(ObjectTemplate());
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->subID);
//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().size() ? zone.getTownTypes() : zone.getDefaultTownTypes());
if(matchUndergroundType)
{
std::set<TFaction> townTypesVerify;
for(TFaction 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, generator.rand);
}
int TownPlacer::getTotalTowns() const
{
return totalTowns;
}

37
lib/rmg/TownPlacer.h Normal file
View File

@@ -0,0 +1,37 @@
/*
* 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"
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, 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;
};

788
lib/rmg/TreasurePlacer.cpp Normal file
View File

@@ -0,0 +1,788 @@
/*
* 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 "../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"
void TreasurePlacer::process()
{
addAllPossibleObjects();
auto * m = zone.getModificator<ObjectManager>();
if(m)
createTreasures(*m);
}
void TreasurePlacer::init()
{
DEPENDENCY(ObjectManager);
DEPENDENCY(ConnectionsPlacer);
POSTFUNCTION(RoadPlacer);
}
void TreasurePlacer::setQuestArtZone(Zone * otherZone)
{
questArtZone = otherZone;
}
void TreasurePlacer::addAllPossibleObjects()
{
ObjectInfo oi;
int numZones = static_cast<int>(map.getZones().size());
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)
{
for(auto temp : handler->getTemplates())
{
if(temp.canBePlacedAt(zone.getTerrainType()))
{
oi.generateObject = [temp]() -> CGObjectInstance *
{
return VLC->objtypeh->getHandlerFor(temp.id, temp.subid)->create(temp);
};
auto rmgInfo = handler->getRMGInfo();
oi.value = rmgInfo.value;
oi.probability = rmgInfo.rarity;
oi.templ = temp;
oi.maxPerZone = rmgInfo.zoneLimit;
vstd::amin(oi.maxPerZone, rmgInfo.mapLimit / numZones); //simple, but should distribute objects evenly on large maps
possibleObjects.push_back(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());
for(int i = 0; i < prisonsLevels; i++)
{
oi.generateObject = [i, this]() -> CGObjectInstance *
{
std::vector<ui32> possibleHeroes;
for(int j = 0; j < map.map().allowedHeroes.size(); j++)
{
if(map.map().allowedHeroes[j])
possibleHeroes.push_back(j);
}
auto hid = *RandomGeneratorUtil::nextItem(possibleHeroes, generator.rand);
auto factory = VLC->objtypeh->getHandlerFor(Obj::PRISON, 0);
auto obj = (CGHeroInstance *) factory->create(ObjectTemplate());
obj->subID = hid; //will be initialized later
obj->exp = generator.getConfig().prisonExperience[i];
obj->setOwner(PlayerColor::NEUTRAL);
map.map().allowedHeroes[hid] = false; //ban this hero
generator.decreasePrisonsRemaining();
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;
oi.maxPerZone = generator.getPrisonsRemaning() / 5; //probably not perfect, but we can't generate more prisons than hereos.
possibleObjects.push_back(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->faction == 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 = 0; i < 4; i++)
vstd::erase_if_present(subObjects, elementalConfluxROE[i]);
}
for(auto secondaryID : subObjects)
{
auto dwellingHandler = dynamic_cast<const CDwellingInstanceConstructor *>(VLC->objtypeh->getHandlerFor(dwellingType, secondaryID).get());
auto creatures = dwellingHandler->getProducedCreatures();
if(creatures.empty())
continue;
auto cre = creatures.front();
if(cre->faction == zone.getTownType())
{
float nativeZonesCount = static_cast<float>(map.getZoneCount(cre->faction));
oi.value = static_cast<ui32>(cre->AIValue * cre->growth * (1 + (nativeZonesCount / map.getTotalZoneCount()) + (nativeZonesCount / 2)));
oi.probability = 40;
for(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;
possibleObjects.push_back(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 = (CGArtifact *) factory->create(ObjectTemplate());
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, generator.rand));
obj->storedArtifact = a;
return obj;
};
oi.setTemplate(Obj::SPELL_SCROLL, 0, zone.getTerrainType());
oi.value = generator.getConfig().scrollValues[i];
oi.probability = 30;
possibleObjects.push_back(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 = (CGPandoraBox *) factory->create(ObjectTemplate());
obj->resources[Res::GOLD] = i * 5000;
return obj;
};
oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
oi.value = i * generator.getConfig().pandoraMultiplierGold;
oi.probability = 5;
possibleObjects.push_back(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 = (CGPandoraBox *) factory->create(ObjectTemplate());
obj->gainedExp = i * 5000;
return obj;
};
oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
oi.value = i * generator.getConfig().pandoraMultiplierExperience;
oi.probability = 20;
possibleObjects.push_back(oi);
}
//pandora box with creatures
const std::vector<int> & tierValues = generator.getConfig().pandoraCreatureValues;
auto creatureToCount = [tierValues](CCreature * creature) -> int
{
if(!creature->AIValue || tierValues.empty()) //bug #2681
return 0; //this box won't be generated
int actualTier = creature->level > tierValues.size() ?
tierValues.size() - 1 :
creature->level - 1;
float creaturesAmount = ((float)tierValues[actualTier]) / creature->AIValue;
if(creaturesAmount <= 5)
{
creaturesAmount = boost::math::round(creaturesAmount); //allow single monsters
if(creaturesAmount < 1)
return 0;
}
else if(creaturesAmount <= 12)
{
(creaturesAmount /= 2) *= 2;
}
else if(creaturesAmount <= 50)
{
creaturesAmount = boost::math::round(creaturesAmount / 5) * 5;
}
else
{
creaturesAmount = boost::math::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 = (CGPandoraBox *) factory->create(ObjectTemplate());
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->AIValue) * creaturesAmount * (1 + (float)(map.getZoneCount(creature->faction)) / map.getTotalZoneCount())) / 3);
oi.probability = 3;
possibleObjects.push_back(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 = (CGPandoraBox *) factory->create(ObjectTemplate());
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, generator.rand);
for(int j = 0; j < std::min(12, (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;
possibleObjects.push_back(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 = (CGPandoraBox *) factory->create(ObjectTemplate());
std::vector <CSpell *> spells;
for(auto spell : VLC->spellh->objects)
{
if(map.isAllowedSpell(spell->id) && spell->school[(ESpellSchool)i])
spells.push_back(spell);
}
RandomGeneratorUtil::randomShuffle(spells, generator.rand);
for(int j = 0; j < std::min(15, (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;
possibleObjects.push_back(oi);
}
// Pandora box with 60 random spells
oi.generateObject = [this]() -> CGObjectInstance *
{
auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
auto obj = (CGPandoraBox *) factory->create(ObjectTemplate());
std::vector <CSpell *> spells;
for(auto spell : VLC->spellh->objects)
{
if(map.isAllowedSpell(spell->id))
spells.push_back(spell);
}
RandomGeneratorUtil::randomShuffle(spells, generator.rand);
for(int j = 0; j < std::min(60, (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;
possibleObjects.push_back(oi);
//seer huts with creatures or generic rewards
if(questArtZone) //we won't be placing seer huts if there is no zone left to place arties
{
static const int genericSeerHuts = 8;
int seerHutsPerType = 0;
const int questArtsRemaining = static_cast<int>(generator.getQuestArtsRemaning().size());
//general issue is that not many artifact types are available for quests
if(questArtsRemaining >= genericSeerHuts + (int)creatures.size())
{
seerHutsPerType = questArtsRemaining / (genericSeerHuts + (int)creatures.size());
}
else if(questArtsRemaining >= genericSeerHuts)
{
seerHutsPerType = 1;
}
oi.maxPerZone = seerHutsPerType;
RandomGeneratorUtil::randomShuffle(creatures, generator.rand);
auto generateArtInfo = [this](ArtifactID id) -> ObjectInfo
{
ObjectInfo artInfo;
artInfo.probability = std::numeric_limits<ui16>::max(); //99,9% to spawn that art in first treasure pile
artInfo.maxPerZone = 1;
artInfo.value = 2000; //treasure art
artInfo.setTemplate(Obj::ARTIFACT, id, this->zone.getTerrainType());
artInfo.generateObject = [id]() -> CGObjectInstance *
{
auto handler = VLC->objtypeh->getHandlerFor(Obj::ARTIFACT, id);
return handler->create(handler->getTemplates().front());
};
return artInfo;
};
for(int i = 0; i < std::min((int)creatures.size(), questArtsRemaining - genericSeerHuts); i++)
{
auto creature = creatures[i];
int creaturesAmount = creatureToCount(creature);
if(!creaturesAmount)
continue;
int randomAppearance = chooseRandomAppearance(generator.rand, Obj::SEER_HUT, zone.getTerrainType());
oi.generateObject = [creature, creaturesAmount, randomAppearance, this, generateArtInfo]() -> CGObjectInstance *
{
auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
auto obj = (CGSeerHut *) factory->create(ObjectTemplate());
obj->rewardType = CGSeerHut::CREATURE;
obj->rID = creature->idNumber;
obj->rVal = creaturesAmount;
obj->quest->missionType = CQuest::MISSION_ART;
ArtifactID artid = *RandomGeneratorUtil::nextItem(generator.getQuestArtsRemaning(), generator.rand);
obj->quest->m5arts.push_back(artid);
obj->quest->lastDay = -1;
obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false;
generator.banQuestArt(artid);
this->questArtZone->getModificator<TreasurePlacer>()->possibleObjects.push_back(generateArtInfo(artid));
return obj;
};
oi.setTemplate(Obj::SEER_HUT, randomAppearance, zone.getTerrainType());
oi.value = static_cast<ui32>(((2 * (creature->AIValue) * creaturesAmount * (1 + (float)(map.getZoneCount(creature->faction)) / map.getTotalZoneCount())) - 4000) / 3);
oi.probability = 3;
possibleObjects.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(generator.rand, Obj::SEER_HUT, zone.getTerrainType());
oi.setTemplate(Obj::SEER_HUT, randomAppearance, zone.getTerrainType());
oi.value = generator.getConfig().questValues[i];
oi.probability = 10;
oi.generateObject = [i, randomAppearance, this, generateArtInfo]() -> CGObjectInstance *
{
auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
auto obj = (CGSeerHut *) factory->create(ObjectTemplate());
obj->rewardType = CGSeerHut::EXPERIENCE;
obj->rID = 0; //unitialized?
obj->rVal = generator.getConfig().questRewardValues[i];
obj->quest->missionType = CQuest::MISSION_ART;
ArtifactID artid = *RandomGeneratorUtil::nextItem(generator.getQuestArtsRemaning(), generator.rand);
obj->quest->m5arts.push_back(artid);
obj->quest->lastDay = -1;
obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false;
generator.banQuestArt(artid);
this->questArtZone->getModificator<TreasurePlacer>()->possibleObjects.push_back(generateArtInfo(artid));
return obj;
};
possibleObjects.push_back(oi);
oi.generateObject = [i, randomAppearance, this, generateArtInfo]() -> CGObjectInstance *
{
auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
auto obj = (CGSeerHut *) factory->create(ObjectTemplate());
obj->rewardType = CGSeerHut::RESOURCES;
obj->rID = Res::GOLD;
obj->rVal = generator.getConfig().questRewardValues[i];
obj->quest->missionType = CQuest::MISSION_ART;
ArtifactID artid = *RandomGeneratorUtil::nextItem(generator.getQuestArtsRemaning(), generator.rand);
obj->quest->m5arts.push_back(artid);
obj->quest->lastDay = -1;
obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false;
generator.banQuestArt(artid);
this->questArtZone->getModificator<TreasurePlacer>()->possibleObjects.push_back(generateArtInfo(artid));
return obj;
};
possibleObjects.push_back(oi);
}
}
}
bool TreasurePlacer::isGuardNeededForTreasure(int value)
{
return zone.getType() != ETemplateZoneType::WATER && 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 = generator.rand.nextInt(minValue, maxValue);
int currentValue = 0;
bool hasLargeObject = false;
while(currentValue <= (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::constuctTreasurePile(const std::vector<ObjectInfo*> & treasureInfos)
{
rmg::Object rmgObject;
for(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;
}
int3 nextPos = *RandomGeneratorUtil::nextItem(accessibleArea.getTiles(), generator.rand);
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.push_back(std::make_pair(total, &oi));
}
}
if(thresholds.empty())
{
return nullptr;
}
else
{
int r = generator.rand.nextInt(1, total);
//binary search = fastest
auto it = std::lower_bound(thresholds.begin(), thresholds.end(), r,
[](const std::pair<ui32, ObjectInfo*> &rhs, const int lhs)->bool
{
return (int)rhs.first < lhs;
});
return it->second;
}
}
void TreasurePlacer::createTreasures(ObjectManager & manager)
{
int mapMonsterStrength = map.getMapGenOptions().getMonsterStrength();
int monsterStrength = zone.zoneMonsterStrength + mapMonsterStrength - 1; //array index from 0 to 4
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
while(true)
{
auto treasurePileInfos = prepareTreasurePile(t);
if(treasurePileInfos.empty())
break;
int value = std::accumulate(treasurePileInfos.begin(), treasurePileInfos.end(), 0, [](int v, const ObjectInfo * oi){return v + oi->value;});
auto rmgObject = constuctTreasurePile(treasurePileInfos);
if(rmgObject.instances().empty()) //handle incorrect placement
{
restoreZoneLimits(treasurePileInfos);
continue;
}
//guard treasure pile
bool guarded = isGuardNeededForTreasure(value);
if(guarded)
guarded = manager.addGuard(rmgObject, value);
int3 pos;
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.getTile(tile);
if(ti.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 1.f;
}, guarded, false, false);
}
else
{
path = manager.placeAndConnectObject(possibleArea, rmgObject, minDistance, guarded, false, false);
}
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);
}
else
{
restoreZoneLimits(treasurePileInfos);
rmgObject.clear();
break;
}
}
}
}
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);
}
ObjectInfo::ObjectInfo()
: templ(), value(0), probability(0), maxPerZone(1)
{
}
void ObjectInfo::setTemplate(si32 type, si32 subtype, Terrain terrainType)
{
auto templHandler = VLC->objtypeh->getHandlerFor(type, subtype);
if(!templHandler)
return;
auto templates = templHandler->getTemplates(terrainType);
if(templates.empty())
return;
templ = templates.front();
}

67
lib/rmg/TreasurePlacer.h Normal file
View File

@@ -0,0 +1,67 @@
/*
* 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
*
*/
#pragma once
#include "Zone.h"
#include "../mapObjects/ObjectTemplate.h"
class CGObjectInstance;
class ObjectManager;
class RmgMap;
class CMapGenerator;
struct ObjectInfo
{
ObjectTemplate templ;
ui32 value = 0;
ui16 probability = 0;
ui32 maxPerZone = -1;
//ui32 maxPerMap; //unused
std::function<CGObjectInstance *()> generateObject;
void setTemplate(si32 type, si32 subtype, Terrain terrain);
ObjectInfo();
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 setQuestArtZone(Zone * otherZone);
void addAllPossibleObjects(); //add objects, including zone-specific, to possibleObjects
protected:
bool isGuardNeededForTreasure(int value);
ObjectInfo * getRandomObject(ui32 desiredValue, ui32 currentValue, ui32 maxValue, bool allowLargeObjects);
std::vector<ObjectInfo*> prepareTreasurePile(const CTreasureInfo & treasureInfo);
rmg::Object constuctTreasurePile(const std::vector<ObjectInfo*> & treasureInfos);
protected:
std::vector<ObjectInfo> possibleObjects;
int minGuardedValue = 0;
rmg::Area treasureArea;
rmg::Area treasureBlockArea;
rmg::Area guards;
Zone * questArtZone = nullptr; //artifacts required for Seer Huts will be placed here - or not if null
};

261
lib/rmg/WaterAdopter.cpp Normal file
View File

@@ -0,0 +1,261 @@
/*
* 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"
void WaterAdopter::process()
{
createWater(map.getMapGenOptions().getWaterContent());
}
void WaterAdopter::init()
{
//make dependencies
DEPENDENCY_ALL(WaterAdopter);
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], generator.rand);
auto sliceEnd = RandomGeneratorUtil::nextItem(reverseDistanceMap[0], generator.rand);
//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(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], generator.rand);
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(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, 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, dirSum;
for(auto & i : waterCoastDiag)
diagSum += i - tile;
for(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(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);
}
}
}
}
map.getZones()[waterZoneId]->area().unite(waterArea);
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);
}

36
lib/rmg/WaterAdopter.h Normal file
View File

@@ -0,0 +1,36 @@
/*
* 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"
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;
};

359
lib/rmg/WaterProxy.cpp Normal file
View File

@@ -0,0 +1,359 @@
/*
* 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 "../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"
void WaterProxy::process()
{
for(auto & t : zone.area().getTilesVector())
{
map.setZoneID(t, zone.getId());
map.setOccupied(t, ETileType::POSSIBLE);
}
paintZoneTerrain(zone, generator.rand, map, zone.getTerrainType());
//check terrain type
for(auto & t : zone.area().getTilesVector())
{
assert(map.isOnMap(t));
assert(map.map().getTile(t).terType == zone.getTerrainType());
}
for(auto z : map.getZones())
{
if(z.second->getId() == zone.getId())
continue;
for(auto & t : z.second->area().getTilesVector())
{
if(map.map().getTile(t).terType == 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
{
return lakes;
}
void WaterProxy::collectLakes()
{
int lakeId = 0;
for(auto lake : connectedAreas(zone.getArea()))
{
lakes.push_back(Lake{});
lakes.back().area = lake;
lakes.back().distanceMap = lake.computeDistanceMap(lakes.back().reverseDistanceMap);
for(auto & t : lake.getBorderOutside())
if(map.isOnMap(t))
lakes.back().neighbourZones[map.getZoneID(t)].add(t);
for(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(auto & ct : lake.neighbourZones[dst.getId()].getTiles())
{
if(map.isPossible(ct))
map.setOccupied(ct, ETileType::BLOCKED);
}
dst.areaPossible().subtract(lake.neighbourZones[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);
auto* boat = (CGBoat*)VLC->objtypeh->getHandlerFor(Obj::BOAT, *RandomGeneratorUtil::nextItem(subObjects, generator.rand))->create(ObjectTemplate());
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](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 boat at water, create paths on water and land
auto path = manager->placeAndConnectObject(shipPositions, rmgObject, 2, false, true, false);
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);
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(generator.rand, Obj::SHIPYARD, land.getTerrainType());
auto shipyard = (CGShipyard*) VLC->objtypeh->getHandlerFor(Obj::SHIPYARD, subtype)->create(ObjectTemplate());
shipyard->tempOwner = PlayerColor::NEUTRAL;
rmg::Object rmgObject(*shipyard);
rmgObject.setTemplate(land.getTerrainType());
bool guarded = manager->addGuard(rmgObject, guard);
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](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)
{
rmg::Area shipyardOut(rmgObject.getArea().getBorderOutside());
if(!shipyardOut.contains(boardingPosition) || (shipyardOut * shipPositions).empty())
return -1.f;
return 1.0f;
}, guarded, true, false);
//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(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(auto i : lake.neighbourZones)
{
if(i.second.contains(t))
return lake.keepConnections.count(i.first) ? std::to_string(i.first)[0] : '=';
}
return '~';
}

55
lib/rmg/WaterProxy.h Normal file
View File

@@ -0,0 +1,55 @@
/*
* 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"
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
};

116
lib/rmg/WaterRoutes.cpp Normal file
View File

@@ -0,0 +1,116 @@
/*
* 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"
void WaterRoutes::process()
{
auto * wproxy = zone.getModificator<WaterProxy>();
if(!wproxy)
return;
for(auto & z : map.getZones())
{
if(z.first != zone.getId())
result.push_back(wproxy->waterRoute(*z.second));
}
//prohibit to place objects on sealed off lakes
for(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(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 ' ';
}

27
lib/rmg/WaterRoutes.h Normal file
View File

@@ -0,0 +1,27 @@
/*
* 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"
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;
};

410
lib/rmg/Zone.cpp Normal file
View File

@@ -0,0 +1,410 @@
/*
* Zone.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "Zone.h"
#include "RmgMap.h"
#include "Functions.h"
#include "TileInfo.h"
#include "../mapping/CMap.h"
#include "../CStopWatch.h"
#include "CMapGenerator.h"
#include "RmgPath.h"
std::function<bool(const int3 &)> AREA_NO_FILTER = [](const int3 & t)
{
return true;
};
Zone::Zone(RmgMap & map, CMapGenerator & generator)
: ZoneOptions(),
townType(ETownType::NEUTRAL),
terrainType(Terrain("grass")),
map(map),
generator(generator)
{
}
bool Zone::isUnderground() const
{
return getPos().z;
}
void Zone::setOptions(const ZoneOptions& options)
{
ZoneOptions::operator=(options);
}
float3 Zone::getCenter() const
{
return center;
}
void Zone::setCenter(const float3 &f)
{
//limit boundaries to (0,1) square
//alternate solution - wrap zone around unitary square. If it doesn't fit on one side, will come out on the opposite side
center = f;
center.x = static_cast<float>(std::fmod(center.x, 1));
center.y = static_cast<float>(std::fmod(center.y, 1));
if(center.x < 0) //fmod seems to work only for positive numbers? we want to stay positive
center.x = 1 - std::abs(center.x);
if(center.y < 0)
center.y = 1 - std::abs(center.y);
}
int3 Zone::getPos() const
{
return pos;
}
void Zone::setPos(const int3 &Pos)
{
pos = Pos;
}
const rmg::Area & Zone::getArea() const
{
return dArea;
}
rmg::Area & Zone::area()
{
return dArea;
}
rmg::Area & Zone::areaPossible()
{
return dAreaPossible;
}
rmg::Area & Zone::areaUsed()
{
return dAreaUsed;
}
void Zone::clearTiles()
{
dArea.clear();
dAreaPossible.clear();
dAreaFree.clear();
}
void Zone::initFreeTiles()
{
rmg::Tileset possibleTiles;
vstd::copy_if(dArea.getTiles(), vstd::set_inserter(possibleTiles), [this](const int3 &tile) -> bool
{
return map.isPossible(tile);
});
dAreaPossible.assign(possibleTiles);
if(dAreaFree.empty())
{
dAreaPossible.erase(pos);
dAreaFree.add(pos); //zone must have at least one free tile where other paths go - for instance in the center
}
}
rmg::Area & Zone::freePaths()
{
return dAreaFree;
}
si32 Zone::getTownType() const
{
return townType;
}
void Zone::setTownType(si32 town)
{
townType = town;
}
const Terrain & Zone::getTerrainType() const
{
return terrainType;
}
void Zone::setTerrainType(const Terrain & terrain)
{
terrainType = terrain;
}
rmg::Path Zone::searchPath(const rmg::Area & src, bool onlyStraight, std::function<bool(const int3 &)> areafilter) const
///connect current tile to any other free tile within zone
{
auto movementCost = [this](const int3 & s, const int3 & d)
{
if(map.isFree(d))
return 1;
else if (map.isPossible(d))
return 2;
return 3;
};
auto area = (dAreaPossible + dAreaFree).getSubarea(areafilter);
rmg::Path freePath(area);
freePath.connect(dAreaFree);
//connect to all pieces
auto goals = connectedAreas(src);
for(auto & goal : goals)
{
auto path = freePath.search(goal, onlyStraight, movementCost);
if(path.getPathArea().empty())
return rmg::Path::invalid();
freePath.connect(path.getPathArea());
}
return freePath;
}
rmg::Path Zone::searchPath(const int3 & src, bool onlyStraight, std::function<bool(const int3 &)> areafilter) const
///connect current tile to any other free tile within zone
{
return searchPath(rmg::Area({src}), onlyStraight, areafilter);
}
void Zone::connectPath(const rmg::Path & path)
///connect current tile to any other free tile within zone
{
dAreaPossible.subtract(path.getPathArea());
dAreaFree.unite(path.getPathArea());
for(auto & t : path.getPathArea().getTilesVector())
map.setOccupied(t, ETileType::FREE);
}
void Zone::fractalize()
{
rmg::Area clearedTiles(dAreaFree);
rmg::Area possibleTiles(dAreaPossible);
rmg::Area tilesToIgnore; //will be erased in this iteration
//the more treasure density, the greater distance between paths. Scaling is experimental.
int totalDensity = 0;
for(auto ti : treasureInfo)
totalDensity += ti.density;
const float minDistance = 10 * 10; //squared
if(type != ETemplateZoneType::JUNCTION)
{
//junction is not fractalized, has only one straight path
//everything else remains blocked
while(!possibleTiles.empty())
{
//link tiles in random order
std::vector<int3> tilesToMakePath = possibleTiles.getTilesVector();
RandomGeneratorUtil::randomShuffle(tilesToMakePath, generator.rand);
int3 nodeFound(-1, -1, -1);
for(auto tileToMakePath : tilesToMakePath)
{
//find closest free tile
int3 closestTile = clearedTiles.nearest(tileToMakePath);
if(closestTile.dist2dSQ(tileToMakePath) <= minDistance)
tilesToIgnore.add(tileToMakePath);
else
{
//if tiles are not close enough, make path to it
nodeFound = tileToMakePath;
clearedTiles.add(nodeFound); //from now on nearby tiles will be considered handled
break; //next iteration - use already cleared tiles
}
}
possibleTiles.subtract(tilesToIgnore);
if(!nodeFound.valid()) //nothing else can be done (?)
break;
tilesToIgnore.clear();
}
}
//cut straight paths towards the center. A* is too slow for that.
auto areas = connectedAreas(clearedTiles);
for(auto & area : areas)
{
if(dAreaFree.overlap(area))
continue; //already found
auto availableArea = dAreaPossible + dAreaFree;
rmg::Path path(availableArea);
path.connect(dAreaFree);
auto res = path.search(area, false);
if(res.getPathArea().empty())
{
dAreaPossible.subtract(area);
dAreaFree.subtract(area);
for(auto & t : area.getTiles())
map.setOccupied(t, ETileType::BLOCKED);
}
else
{
dAreaPossible.subtract(res.getPathArea());
dAreaFree.unite(res.getPathArea());
for(auto & t : res.getPathArea().getTiles())
map.setOccupied(t, ETileType::FREE);
}
}
//now block most distant tiles away from passages
float blockDistance = minDistance * 0.25f;
auto areaToBlock = dArea.getSubarea([this, blockDistance](const int3 & t)
{
float distance = static_cast<float>(dAreaFree.distanceSqr(t));
return distance > blockDistance;
});
dAreaPossible.subtract(areaToBlock);
dAreaFree.subtract(areaToBlock);
for(auto & t : areaToBlock.getTiles())
map.setOccupied(t, ETileType::BLOCKED);
}
void Zone::initModificators()
{
for(auto & modificator : modificators)
{
modificator->init();
}
logGlobal->info("Zone %d modificators initialized", getId());
}
void Zone::processModificators()
{
for(auto & modificator : modificators)
{
try
{
modificator->run();
}
catch (const rmgException & e)
{
logGlobal->info("Zone %d, modificator %s - FAILED: %s", getId(), e.what());
throw e;
}
}
logGlobal->info("Zone %d filled successfully", getId());
}
Modificator::Modificator(Zone & zone, RmgMap & map, CMapGenerator & generator) : zone(zone), map(map), generator(generator)
{
}
void Modificator::setName(const std::string & n)
{
name = n;
}
const std::string & Modificator::getName() const
{
return name;
}
bool Modificator::isFinished() const
{
return finished;
}
void Modificator::run()
{
started = true;
if(!finished)
{
for(auto * modificator : preceeders)
{
if(!modificator->started)
modificator->run();
}
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)
{
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()));
auto & mapInstance = map.map();
int levels = mapInstance.twoLevel ? 2 : 1;
int width = mapInstance.width;
int height = mapInstance.height;
for (int k = 0; k < levels; k++)
{
for(int j=0; j<height; j++)
{
for (int i=0; i<width; i++)
{
out << dump(int3(i, j, k));
}
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 '?';
}
Modificator::~Modificator()
{
}

143
lib/rmg/Zone.h Normal file
View File

@@ -0,0 +1,143 @@
/*
* Zone.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 "float3.h"
#include "../int3.h"
#include "CRmgTemplate.h"
#include "RmgArea.h"
#include "RmgPath.h"
#include "RmgObject.h"
//uncomment to generate dumps afger every step of map generation
//#define RMG_DUMP
#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>()); \
}
class RmgMap;
class CMapGenerator;
class Zone;
class Modificator
{
public:
Modificator() = delete;
Modificator(Zone & zone, RmgMap & map, CMapGenerator & generator);
virtual void process() = 0;
virtual void init() {/*override to add dependencies*/}
virtual char dump(const int3 &);
virtual ~Modificator();
void setName(const std::string & n);
const std::string & getName() const;
void run();
void dependency(Modificator * modificator);
void postfunction(Modificator * modificator);
protected:
RmgMap & map;
CMapGenerator & generator;
Zone & zone;
bool isFinished() const;
private:
std::string name;
bool started = false;
bool finished = false;
std::list<Modificator*> preceeders; //must be ordered container
void dump();
};
extern std::function<bool(const int3 &)> AREA_NO_FILTER;
class Zone : public rmg::ZoneOptions
{
public:
Zone(RmgMap & map, CMapGenerator & generator);
Zone(const Zone &) = delete;
void setOptions(const rmg::ZoneOptions & options);
bool isUnderground() const;
float3 getCenter() const;
void setCenter(const float3 &f);
int3 getPos() const;
void setPos(const int3 &pos);
const rmg::Area & getArea() const;
rmg::Area & area();
rmg::Area & areaPossible();
rmg::Area & freePaths();
rmg::Area & areaUsed();
void initFreeTiles();
void clearTiles();
void fractalize();
si32 getTownType() const;
void setTownType(si32 town);
const Terrain & getTerrainType() const;
void setTerrainType(const Terrain & terrain);
void connectPath(const rmg::Path & path);
rmg::Path searchPath(const rmg::Area & src, bool onlyStraight, std::function<bool(const int3 &)> areafilter = AREA_NO_FILTER) const;
rmg::Path searchPath(const int3 & src, bool onlyStraight, std::function<bool(const int3 &)> areafilter = AREA_NO_FILTER) const;
template<class T>
T* getModificator()
{
for(auto & m : modificators)
if(auto * mm = dynamic_cast<T*>(m.get()))
return mm;
return nullptr;
}
template<class T>
void addModificator()
{
modificators.emplace_back(new T(*this, map, generator));
}
void initModificators();
void processModificators();
protected:
CMapGenerator & generator;
RmgMap & map;
std::list<std::unique_ptr<Modificator>> modificators;
//placement info
int3 pos;
float3 center;
rmg::Area dArea; //irregular area assined to zone
rmg::Area dAreaPossible;
rmg::Area dAreaFree; //core paths of free tiles that all other objects will be linked to
rmg::Area dAreaUsed;
//template info
si32 townType;
Terrain terrainType;
};