1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-04-15 11:46:56 +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 GitHub
parent 1644a19053
commit 6da7c9ccb5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
80 changed files with 6768 additions and 8682 deletions

View File

@ -24,7 +24,7 @@ jobs:
test: 0
preset: linux-gcc-release
- platform: mac
os: macos-latest
os: macos-10.15
test: 0
pack: 1
extension: dmg

2
.gitignore vendored
View File

@ -26,8 +26,6 @@ CMakeFiles
Makefile
cmake_install.cmake
install_manifest.txt
*_cotire.cmake
cotire
moc_*.cpp
qrc_*.cpp
ui_*.h

View File

@ -36,8 +36,6 @@ target_include_directories(BattleAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(BattleAI PRIVATE vcmi)
vcmi_set_output_dir(BattleAI "AI")
set_target_properties(BattleAI PROPERTIES ${PCH_PROPERTIES})
cotire(BattleAI)
enable_pch(BattleAI)
install(TARGETS BattleAI RUNTIME DESTINATION ${AI_LIB_DIR} LIBRARY DESTINATION ${AI_LIB_DIR})

View File

@ -18,7 +18,6 @@ target_include_directories(EmptyAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(EmptyAI PRIVATE vcmi)
vcmi_set_output_dir(EmptyAI "AI")
set_target_properties(EmptyAI PROPERTIES ${PCH_PROPERTIES})
enable_pch(EmptyAI)
install(TARGETS EmptyAI RUNTIME DESTINATION ${AI_LIB_DIR} LIBRARY DESTINATION ${AI_LIB_DIR})

View File

@ -135,8 +135,6 @@ target_link_libraries(Nullkiller PRIVATE vcmi fuzzylite::fuzzylite)
target_link_libraries(Nullkiller PRIVATE TBB::tbb)
vcmi_set_output_dir(Nullkiller "AI")
set_target_properties(Nullkiller PROPERTIES ${PCH_PROPERTIES})
cotire(Nullkiller)
enable_pch(Nullkiller)
install(TARGETS Nullkiller RUNTIME DESTINATION ${AI_LIB_DIR} LIBRARY DESTINATION ${AI_LIB_DIR})

View File

@ -18,8 +18,6 @@ target_link_libraries(StupidAI PRIVATE vcmi)
target_include_directories(StupidAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
vcmi_set_output_dir(StupidAI "AI")
set_target_properties(StupidAI PROPERTIES ${PCH_PROPERTIES})
cotire(StupidAI)
enable_pch(StupidAI)
install(TARGETS StupidAI RUNTIME DESTINATION ${AI_LIB_DIR} LIBRARY DESTINATION ${AI_LIB_DIR})

View File

@ -114,8 +114,6 @@ target_include_directories(VCAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(VCAI PRIVATE vcmi fuzzylite::fuzzylite)
vcmi_set_output_dir(VCAI "AI")
set_target_properties(VCAI PROPERTIES ${PCH_PROPERTIES})
cotire(VCAI)
enable_pch(VCAI)
install(TARGETS VCAI RUNTIME DESTINATION ${AI_LIB_DIR} LIBRARY DESTINATION ${AI_LIB_DIR})

View File

@ -5,4 +5,4 @@ brew update
brew install smpeg2 libpng freetype qt5 ffmpeg ninja boost tbb luajit
brew install sdl2 sdl2_ttf sdl2_image sdl2_mixer
echo CMAKE_PREFIX_PATH="/usr/local/opt/ffmpeg@4:/usr/local/opt/qt5:$CMAKE_PREFIX_PATH" >> $GITHUB_ENV
echo CMAKE_PREFIX_PATH="$(brew --prefix)/opt/qt5:$CMAKE_PREFIX_PATH" >> $GITHUB_ENV

View File

@ -48,7 +48,9 @@ option(ENABLE_ERM "Enable compilation of ERM scripting module" ON)
option(ENABLE_LUA "Enable compilation of LUA scripting module" ON)
option(ENABLE_LAUNCHER "Enable compilation of launcher" ON)
option(ENABLE_TEST "Enable compilation of unit tests" ON)
option(ENABLE_PCH "Enable compilation using precompiled headers" ON)
if(NOT ${CMAKE_VERSION} VERSION_LESS "3.16.0")
option(ENABLE_PCH "Enable compilation using precompiled headers" ON)
endif(NOT ${CMAKE_VERSION} VERSION_LESS "3.16.0")
option(ENABLE_GITVERSION "Enable Version.cpp with Git commit hash" ON)
option(ENABLE_DEBUG_CONSOLE "Enable debug console for Windows builds" ON)
option(ENABLE_MULTI_PROCESS_BUILDS "Enable /MP flag for MSVS solution" ON)
@ -95,18 +97,14 @@ else()
endif(ENABLE_GITVERSION)
# Precompiled header configuration
if(ENABLE_PCH)
include(cotire)
set(PCH_PROPERTIES
COTIRE_ENABLE_PRECOMPILED_HEADER ${ENABLE_PCH}
COTIRE_ADD_UNITY_BUILD FALSE
COTIRE_CXX_PREFIX_HEADER_INIT "StdInc.h"
)
else()
set(PCH_PROPERTIES COTIRE_ADD_UNITY_BUILD FALSE)
macro(cotire ignore)
endmacro(cotire)
endif(ENABLE_PCH)
if(ENABLE_PCH AND NOT ${CMAKE_VERSION} VERSION_LESS "3.16.0")
macro(enable_pch name)
target_precompile_headers(${name} PRIVATE $<$<COMPILE_LANGUAGE:CXX>:<StdInc.h$<ANGLE-R>>)
endmacro(enable_pch)
else(ENABLE_PCH AND NOT ${CMAKE_VERSION} VERSION_LESS "3.16.0")
macro(enable_pch ignore)
endmacro(enable_pch)
endif(ENABLE_PCH AND NOT ${CMAKE_VERSION} VERSION_LESS "3.16.0")
############################################
# Documentation section #

View File

@ -186,9 +186,7 @@ target_include_directories(vcmiclient
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
vcmi_set_output_dir(vcmiclient "")
set_target_properties(vcmiclient PROPERTIES ${PCH_PROPERTIES})
cotire(vcmiclient)
enable_pch(vcmiclient)
install(TARGETS vcmiclient DESTINATION ${BIN_DIR})

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

@ -443,14 +443,15 @@ void CPlayerInterface::heroKilled(const CGHeroInstance* hero)
}
wanderingHeroes -= hero;
if (vstd::contains(paths, hero))
paths.erase(hero);
adventureInt->heroList.update(hero);
if (makingTurn && newSelection)
adventureInt->select(newSelection, true);
else if (adventureInt->selection == hero)
adventureInt->selection = nullptr;
if (vstd::contains(paths, hero))
paths.erase(hero);
}
void CPlayerInterface::heroVisit(const CGHeroInstance * visitor, const CGObjectInstance * visitedObj, bool start)
@ -2765,7 +2766,8 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
destinationTeleport = destTeleportObj->id;
destinationTeleportPos = nextCoord;
doMovement(h->pos, false);
if (path.nodes[i-1].action == CGPathNode::TELEPORT_BLOCKING_VISIT)
if (path.nodes[i-1].action == CGPathNode::TELEPORT_BLOCKING_VISIT
|| path.nodes[i-1].action == CGPathNode::TELEPORT_BATTLE)
{
destinationTeleport = ObjectInstanceID();
destinationTeleportPos = int3(-1);

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

@ -1,22 +0,0 @@
Copyright (c) 2012-2014 Sascha Kratky
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

File diff suppressed because it is too large Load Diff

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

@ -101,10 +101,7 @@ target_include_directories(vcmilauncher
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
)
vcmi_set_output_dir(vcmilauncher "")
# temporary(?) disabled - generation of PCH takes too much time since cotire is trying to collect all Qt headers
#set_target_properties(vcmilauncher PROPERTIES ${PCH_PROPERTIES})
#cotire(vcmilauncher)
enable_pch(vcmilauncher)
# Copy to build directory for easier debugging
add_custom_command(TARGET vcmilauncher POST_BUILD

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
@ -424,8 +460,7 @@ endif()
vcmi_set_output_dir(vcmi "")
set_target_properties(vcmi PROPERTIES ${PCH_PROPERTIES})
cotire(vcmi)
enable_pch(vcmi)
# We want to deploy assets into build directory for easier debugging without install
add_custom_command(TARGET vcmi POST_BUILD

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;
};

View File

@ -45,6 +45,21 @@ ui16 CTypeList::getTypeID(const std::type_info *type, bool throws) const
return descriptor->typeID;
}
CTypeList::TypeInfoPtr CTypeList::getTypeDescriptor(ui16 typeID) const
{
auto found = std::find_if(typeInfos.begin(), typeInfos.end(), [typeID](const std::pair<const std::type_info *, TypeInfoPtr> & p) -> bool
{
return p.second->typeID == typeID;
});
if(found != typeInfos.end())
{
return found->second;
}
return TypeInfoPtr();
}
std::vector<CTypeList::TypeInfoPtr> CTypeList::castSequence(TypeInfoPtr from, TypeInfoPtr to) const
{
if(!strcmp(from->name, to->name))

View File

@ -148,6 +148,8 @@ public:
return getTypeID(getTypeInfo(t), throws);
}
TypeInfoPtr getTypeDescriptor(ui16 typeID) const;
template<typename TInput>
void * castToMostDerived(const TInput * inputPtr) const
{

View File

@ -18,8 +18,6 @@ add_library(vcmiERM SHARED ${lib_SRCS} ${lib_HDRS})
target_link_libraries(vcmiERM Boost::boost vcmi)
vcmi_set_output_dir(vcmiERM "scripting")
set_target_properties(vcmiERM PROPERTIES ${PCH_PROPERTIES})
cotire(vcmiERM)
enable_pch(vcmiERM)
install(TARGETS vcmiERM DESTINATION ${SCRIPTING_LIB_DIR})

View File

@ -43,9 +43,7 @@ add_library(vcmiLua SHARED ${lib_SRCS})
target_link_libraries(vcmiLua Boost::boost luajit::luajit vcmi)
vcmi_set_output_dir(vcmiLua "scripting")
set_target_properties(vcmiLua PROPERTIES ${PCH_PROPERTIES})
cotire(vcmiLua)
enable_pch(vcmiLua)
install(TARGETS vcmiLua DESTINATION ${SCRIPTING_LIB_DIR})

View File

@ -43,8 +43,6 @@ if(WIN32)
endif()
vcmi_set_output_dir(vcmiserver "")
set_target_properties(vcmiserver PROPERTIES ${PCH_PROPERTIES})
cotire(vcmiserver)
enable_pch(vcmiserver)
install(TARGETS vcmiserver DESTINATION ${BIN_DIR})

View File

@ -177,8 +177,7 @@ endif()
vcmi_set_output_dir(vcmitest "")
set_target_properties(vcmitest PROPERTIES ${PCH_PROPERTIES})
cotire(vcmitest)
enable_pch(vcmitest)
file (GLOB_RECURSE testdata "testdata/*.*")
foreach(resource ${testdata})