From 5151da59a769916a0ebd8ff1e5ba75663ac430f0 Mon Sep 17 00:00:00 2001 From: Nordsoft91 Date: Mon, 20 Jun 2022 17:39:50 +0300 Subject: [PATCH 01/15] New terrain support - part 1 (#755) Initial support of new terrains --- AI/Nullkiller/Pathfinding/AINodeStorage.cpp | 18 +- AI/Nullkiller/Pathfinding/Actors.cpp | 2 +- AI/VCAI/Pathfinding/AINodeStorage.cpp | 16 +- AI/VCAI/VCAI.h | 5 - client/CMusicHandler.cpp | 60 +++--- client/CMusicHandler.h | 17 +- client/CPlayerInterface.cpp | 14 +- client/Client.cpp | 12 +- client/battle/CBattleInterface.cpp | 2 +- client/mapHandler.cpp | 106 +++++----- client/mapHandler.h | 4 +- client/widgets/AdventureMapClasses.cpp | 36 ++-- client/widgets/AdventureMapClasses.h | 5 +- client/windows/CAdvmapInterface.cpp | 2 +- config/gameConfig.json | 4 + config/obstacles.json | 158 +++++++-------- config/randomMap.json | 4 +- config/terrains.json | 43 ++++- lib/CArtHandler.h | 15 +- lib/CCreatureHandler.cpp | 16 +- lib/CCreatureHandler.h | 20 +- lib/CGameInfoCallback.cpp | 2 +- lib/CGameState.cpp | 36 ++-- lib/CGameState.h | 9 +- lib/CGeneralTextHandler.cpp | 15 +- lib/CGeneralTextHandler.h | 2 +- lib/CHeroHandler.cpp | 19 +- lib/CHeroHandler.h | 38 +--- lib/CMakeLists.txt | 2 + lib/CModHandler.h | 29 +-- lib/CPathfinder.cpp | 28 ++- lib/CPathfinder.h | 3 +- lib/CPlayerState.h | 8 - lib/CSkillHandler.h | 14 +- lib/CStack.cpp | 8 +- lib/CStack.h | 5 +- lib/CTownHandler.cpp | 142 +------------- lib/CTownHandler.h | 61 ++---- lib/GameConstants.cpp | 32 --- lib/GameConstants.h | 48 +---- lib/HeroBonus.cpp | 15 +- lib/HeroBonus.h | 39 +--- lib/IGameCallback.cpp | 6 +- lib/JsonDetail.h | 2 +- lib/JsonNode.cpp | 12 +- lib/JsonNode.h | 11 +- lib/NetPacksLib.cpp | 6 +- lib/PathfinderUtil.h | 4 +- lib/StartInfo.h | 14 +- lib/StringConstants.h | 4 - lib/Terrain.cpp | 204 ++++++++++++++++++++ lib/Terrain.h | 93 +++++++++ lib/VCMI_Lib.cpp | 12 -- lib/VCMI_Lib.h | 22 +-- lib/battle/BattleInfo.cpp | 7 +- lib/battle/BattleInfo.h | 7 +- lib/battle/BattleProxy.cpp | 3 +- lib/battle/BattleProxy.h | 2 +- lib/battle/CBattleInfoEssentials.cpp | 4 +- lib/battle/CBattleInfoEssentials.h | 2 +- lib/battle/IBattleInfoCallback.h | 4 +- lib/battle/IBattleState.h | 2 +- lib/mapObjects/CGHeroInstance.cpp | 47 ++--- lib/mapObjects/CGHeroInstance.h | 25 +-- lib/mapObjects/CGTownInstance.cpp | 106 ---------- lib/mapObjects/CGTownInstance.h | 18 +- lib/mapObjects/CObjectClassesHandler.cpp | 10 +- lib/mapObjects/CObjectClassesHandler.h | 38 +--- lib/mapObjects/CObjectHandler.cpp | 2 +- lib/mapObjects/CObjectHandler.h | 10 +- lib/mapObjects/CQuest.h | 9 +- lib/mapObjects/CRewardableObject.h | 5 - lib/mapObjects/ObjectTemplate.cpp | 44 +++-- lib/mapObjects/ObjectTemplate.h | 10 +- lib/mapping/CCampaignHandler.cpp | 11 -- lib/mapping/CCampaignHandler.h | 18 +- lib/mapping/CDrawRoadsOperation.cpp | 6 +- lib/mapping/CDrawRoadsOperation.h | 4 +- lib/mapping/CMap.cpp | 14 +- lib/mapping/CMap.h | 24 +-- lib/mapping/CMapDefines.h | 6 +- lib/mapping/CMapEditManager.cpp | 46 ++--- lib/mapping/CMapEditManager.h | 13 +- lib/mapping/MapFormatH3M.cpp | 8 +- lib/mapping/MapFormatJson.cpp | 47 ++--- lib/rmg/CMapGenerator.cpp | 51 ++--- lib/rmg/CMapGenerator.h | 10 +- lib/rmg/CRmgTemplate.cpp | 56 ++++-- lib/rmg/CRmgTemplate.h | 9 +- lib/rmg/CRmgTemplateZone.cpp | 77 ++++++-- lib/rmg/CRmgTemplateZone.h | 18 +- lib/rmg/CZonePlacer.cpp | 38 ++-- lib/serializer/CSerializer.h | 4 +- lib/spells/CSpellHandler.cpp | 51 ----- lib/spells/CSpellHandler.h | 80 +------- scripting/lua/api/BattleCb.cpp | 4 +- server/CGameHandler.cpp | 14 +- server/CGameHandler.h | 26 +-- test/game/CGameStateTest.cpp | 2 +- test/googletest | 2 +- test/map/CMapEditManagerTest.cpp | 36 ++-- test/mock/mock_IBattleInfoCallback.h | 2 +- test/mock/mock_battle_IBattleState.h | 2 +- 103 files changed, 1066 insertions(+), 1472 deletions(-) create mode 100644 lib/Terrain.cpp create mode 100644 lib/Terrain.h diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index 1d2782d44..a52457ea3 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -88,24 +88,22 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta for(pos.z = 0; pos.z < sizes.z; ++pos.z) { const TerrainTile * tile = &gs->map->getTile(pos); - switch(tile->terType) + if(!tile->terType.isPassable()) + continue; + + if(tile->terType.isWater()) { - case ETerrainType::ROCK: - break; - - case ETerrainType::WATER: resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); if(useFlying) resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); if(useWaterWalking) resetTile(pos, ELayer::WATER, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); - break; - - default: + } + else + { resetTile(pos, ELayer::LAND, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); if(useFlying) resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); - break; } } } @@ -1428,4 +1426,4 @@ std::string AIPath::toString() const str << node.targetHero->name << "[" << std::hex << node.chainMask << std::dec << "]" << "->" << node.coord.toString() << "; "; return str.str(); -} \ No newline at end of file +} diff --git a/AI/Nullkiller/Pathfinding/Actors.cpp b/AI/Nullkiller/Pathfinding/Actors.cpp index e10a7e6d5..68d10fecc 100644 --- a/AI/Nullkiller/Pathfinding/Actors.cpp +++ b/AI/Nullkiller/Pathfinding/Actors.cpp @@ -70,7 +70,7 @@ int ChainActor::maxMovePoints(CGPathNode::ELayer layer) { #if AI_TRACE_LEVEL > 0 if(!hero) - throw std::exception("Asking movement points for static actor"); + throw std::logic_error("Asking movement points for static actor"); #endif return hero->maxMovePointsCached(layer, tiCache.get()); diff --git a/AI/VCAI/Pathfinding/AINodeStorage.cpp b/AI/VCAI/Pathfinding/AINodeStorage.cpp index 30bfc93a9..7c2fe609b 100644 --- a/AI/VCAI/Pathfinding/AINodeStorage.cpp +++ b/AI/VCAI/Pathfinding/AINodeStorage.cpp @@ -46,24 +46,22 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta for(pos.z=0; pos.z < sizes.z; ++pos.z) { const TerrainTile * tile = &gs->map->getTile(pos); - switch(tile->terType) + if(!tile->terType.isPassable()) + continue; + + if(tile->terType.isWater()) { - case ETerrainType::ROCK: - break; - - case ETerrainType::WATER: resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); if(useFlying) resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); if(useWaterWalking) resetTile(pos, ELayer::WATER, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); - break; - - default: + } + else + { resetTile(pos, ELayer::LAND, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); if(useFlying) resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); - break; } } } diff --git a/AI/VCAI/VCAI.h b/AI/VCAI/VCAI.h index 9e1d69738..f19fed2ba 100644 --- a/AI/VCAI/VCAI.h +++ b/AI/VCAI/VCAI.h @@ -347,11 +347,6 @@ public: h & visitableObjs; h & alreadyVisited; h & reservedObjs; - if (version < 788 && !h.saving) - { - TResources saving; - h & saving; //mind the ambiguity - } h & status; h & battlename; h & heroesUnableToExplore; diff --git a/client/CMusicHandler.cpp b/client/CMusicHandler.cpp index 2f550780c..894b76124 100644 --- a/client/CMusicHandler.cpp +++ b/client/CMusicHandler.cpp @@ -19,6 +19,7 @@ #include "../lib/StringConstants.h" #include "../lib/CRandomGenerator.h" #include "../lib/VCMIDirs.h" +#include "../lib/Terrain.h" #define VCMI_SOUND_NAME(x) #define VCMI_SOUND_FILE(y) #y, @@ -92,20 +93,34 @@ CSoundHandler::CSoundHandler(): soundBase::pickup04, soundBase::pickup05, soundBase::pickup06, soundBase::pickup07 }; - horseSounds = // must be the same order as terrains (see ETerrainType); - { - soundBase::horseDirt, soundBase::horseSand, soundBase::horseGrass, - soundBase::horseSnow, soundBase::horseSwamp, soundBase::horseRough, - soundBase::horseSubterranean, soundBase::horseLava, - soundBase::horseWater, soundBase::horseRock - }; - battleIntroSounds = { soundBase::battle00, soundBase::battle01, soundBase::battle02, soundBase::battle03, soundBase::battle04, soundBase::battle05, soundBase::battle06, soundBase::battle07 }; + + //predefine terrain set + //TODO: need refactoring - support custom sounds for new terrains and load from json + int h3mTerrId = 0; + for(auto snd : + { + soundBase::horseDirt, soundBase::horseSand, soundBase::horseGrass, + soundBase::horseSnow, soundBase::horseSwamp, soundBase::horseRough, + soundBase::horseSubterranean, soundBase::horseLava, + soundBase::horseWater, soundBase::horseRock + }) + { + horseSounds[Terrain::createTerrainTypeH3M(h3mTerrId++)] = snd; + } + for(auto & terrain : Terrain::Manager::terrains()) + { + //since all sounds are hardcoded, let's keep it + if(vstd::contains(horseSounds, terrain)) + continue; + + horseSounds[terrain] = horseSounds.at(Terrain::createTerrainTypeH3M(Terrain::Manager::getInfo(terrain).horseSoundId)); + } }; void CSoundHandler::init() @@ -341,26 +356,22 @@ CMusicHandler::CMusicHandler(): return true; }); - int battleMusicID = 0; - int AIThemeID = 0; - for(const ResourceID & file : mp3files) { if(boost::algorithm::istarts_with(file.getName(), "MUSIC/Combat")) - addEntryToSet("battle", battleMusicID++, file.getName()); + addEntryToSet("battle", file.getName(), file.getName()); else if(boost::algorithm::istarts_with(file.getName(), "MUSIC/AITheme")) - addEntryToSet("enemy-turn", AIThemeID++, file.getName()); + addEntryToSet("enemy-turn", file.getName(), file.getName()); } - JsonNode terrains(ResourceID("config/terrains.json")); - for (auto entry : terrains.Struct()) + for(auto & terrain : Terrain::Manager::terrains()) { - int terrIndex = vstd::find_pos(GameConstants::TERRAIN_NAMES, entry.first); - addEntryToSet("terrain", terrIndex, "Music/" + entry.second["music"].String()); + auto & entry = Terrain::Manager::getInfo(terrain); + addEntryToSet("terrain", terrain, "Music/" + entry.musicFilename); } } -void CMusicHandler::addEntryToSet(std::string set, int musicID, std::string musicURI) +void CMusicHandler::addEntryToSet(const std::string & set, const std::string & musicID, const std::string & musicURI) { musicsSet[set][musicID] = musicURI; } @@ -388,7 +399,7 @@ void CMusicHandler::release() CAudioBase::release(); } -void CMusicHandler::playMusic(std::string musicURI, bool loop) +void CMusicHandler::playMusic(const std::string & musicURI, bool loop) { if (current && current->isTrack(musicURI)) return; @@ -396,7 +407,7 @@ void CMusicHandler::playMusic(std::string musicURI, bool loop) queueNext(this, "", musicURI, loop); } -void CMusicHandler::playMusicFromSet(std::string whichSet, bool loop) +void CMusicHandler::playMusicFromSet(const std::string & whichSet, bool loop) { auto selectedSet = musicsSet.find(whichSet); if (selectedSet == musicsSet.end()) @@ -412,8 +423,7 @@ void CMusicHandler::playMusicFromSet(std::string whichSet, bool loop) queueNext(this, whichSet, "", loop); } - -void CMusicHandler::playMusicFromSet(std::string whichSet, int entryID, bool loop) +void CMusicHandler::playMusicFromSet(const std::string & whichSet, const std::string & entryID, bool loop) { auto selectedSet = musicsSet.find(whichSet); if (selectedSet == musicsSet.end()) @@ -425,7 +435,7 @@ void CMusicHandler::playMusicFromSet(std::string whichSet, int entryID, bool loo auto selectedEntry = selectedSet->second.find(entryID); if (selectedEntry == selectedSet->second.end()) { - logGlobal->error("Error: playing non-existing entry %d from set: %s", entryID, whichSet); + logGlobal->error("Error: playing non-existing entry %s from set: %s", entryID, whichSet); return; } @@ -452,7 +462,7 @@ void CMusicHandler::queueNext(std::unique_ptr queued) } } -void CMusicHandler::queueNext(CMusicHandler *owner, std::string setName, std::string musicURI, bool looped) +void CMusicHandler::queueNext(CMusicHandler *owner, const std::string & setName, const std::string & musicURI, bool looped) { try { @@ -552,7 +562,7 @@ bool MusicEntry::play() if (!setName.empty()) { - auto set = owner->musicsSet[setName]; + const auto & set = owner->musicsSet[setName]; load(RandomGeneratorUtil::nextItem(set, CRandomGenerator::getDefault())->second); } diff --git a/client/CMusicHandler.h b/client/CMusicHandler.h index 36fe6f2ec..b2f3c9a81 100644 --- a/client/CMusicHandler.h +++ b/client/CMusicHandler.h @@ -16,6 +16,7 @@ struct _Mix_Music; struct SDL_RWops; typedef struct _Mix_Music Mix_Music; struct Mix_Chunk; +class Terrain; class CAudioBase { protected: @@ -81,8 +82,8 @@ public: // Sets std::vector pickupSounds; - std::vector horseSounds; std::vector battleIntroSounds; + std::map horseSounds; }; // Helper //now it looks somewhat useless @@ -118,6 +119,7 @@ public: class CMusicHandler: public CAudioBase { private: + //update volume on configuration change SettingsListener listener; void onVolumeChange(const JsonNode &volumeNode); @@ -125,26 +127,27 @@ private: std::unique_ptr current; std::unique_ptr next; - void queueNext(CMusicHandler *owner, std::string setName, std::string musicURI, bool looped); + void queueNext(CMusicHandler *owner, const std::string & setName, const std::string & musicURI, bool looped); void queueNext(std::unique_ptr queued); - std::map > musicsSet; + std::map> musicsSet; public: + CMusicHandler(); /// add entry with URI musicURI in set. Track will have ID musicID - void addEntryToSet(std::string set, int musicID, std::string musicURI); + void addEntryToSet(const std::string & set, const std::string & entryID, const std::string & musicURI); void init() override; void release() override; void setVolume(ui32 percent) override; /// play track by URI, if loop = true music will be looped - void playMusic(std::string musicURI, bool loop); + void playMusic(const std::string & musicURI, bool loop); /// play random track from this set - void playMusicFromSet(std::string musicSet, bool loop); + void playMusicFromSet(const std::string & musicSet, bool loop); /// play specific track from set - void playMusicFromSet(std::string musicSet, int entryID, bool loop); + void playMusicFromSet(const std::string & musicSet, const std::string & entryID, bool loop); void stopMusic(int fade_ms=1000); void musicFinishedCallback(); diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 57d97f0e1..779f973b9 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -1351,12 +1351,6 @@ void CPlayerInterface::heroBonusChanged( const CGHeroInstance *hero, const Bonus template void CPlayerInterface::serializeTempl( Handler &h, const int version ) { - if(version < 774 && !h.saving) - { - bool observerInDuelMode = false; - h & observerInDuelMode; - } - h & wanderingHeroes; h & towns; h & sleepingHeroes; @@ -2742,8 +2736,8 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path) { path.convert(0); - ETerrainType currentTerrain = ETerrainType::BORDER; // not init yet - ETerrainType newTerrain; + Terrain currentTerrain = Terrain("BORDER"); // not init yet + Terrain newTerrain; int sh = -1; auto canStop = [&](CGPathNode * node) -> bool @@ -2779,7 +2773,9 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path) destinationTeleportPos = int3(-1); } if(i != path.nodes.size() - 1) + { sh = CCS->soundh->playSound(CCS->soundh->horseSounds[currentTerrain], -1); + } continue; } @@ -2797,7 +2793,7 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path) #endif { newTerrain = cb->getTile(CGHeroInstance::convertPosition(currentCoord, false))->terType; - if (newTerrain != currentTerrain) + if(newTerrain != currentTerrain) { CCS->soundh->stopSound(sh); sh = CCS->soundh->playSound(CCS->soundh->horseSounds[newTerrain], -1); diff --git a/client/Client.cpp b/client/Client.cpp index 40ec2f7e6..a200c2044 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -274,12 +274,6 @@ void CClient::serialize(BinarySerializer & h, const int version) void CClient::serialize(BinaryDeserializer & h, const int version) { assert(!h.saving); - if(version < 787) - { - bool hotSeat = false; - h & hotSeat; - } - ui8 players = 0; h & players; @@ -337,11 +331,7 @@ void CClient::serialize(BinaryDeserializer & h, const int version) { JsonNode scriptsState; - if(version >= 800) - { - h & scriptsState; - } - + h & scriptsState; clientScripts->serializeState(h.saving, scriptsState); } diff --git a/client/battle/CBattleInterface.cpp b/client/battle/CBattleInterface.cpp index a47c209e8..e04f36b7a 100644 --- a/client/battle/CBattleInterface.cpp +++ b/client/battle/CBattleInterface.cpp @@ -454,7 +454,7 @@ CBattleInterface::~CBattleInterface() if (adventureInt && adventureInt->selection) { - int terrain = LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->terType; + auto & terrain = LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->terType; CCS->musich->playMusicFromSet("terrain", terrain, true); } animsAreDisplayed.setn(false); diff --git a/client/mapHandler.cpp b/client/mapHandler.cpp index 54ae44a0f..f33a8dad5 100644 --- a/client/mapHandler.cpp +++ b/client/mapHandler.cpp @@ -20,6 +20,7 @@ #include "../lib/CGameState.h" #include "../lib/CHeroHandler.h" #include "../lib/CTownHandler.h" +#include "../lib/CModHandler.h" #include "Graphics.h" #include "../lib/mapping/CMap.h" #include "../lib/CConfigHandler.h" @@ -29,6 +30,9 @@ #include "CMT.h" #include "CMusicHandler.h" #include "../lib/CRandomGenerator.h" +#include "../lib/Terrain.h" +#include "../lib/filesystem/ResourceID.h" +#include "../lib/JsonDetail.h" #define ADVOPT (conf.go()->ac) @@ -142,79 +146,68 @@ EMapAnimRedrawStatus CMapHandler::drawTerrainRectNew(SDL_Surface * targetSurface void CMapHandler::initTerrainGraphics() { - static const std::vector TERRAIN_FILES = + static const std::map ROAD_FILES = { - "DIRTTL", - "SANDTL", - "GRASTL", - "SNOWTL", - "SWMPTL", - - "ROUGTL", - "SUBBTL", - "LAVATL", - "WATRTL", - "ROCKTL" + {ROAD_NAMES[1], "dirtrd"}, + {ROAD_NAMES[2], "gravrd"}, + {ROAD_NAMES[3], "cobbrd"} }; - static const std::vector ROAD_FILES = + static const std::map RIVER_FILES = { - "dirtrd", - "gravrd", - "cobbrd" + {RIVER_NAMES[1], "clrrvr"}, + {RIVER_NAMES[2], "icyrvr"}, + {RIVER_NAMES[3], "mudrvr"}, + {RIVER_NAMES[4], "lavrvr"} }; + - static const std::vector RIVER_FILES = + auto loadFlipped = [](TFlippedAnimations & animation, TFlippedCache & cache, const std::map & files) { - "clrrvr", - "icyrvr", - "mudrvr", - "lavrvr" - }; - - auto loadFlipped = [](int types, TFlippedAnimations & animation, TFlippedCache & cache, const std::vector & files) - { - animation.resize(types); - cache.resize(types); - //no rotation and basic setup - for(int i = 0; i < types; i++) + for(auto & type : files) { - animation[i][0] = make_unique(files[i]); - animation[i][0]->preload(); - const size_t views = animation[i][0]->size(0); - cache[i].resize(views); + animation[type.first][0] = make_unique(type.second); + animation[type.first][0]->preload(); + const size_t views = animation[type.first][0]->size(0); + cache[type.first].resize(views); for(int j = 0; j < views; j++) - cache[i][j][0] = animation[i][0]->getImage(j); + cache[type.first][j][0] = animation[type.first][0]->getImage(j); } for(int rotation = 1; rotation < 4; rotation++) { - for(int i = 0; i < types; i++) + for(auto & type : files) { - animation[i][rotation] = make_unique(files[i]); - animation[i][rotation]->preload(); - const size_t views = animation[i][rotation]->size(0); + animation[type.first][rotation] = make_unique(type.second); + animation[type.first][rotation]->preload(); + const size_t views = animation[type.first][rotation]->size(0); for(int j = 0; j < views; j++) { - auto image = animation[i][rotation]->getImage(j); + auto image = animation[type.first][rotation]->getImage(j); if(rotation == 2 || rotation == 3) image->horizontalFlip(); if(rotation == 1 || rotation == 3) image->verticalFlip(); - cache[i][j][rotation] = image; + cache[type.first][j][rotation] = image; } } } }; - - loadFlipped(GameConstants::TERRAIN_TYPES, terrainAnimations, terrainImages, TERRAIN_FILES); - loadFlipped(3, roadAnimations, roadImages, ROAD_FILES); - loadFlipped(4, riverAnimations, riverImages, RIVER_FILES); + + std::map terrainFiles; + for(auto & terrain : Terrain::Manager::terrains()) + { + terrainFiles[terrain] = Terrain::Manager::getInfo(terrain).tilesFilename; + } + + loadFlipped(terrainAnimations, terrainImages, terrainFiles); + loadFlipped(roadAnimations, roadImages, ROAD_FILES); + loadFlipped(riverAnimations, riverImages, RIVER_FILES); // Create enough room for the whole map and its frame @@ -626,6 +619,9 @@ void CMapHandler::CMapBlitter::drawTileTerrain(SDL_Surface * targetSurf, const T Rect destRect(realTileRect); ui8 rotation = tinfo.extTileFlags % 4; + + if(parent->terrainImages[tinfo.terType].size()<=tinfo.terView) + return; drawElement(EMapCacheType::TERRAIN, parent->terrainImages[tinfo.terType][tinfo.terView][rotation], nullptr, targetSurf, &destRect); } @@ -802,21 +798,21 @@ void CMapHandler::CMapBlitter::drawObjects(SDL_Surface * targetSurf, const Terra void CMapHandler::CMapBlitter::drawRoad(SDL_Surface * targetSurf, const TerrainTile & tinfo, const TerrainTile * tinfoUpper) const { - if (tinfoUpper && tinfoUpper->roadType != ERoadType::NO_ROAD) + if (tinfoUpper && tinfoUpper->roadType != ROAD_NAMES[0]) { ui8 rotation = (tinfoUpper->extTileFlags >> 4) % 4; Rect source(0, tileSize / 2, tileSize, tileSize / 2); Rect dest(realPos.x, realPos.y, tileSize, tileSize / 2); - drawElement(EMapCacheType::ROADS, parent->roadImages[tinfoUpper->roadType - 1][tinfoUpper->roadDir][rotation], + drawElement(EMapCacheType::ROADS, parent->roadImages[tinfoUpper->roadType][tinfoUpper->roadDir][rotation], &source, targetSurf, &dest); } - if(tinfo.roadType != ERoadType::NO_ROAD) //print road from this tile + if(tinfo.roadType != ROAD_NAMES[0]) //print road from this tile { ui8 rotation = (tinfo.extTileFlags >> 4) % 4; Rect source(0, 0, tileSize, halfTileSizeCeil); Rect dest(realPos.x, realPos.y + tileSize / 2, tileSize, tileSize / 2); - drawElement(EMapCacheType::ROADS, parent->roadImages[tinfo.roadType - 1][tinfo.roadDir][rotation], + drawElement(EMapCacheType::ROADS, parent->roadImages[tinfo.roadType][tinfo.roadDir][rotation], &source, targetSurf, &dest); } } @@ -825,7 +821,7 @@ void CMapHandler::CMapBlitter::drawRiver(SDL_Surface * targetSurf, const Terrain { Rect destRect(realTileRect); ui8 rotation = (tinfo.extTileFlags >> 2) % 4; - drawElement(EMapCacheType::RIVERS, parent->riverImages[tinfo.riverType-1][tinfo.riverDir][rotation], nullptr, targetSurf, &destRect); + drawElement(EMapCacheType::RIVERS, parent->riverImages[tinfo.riverType][tinfo.riverDir][rotation], nullptr, targetSurf, &destRect); } void CMapHandler::CMapBlitter::drawFow(SDL_Surface * targetSurf) const @@ -876,7 +872,7 @@ void CMapHandler::CMapBlitter::blit(SDL_Surface * targetSurf, const MapDrawingIn if(isVisible || info->showAllTerrain) { drawTileTerrain(targetSurf, tinfo, tile); - if (tinfo.riverType) + if(tinfo.riverType != RIVER_NAMES[0]) drawRiver(targetSurf, tinfo); drawRoad(targetSurf, tinfo, tinfoUpper); } @@ -1326,13 +1322,13 @@ bool CMapHandler::canStartHeroMovement() void CMapHandler::updateWater() //shift colors in palettes of water tiles { - for(auto & elem : terrainImages[7]) + for(auto & elem : terrainImages["lava"]) { for(auto img : elem) img->shiftPalette(246, 9); } - for(auto & elem : terrainImages[8]) + for(auto & elem : terrainImages["water"]) { for(auto img : elem) { @@ -1341,7 +1337,7 @@ void CMapHandler::updateWater() //shift colors in palettes of water tiles } } - for(auto & elem : riverImages[0]) + for(auto & elem : riverImages["clrrvr"]) { for(auto img : elem) { @@ -1350,7 +1346,7 @@ void CMapHandler::updateWater() //shift colors in palettes of water tiles } } - for(auto & elem : riverImages[2]) + for(auto & elem : riverImages["mudrvr"]) { for(auto img : elem) { @@ -1360,7 +1356,7 @@ void CMapHandler::updateWater() //shift colors in palettes of water tiles } } - for(auto & elem : riverImages[3]) + for(auto & elem : riverImages["lavrvr"]) { for(auto img : elem) img->shiftPalette(240, 9); diff --git a/client/mapHandler.h b/client/mapHandler.h index 03610ead2..5206e0613 100644 --- a/client/mapHandler.h +++ b/client/mapHandler.h @@ -354,8 +354,8 @@ public: //terrain graphics //FIXME: unique_ptr should be enough, but fails to compile in MSVS 2013 - typedef std::vector, 4>> TFlippedAnimations; //[type, rotation] - typedef std::vector, 4>>> TFlippedCache;//[type, view type, rotation] + typedef std::map, 4>> TFlippedAnimations; //[type, rotation] + typedef std::map, 4>>> TFlippedCache;//[type, view type, rotation] TFlippedAnimations terrainAnimations;//[terrain type, rotation] TFlippedCache terrainImages;//[terrain type, view type, rotation] diff --git a/client/widgets/AdventureMapClasses.cpp b/client/widgets/AdventureMapClasses.cpp index 5113df340..eac733371 100644 --- a/client/widgets/AdventureMapClasses.cpp +++ b/client/widgets/AdventureMapClasses.cpp @@ -42,6 +42,7 @@ #include "../../lib/CHeroHandler.h" #include "../../lib/CModHandler.h" #include "../../lib/CTownHandler.h" +#include "../../lib/Terrain.h" #include "../../lib/filesystem/Filesystem.h" #include "../../lib/JsonNode.h" #include "../../lib/mapObjects/CGHeroInstance.h" @@ -494,41 +495,30 @@ void CMinimapInstance::showAll(SDL_Surface * to) } } -std::map > CMinimap::loadColors(std::string from) +std::map > CMinimap::loadColors() { - std::map > ret; + std::map > ret; - const JsonNode config(ResourceID(from, EResType::TEXT)); - - for(auto &m : config.Struct()) + for(auto & terrain : Terrain::Manager::terrains()) { - auto index = boost::find(GameConstants::TERRAIN_NAMES, m.first); - if (index == std::end(GameConstants::TERRAIN_NAMES)) - { - logGlobal->error("Error: unknown terrain in terrains.json: %s", m.first); - continue; - } - int terrainID = static_cast(index - std::begin(GameConstants::TERRAIN_NAMES)); - - const JsonVector &unblockedVec = m.second["minimapUnblocked"].Vector(); + auto & m = Terrain::Manager::getInfo(terrain); SDL_Color normal = { - ui8(unblockedVec[0].Float()), - ui8(unblockedVec[1].Float()), - ui8(unblockedVec[2].Float()), + ui8(m.minimapUnblocked[0]), + ui8(m.minimapUnblocked[1]), + ui8(m.minimapUnblocked[2]), ui8(255) }; - const JsonVector &blockedVec = m.second["minimapBlocked"].Vector(); SDL_Color blocked = { - ui8(blockedVec[0].Float()), - ui8(blockedVec[1].Float()), - ui8(blockedVec[2].Float()), + ui8(m.minimapBlocked[0]), + ui8(m.minimapBlocked[1]), + ui8(m.minimapBlocked[2]), ui8(255) }; - ret.insert(std::make_pair(terrainID, std::make_pair(normal, blocked))); + ret[terrain] = std::make_pair(normal, blocked); } return ret; } @@ -536,7 +526,7 @@ std::map > CMinimap::loadColors(std::string CMinimap::CMinimap(const Rect & position) : CIntObject(LCLICK | RCLICK | HOVER | MOVE, position.topLeft()), level(0), - colors(loadColors("config/terrains.json")) + colors(loadColors()) { OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); pos.w = position.w; diff --git a/client/widgets/AdventureMapClasses.h b/client/widgets/AdventureMapClasses.h index d87c58a81..97cb7da74 100644 --- a/client/widgets/AdventureMapClasses.h +++ b/client/widgets/AdventureMapClasses.h @@ -30,6 +30,7 @@ struct InfoAboutTown; class CHeroTooltip; class CTownTooltip; class CTextBox; +class Terrain; /// Base UI Element for hero\town lists class CList : public CIntObject @@ -216,7 +217,7 @@ protected: int level; //to initialize colors - std::map > loadColors(std::string from); + std::map > loadColors(); void clickLeft(tribool down, bool previousState) override; void clickRight(tribool down, bool previousState) override; @@ -227,7 +228,7 @@ protected: public: // terrainID -> (normal color, blocked color) - const std::map > colors; + const std::map > colors; CMinimap(const Rect & position); diff --git a/client/windows/CAdvmapInterface.cpp b/client/windows/CAdvmapInterface.cpp index 9959529c2..fbf5e9c76 100644 --- a/client/windows/CAdvmapInterface.cpp +++ b/client/windows/CAdvmapInterface.cpp @@ -265,7 +265,7 @@ void CTerrainRect::showPath(const SDL_Rect * extRect, SDL_Surface * to) {-1, 1, 2, 23, -1, 3, 22, 21, 12} }; //table of magic values TODO meaning, change variable name - for (int i=0; i < (int)currentPath->nodes.size()-1; ++i) + for (int i = 0; i < -1 + (int)currentPath->nodes.size(); ++i) { const int3 &curPos = currentPath->nodes[i].coord, &nextPos = currentPath->nodes[i+1].coord; if(curPos.z != adventureInt->position.z) diff --git a/config/gameConfig.json b/config/gameConfig.json index ec5ee608f..57872e9a4 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -80,5 +80,9 @@ "skills" : [ "config/skills.json" + ], + "terrains": + [ + "config/terrains.json" ] } diff --git a/config/obstacles.json b/config/obstacles.json index 10c8360e5..bee4258e4 100644 --- a/config/obstacles.json +++ b/config/obstacles.json @@ -14,7 +14,7 @@ "obstacles" : [ { "id" : 0, - "allowedTerrain" : [0], + "allowedTerrain" : ["dirt"], "specialBattlefields" : [], "width" : 2, "height" : 1, @@ -24,7 +24,7 @@ }, { "id" : 1, - "allowedTerrain" : [0, 1, 5, 6], + "allowedTerrain" : ["dirt", "sand", "rough", "subterra"], "specialBattlefields" : [0], "width" : 3, "height" : 2, @@ -34,7 +34,7 @@ }, { "id" : 2, - "allowedTerrain" : [0], + "allowedTerrain" : ["dirt"], "specialBattlefields" : [], "width" : 4, "height" : 2, @@ -44,7 +44,7 @@ }, { "id" : 3, - "allowedTerrain" : [0, 5], + "allowedTerrain" : ["dirt", "rough"], "specialBattlefields" : [1], "width" : 2, "height" : 1, @@ -54,7 +54,7 @@ }, { "id" : 4, - "allowedTerrain" : [0, 5, 6], + "allowedTerrain" : ["dirt", "rough", "subterra"], "specialBattlefields" : [0, 1], "width" : 2, "height" : 1, @@ -64,7 +64,7 @@ }, { "id" : 5, - "allowedTerrain" : [0], + "allowedTerrain" : ["dirt"], "specialBattlefields" : [], "width" : 4, "height" : 2, @@ -74,7 +74,7 @@ }, { "id" : 6, - "allowedTerrain" : [0], + "allowedTerrain" : ["dirt"], "specialBattlefields" : [], "width" : 3, "height" : 2, @@ -84,7 +84,7 @@ }, { "id" : 7, - "allowedTerrain" : [0], + "allowedTerrain" : ["dirt"], "specialBattlefields" : [], "width" : 2, "height" : 2, @@ -94,7 +94,7 @@ }, { "id" : 8, - "allowedTerrain" : [0], + "allowedTerrain" : ["dirt"], "specialBattlefields" : [], "width" : 2, "height" : 2, @@ -104,7 +104,7 @@ }, { "id" : 9, - "allowedTerrain" : [0], + "allowedTerrain" : ["dirt"], "specialBattlefields" : [], "width" : 2, "height" : 2, @@ -114,7 +114,7 @@ }, { "id" : 10, - "allowedTerrain" : [0], + "allowedTerrain" : ["dirt"], "specialBattlefields" : [], "width" : 2, "height" : 2, @@ -124,7 +124,7 @@ }, { "id" : 11, - "allowedTerrain" : [0], + "allowedTerrain" : ["dirt"], "specialBattlefields" : [], "width" : 2, "height" : 1, @@ -134,7 +134,7 @@ }, { "id" : 12, - "allowedTerrain" : [0, 5], + "allowedTerrain" : ["dirt", "rough"], "specialBattlefields" : [1], "width" : 3, "height" : 3, @@ -144,7 +144,7 @@ }, { "id" : 13, - "allowedTerrain" : [0, 5], + "allowedTerrain" : ["dirt", "rough"], "specialBattlefields" : [1], "width" : 3, "height" : 2, @@ -154,7 +154,7 @@ }, { "id" : 14, - "allowedTerrain" : [0, 5], + "allowedTerrain" : ["dirt", "rough"], "specialBattlefields" : [1], "width" : 3, "height" : 2, @@ -164,7 +164,7 @@ }, { "id" : 15, - "allowedTerrain" : [0, 5], + "allowedTerrain" : ["dirt", "rough"], "specialBattlefields" : [1], "width" : 3, "height" : 3, @@ -174,7 +174,7 @@ }, { "id" : 16, - "allowedTerrain" : [1], + "allowedTerrain" : ["sand"], "specialBattlefields" : [], "width" : 4, "height" : 4, @@ -184,7 +184,7 @@ }, { "id" : 17, - "allowedTerrain" : [1], + "allowedTerrain" : ["sand"], "specialBattlefields" : [], "width" : 3, "height" : 2, @@ -194,7 +194,7 @@ }, { "id" : 18, - "allowedTerrain" : [1], + "allowedTerrain" : ["sand"], "specialBattlefields" : [], "width" : 4, "height" : 2, @@ -204,7 +204,7 @@ }, { "id" : 19, - "allowedTerrain" : [2, 4], + "allowedTerrain" : ["grass", "swamp"], "specialBattlefields" : [], "width" : 2, "height" : 1, @@ -214,7 +214,7 @@ }, { "id" : 20, - "allowedTerrain" : [2, 4], + "allowedTerrain" : ["grass", "swamp"], "specialBattlefields" : [2], "width" : 2, "height" : 2, @@ -224,7 +224,7 @@ }, { "id" : 21, - "allowedTerrain" : [2, 4], + "allowedTerrain" : ["grass", "swamp"], "specialBattlefields" : [], "width" : 1, "height" : 1, @@ -234,7 +234,7 @@ }, { "id" : 22, - "allowedTerrain" : [2], + "allowedTerrain" : ["grass"], "specialBattlefields" : [2], "width" : 6, "height" : 2, @@ -244,7 +244,7 @@ }, { "id" : 23, - "allowedTerrain" : [2], + "allowedTerrain" : ["grass"], "specialBattlefields" : [], "width" : 7, "height" : 1, @@ -254,7 +254,7 @@ }, { "id" : 24, - "allowedTerrain" : [3], + "allowedTerrain" : ["snow"], "specialBattlefields" : [], "width" : 3, "height" : 1, @@ -264,7 +264,7 @@ }, { "id" : 25, - "allowedTerrain" : [3], + "allowedTerrain" : ["snow"], "specialBattlefields" : [], "width" : 5, "height" : 1, @@ -274,7 +274,7 @@ }, { "id" : 26, - "allowedTerrain" : [3], + "allowedTerrain" : ["snow"], "specialBattlefields" : [], "width" : 3, "height" : 3, @@ -284,7 +284,7 @@ }, { "id" : 27, - "allowedTerrain" : [3], + "allowedTerrain" : ["snow"], "specialBattlefields" : [], "width" : 3, "height" : 1, @@ -294,7 +294,7 @@ }, { "id" : 28, - "allowedTerrain" : [3], + "allowedTerrain" : ["snow"], "specialBattlefields" : [], "width" : 3, "height" : 1, @@ -304,7 +304,7 @@ }, { "id" : 29, - "allowedTerrain" : [3], + "allowedTerrain" : ["snow"], "specialBattlefields" : [], "width" : 3, "height" : 2, @@ -314,7 +314,7 @@ }, { "id" : 30, - "allowedTerrain" : [3], + "allowedTerrain" : ["snow"], "specialBattlefields" : [], "width" : 2, "height" : 1, @@ -324,7 +324,7 @@ }, { "id" : 31, - "allowedTerrain" : [3], + "allowedTerrain" : ["snow"], "specialBattlefields" : [], "width" : 3, "height" : 2, @@ -334,7 +334,7 @@ }, { "id" : 32, - "allowedTerrain" : [3], + "allowedTerrain" : ["snow"], "specialBattlefields" : [], "width" : 7, "height" : 2, @@ -344,7 +344,7 @@ }, { "id" : 33, - "allowedTerrain" : [3], + "allowedTerrain" : ["snow"], "specialBattlefields" : [], "width" : 5, "height" : 5, @@ -354,7 +354,7 @@ }, { "id" : 34, - "allowedTerrain" : [4], + "allowedTerrain" : ["swamp"], "specialBattlefields" : [], "width" : 2, "height" : 2, @@ -364,7 +364,7 @@ }, { "id" : 35, - "allowedTerrain" : [4], + "allowedTerrain" : ["swamp"], "specialBattlefields" : [], "width" : 8, "height" : 3, @@ -374,7 +374,7 @@ }, { "id" : 36, - "allowedTerrain" : [4], + "allowedTerrain" : ["swamp"], "specialBattlefields" : [], "width" : 2, "height" : 1, @@ -384,7 +384,7 @@ }, { "id" : 37, - "allowedTerrain" : [4], + "allowedTerrain" : ["swamp"], "specialBattlefields" : [], "width" : 3, "height" : 1, @@ -394,7 +394,7 @@ }, { "id" : 38, - "allowedTerrain" : [4], + "allowedTerrain" : ["swamp"], "specialBattlefields" : [], "width" : 5, "height" : 4, @@ -404,7 +404,7 @@ }, { "id" : 39, - "allowedTerrain" : [4], + "allowedTerrain" : ["swamp"], "specialBattlefields" : [], "width" : 4, "height" : 3, @@ -414,7 +414,7 @@ }, { "id" : 40, - "allowedTerrain" : [5], + "allowedTerrain" : ["rough"], "specialBattlefields" : [1], "width" : 2, "height" : 2, @@ -424,7 +424,7 @@ }, { "id" : 41, - "allowedTerrain" : [5], + "allowedTerrain" : ["rough"], "specialBattlefields" : [1], "width" : 4, "height" : 3, @@ -434,7 +434,7 @@ }, { "id" : 42, - "allowedTerrain" : [5], + "allowedTerrain" : ["rough"], "specialBattlefields" : [1], "width" : 3, "height" : 2, @@ -444,7 +444,7 @@ }, { "id" : 43, - "allowedTerrain" : [5], + "allowedTerrain" : ["rough"], "specialBattlefields" : [1], "width" : 3, "height" : 3, @@ -454,7 +454,7 @@ }, { "id" : 44, - "allowedTerrain" : [5], + "allowedTerrain" : ["rough"], "specialBattlefields" : [1], "width" : 3, "height" : 3, @@ -464,7 +464,7 @@ }, { "id" : 45, - "allowedTerrain" : [6], + "allowedTerrain" : ["subterra"], "specialBattlefields" : [], "width" : 3, "height" : 3, @@ -474,7 +474,7 @@ }, { "id" : 46, - "allowedTerrain" : [6], + "allowedTerrain" : ["subterra"], "specialBattlefields" : [], "width" : 3, "height" : 2, @@ -484,7 +484,7 @@ }, { "id" : 47, - "allowedTerrain" : [6], + "allowedTerrain" : ["subterra"], "specialBattlefields" : [], "width" : 4, "height" : 3, @@ -494,7 +494,7 @@ }, { "id" : 48, - "allowedTerrain" : [7], + "allowedTerrain" : ["lava"], "specialBattlefields" : [], "width" : 4, "height" : 3, @@ -504,7 +504,7 @@ }, { "id" : 49, - "allowedTerrain" : [7], + "allowedTerrain" : ["lava"], "specialBattlefields" : [], "width" : 4, "height" : 2, @@ -514,7 +514,7 @@ }, { "id" : 50, - "allowedTerrain" : [7], + "allowedTerrain" : ["lava"], "specialBattlefields" : [], "width" : 5, "height" : 3, @@ -524,7 +524,7 @@ }, { "id" : 51, - "allowedTerrain" : [7], + "allowedTerrain" : ["lava"], "specialBattlefields" : [], "width" : 3, "height" : 2, @@ -534,7 +534,7 @@ }, { "id" : 52, - "allowedTerrain" : [7], + "allowedTerrain" : ["lava"], "specialBattlefields" : [], "width" : 4, "height" : 4, @@ -544,7 +544,7 @@ }, { "id" : 53, - "allowedTerrain" : [7], + "allowedTerrain" : ["lava"], "specialBattlefields" : [], "width" : 5, "height" : 3, @@ -554,7 +554,7 @@ }, { "id" : 54, - "allowedTerrain" : [7], + "allowedTerrain" : ["lava"], "specialBattlefields" : [], "width" : 5, "height" : 3, @@ -564,7 +564,7 @@ }, { "id" : 55, - "allowedTerrain" : [8], + "allowedTerrain" : ["water"], "specialBattlefields" : [], "width" : 3, "height" : 3, @@ -926,7 +926,7 @@ "absoluteObstacles" : [ { "id" : 0, - "allowedTerrain" : [0], + "allowedTerrain" : ["dirt"], "specialBattlefields" : [], "width" : 124, "height" : 254, @@ -935,7 +935,7 @@ }, { "id" : 1, - "allowedTerrain" : [0], + "allowedTerrain" : ["dirt"], "specialBattlefields" : [], "width" : 256, "height" : 254, @@ -944,7 +944,7 @@ }, { "id" : 2, - "allowedTerrain" : [0], + "allowedTerrain" : ["dirt"], "specialBattlefields" : [], "width" : 168, "height" : 212, @@ -953,7 +953,7 @@ }, { "id" : 3, - "allowedTerrain" : [0], + "allowedTerrain" : ["dirt"], "specialBattlefields" : [], "width" : 124, "height" : 254, @@ -962,7 +962,7 @@ }, { "id" : 4, - "allowedTerrain" : [0], + "allowedTerrain" : ["dirt"], "specialBattlefields" : [], "width" : 146, "height" : 254, @@ -971,7 +971,7 @@ }, { "id" : 5, - "allowedTerrain" : [2], + "allowedTerrain" : ["grass"], "specialBattlefields" : [], "width" : 173, "height" : 221, @@ -980,7 +980,7 @@ }, { "id" : 6, - "allowedTerrain" : [2], + "allowedTerrain" : ["grass"], "specialBattlefields" : [], "width" : 180, "height" : 264, @@ -989,7 +989,7 @@ }, { "id" : 7, - "allowedTerrain" : [3], + "allowedTerrain" : ["snow"], "specialBattlefields" : [], "width" : 166, "height" : 255, @@ -998,7 +998,7 @@ }, { "id" : 8, - "allowedTerrain" : [3], + "allowedTerrain" : ["snow"], "specialBattlefields" : [], "width" : 302, "height" : 172, @@ -1007,7 +1007,7 @@ }, { "id" : 9, - "allowedTerrain" : [4], + "allowedTerrain" : ["swamp"], "specialBattlefields" : [], "width" : 300, "height" : 170, @@ -1016,7 +1016,7 @@ }, { "id" : 10, - "allowedTerrain" : [4], + "allowedTerrain" : ["swamp"], "specialBattlefields" : [], "width" : 278, "height" : 171, @@ -1025,7 +1025,7 @@ }, { "id" : 11, - "allowedTerrain" : [4], + "allowedTerrain" : ["swamp"], "specialBattlefields" : [], "width" : 256, "height" : 254, @@ -1034,7 +1034,7 @@ }, { "id" : 12, - "allowedTerrain" : [7], + "allowedTerrain" : ["lava"], "specialBattlefields" : [], "width" : 124, "height" : 254, @@ -1043,7 +1043,7 @@ }, { "id" : 13, - "allowedTerrain" : [7], + "allowedTerrain" : ["lava"], "specialBattlefields" : [], "width" : 256, "height" : 128, @@ -1052,7 +1052,7 @@ }, { "id" : 14, - "allowedTerrain" : [5], + "allowedTerrain" : ["rough"], "specialBattlefields" : [1], "width" : 186, "height" : 212, @@ -1061,7 +1061,7 @@ }, { "id" : 15, - "allowedTerrain" : [5], + "allowedTerrain" : ["rough"], "specialBattlefields" : [1], "width" : 347, "height" : 174, @@ -1070,7 +1070,7 @@ }, { "id" : 16, - "allowedTerrain" : [5], + "allowedTerrain" : ["rough"], "specialBattlefields" : [1], "width" : 294, "height" : 169, @@ -1079,7 +1079,7 @@ }, { "id" : 17, - "allowedTerrain" : [5], + "allowedTerrain" : ["rough"], "specialBattlefields" : [1], "width" : 165, "height" : 257, @@ -1088,7 +1088,7 @@ }, { "id" : 18, - "allowedTerrain" : [5], + "allowedTerrain" : ["rough"], "specialBattlefields" : [1], "width" : 208, "height" : 268, @@ -1097,7 +1097,7 @@ }, { "id" : 19, - "allowedTerrain" : [5], + "allowedTerrain" : ["rough"], "specialBattlefields" : [1], "width" : 252, "height" : 254, @@ -1106,7 +1106,7 @@ }, { "id" : 20, - "allowedTerrain" : [5], + "allowedTerrain" : ["rough"], "specialBattlefields" : [1], "width" : 278, "height" : 128, @@ -1115,7 +1115,7 @@ }, { "id" : 21, - "allowedTerrain" : [5], + "allowedTerrain" : ["rough"], "specialBattlefields" : [1], "width" : 208, "height" : 268, @@ -1124,7 +1124,7 @@ }, { "id" : 22, - "allowedTerrain" : [5], + "allowedTerrain" : ["rough"], "specialBattlefields" : [1], "width" : 168, "height" : 212, diff --git a/config/randomMap.json b/config/randomMap.json index 72de50821..260acf3e0 100644 --- a/config/randomMap.json +++ b/config/randomMap.json @@ -2,7 +2,7 @@ "terrain" : { "undergroundAllow" : ["lava"], //others to be replaced by subterranena - "groundProhibit" : ["subterranean"] //to be replaced by dirt + "groundProhibit" : ["subterra"] //to be replaced by dirt }, "waterZone" : { @@ -31,7 +31,7 @@ "extraResourcesLimit" : 3 }, "minGuardStrength" : 2000, - "defaultRoadType" : "cobblestone_road", + "defaultRoadType" : "pc", //pd - dirt, pg - gravel, pc - cobblestone "treasureValueLimit" : 20000, //generate pandora with gold for treasure above this limit "prisons" : { diff --git a/config/terrains.json b/config/terrains.json index 0e56d0986..b6c1b0bf6 100644 --- a/config/terrains.json +++ b/config/terrains.json @@ -4,69 +4,92 @@ "moveCost" : 100, "minimapUnblocked" : [ 82, 56, 8 ], "minimapBlocked" : [ 57, 40, 8 ], - "music" : "Dirt.mp3" + "music" : "Dirt.mp3", + "tiles" : "DIRTTL", + "code" : "dt" }, "sand" : { "moveCost" : 150, "minimapUnblocked" : [ 222, 207, 140 ], "minimapBlocked" : [ 165, 158, 107 ], - "music" : "Sand.mp3" + "music" : "Sand.mp3", + "tiles" : "SANDTL", + "code" : "sa" }, "grass" : { "moveCost" : 100, "minimapUnblocked" : [ 0, 65, 0 ], "minimapBlocked" : [ 0, 48, 0 ], - "music" : "Grass.mp3" + "music" : "Grass.mp3", + "tiles" : "GRASTL", + "code" : "gr" }, "snow" : { "moveCost" : 150, "minimapUnblocked" : [ 181, 199, 198 ], "minimapBlocked" : [ 140, 158, 156 ], - "music" : "Snow.mp3" + "music" : "Snow.mp3", + "tiles" : "SNOWTL", + "code" : "sn" }, "swamp" : { "moveCost" : 175, "minimapUnblocked" : [ 74, 134, 107 ], "minimapBlocked" : [ 33, 89, 66 ], - "music" : "Swamp.mp3" + "music" : "Swamp.mp3", + "tiles" : "SWMPTL", + "code" : "sw" }, "rough" : { "moveCost" : 125, "minimapUnblocked" : [ 132, 113, 49 ], "minimapBlocked" : [ 99, 81, 33 ], - "music" : "Rough.mp3" + "music" : "Rough.mp3", + "tiles" : "ROUGTL", + "code" : "rg" }, "subterra" : { "moveCost" : 100, "minimapUnblocked" : [ 132, 48, 0 ], "minimapBlocked" : [ 90, 8, 0 ], - "music" : "Underground.mp3" + "music" : "Underground.mp3", + "tiles" : "SUBBTL", + "type" : "SUB", + "code" : "sb" }, "lava" : { "moveCost" : 100, "minimapUnblocked" : [ 74, 73, 74 ], "minimapBlocked" : [ 41, 40, 41 ], - "music" : "Lava.mp3" + "music" : "Lava.mp3", + "tiles" : "LAVATL", + "code" : "lv" }, "water" : { "moveCost" : 100, "minimapUnblocked" : [ 8, 81, 148 ], "minimapBlocked" : [ 8, 81, 148 ], - "music" : "Water.mp3" + "music" : "Water.mp3", + "tiles" : "WATRTL", + "type" : "WATER", + "code" : "wt" }, "rock" : { "moveCost" : -1, "minimapUnblocked" : [ 0, 0, 0 ], "minimapBlocked" : [ 0, 0, 0 ], - "music" : "Underground.mp3" // Impossible in H3 + "music" : "Underground.mp3", // Impossible in H3 + "tiles" : "ROCKTL", + "type" : "ROCK", + "code" : "rc" } } diff --git a/lib/CArtHandler.h b/lib/CArtHandler.h index 0dc2bb6b0..2acdbeefc 100644 --- a/lib/CArtHandler.h +++ b/lib/CArtHandler.h @@ -102,19 +102,8 @@ public: h & constituentOf; h & aClass; h & id; - if(version >= 759) - { - h & identifier; - } - - if(version >= 771) - { - h & warMachine; - } - else if(!h.saving) - { - fillWarMachine(); - } + h & identifier; + h & warMachine; } CArtifact(); diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index 97306a3dd..8fa13ef39 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -16,6 +16,7 @@ #include "CGameState.h" #include "CTownHandler.h" #include "CModHandler.h" +#include "Terrain.h" #include "StringConstants.h" #include "serializer/JsonDeserializer.h" #include "serializer/JsonUpdater.h" @@ -282,13 +283,13 @@ std::string CCreature::nodeName() const return "\"" + namePl + "\""; } -bool CCreature::isItNativeTerrain(ETerrainType::EETerrainType terrain) const +bool CCreature::isItNativeTerrain(const Terrain & terrain) const { auto native = getNativeTerrain(); - return native == terrain || native == ETerrainType::ANY_TERRAIN; + return native == terrain || native == Terrain::ANY; } -ETerrainType::EETerrainType CCreature::getNativeTerrain() const +Terrain CCreature::getNativeTerrain() const { const std::string cachingStringBlocksRetaliation = "type_NO_TERRAIN_PENALTY"; static const auto selectorBlocksRetaliation = Selector::type()(Bonus::NO_TERRAIN_PENALTY); @@ -296,8 +297,8 @@ ETerrainType::EETerrainType CCreature::getNativeTerrain() const //this code is used in the CreatureTerrainLimiter::limit to setup battle bonuses //and in the CGHeroInstance::getNativeTerrain() to setup mevement bonuses or/and penalties. return hasBonus(selectorBlocksRetaliation, selectorBlocksRetaliation) - ? ETerrainType::ANY_TERRAIN - : (ETerrainType::EETerrainType)(*VLC->townh)[faction]->nativeTerrain; + ? Terrain::ANY + : (Terrain)(*VLC->townh)[faction]->nativeTerrain; } void CCreature::updateFrom(const JsonNode & data) @@ -1340,11 +1341,6 @@ void CCreatureHandler::removeBonusesFromAllCreatures() allCreatures.removeBonuses(Selector::all); } -void CCreatureHandler::restoreAllCreaturesNodeType794() -{ - allCreatures.setNodeType(CBonusSystemNode::ENodeTypes::ALL_CREATURES); -} - void CCreatureHandler::buildBonusTreeForTiers() { for(CCreature * c : objects) diff --git a/lib/CCreatureHandler.h b/lib/CCreatureHandler.h index dfdcd3e01..46939ea20 100644 --- a/lib/CCreatureHandler.h +++ b/lib/CCreatureHandler.h @@ -24,6 +24,7 @@ class CLegacyConfigParser; class CCreatureHandler; class CCreature; class JsonSerializeFormat; +class Terrain; class DLL_LINKAGE CCreature : public Creature, public CBonusSystemNode { @@ -118,14 +119,14 @@ public: ArtifactID warMachine; - bool isItNativeTerrain(ETerrainType::EETerrainType terrain) const; + bool isItNativeTerrain(const Terrain & terrain) const; /** Returns creature native terrain considering some terrain bonuses. @param considerBonus is used to avoid Dead Lock when this method is called inside getAllBonuses considerBonus = true is called from Pathfinder and fills actual nativeTerrain considering bonus(es). considerBonus = false is called on Battle init and returns already prepared nativeTerrain without Bonus system calling. */ - ETerrainType::EETerrainType getNativeTerrain() const; + Terrain getNativeTerrain() const; int32_t getIndex() const override; int32_t getIconIndex() const override; const std::string & getName() const override; @@ -211,18 +212,8 @@ public: h & doubleWide; h & special; - if(version>=759) - { - h & identifier; - } - if(version >= 771) - { - h & warMachine; - } - else if(!h.saving) - { - fillWarMachine(); - } + h & identifier; + h & warMachine; } CCreature(); @@ -281,7 +272,6 @@ public: void addBonusForTier(int tier, const std::shared_ptr & b); //tier must be <1-7> void addBonusForAllCreatures(const std::shared_ptr & b); //due to CBonusSystem::addNewBonus(const std::shared_ptr& b); void removeBonusesFromAllCreatures(); - void restoreAllCreaturesNodeType794(); //restore ALL_CREATURES node type for old saves CCreatureHandler(); ~CCreatureHandler(); diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index 38737d81e..b089ac138 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -574,7 +574,7 @@ EBuildingState::EBuildingState CGameInfoCallback::canBuildStructure( const CGTow { const TerrainTile *tile = getTile(t->bestLocation(), false); - if(!tile || tile->terType != ETerrainType::WATER) + if(!tile || tile->terType.isLand()) return EBuildingState::NO_WATER; //lack of water } diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index a0fcdad80..a70f7a998 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -963,8 +963,8 @@ void CGameState::initGrailPosition() const TerrainTile &t = map->getTile(int3(i, j, k)); if(!t.blocked && !t.visitable - && t.terType != ETerrainType::WATER - && t.terType != ETerrainType::ROCK + && t.terType.isLand() + && t.terType.isPassable() && (int)map->grailPos.dist2dSQ(int3(i, j, k)) <= (map->grailRadius * map->grailRadius)) allowedPos.push_back(int3(i,j,k)); } @@ -1940,31 +1940,31 @@ BFieldType CGameState::battleGetBattlefieldType(int3 tile, CRandomGenerator & ra if(map->isCoastalTile(tile)) //coastal tile is always ground return BFieldType::SAND_SHORE; - switch(t.terType) - { - case ETerrainType::DIRT: + if(t.terType == Terrain("dirt")) return BFieldType(rand.nextInt(3, 5)); - case ETerrainType::SAND: + if(t.terType == Terrain("sand")) return BFieldType::SAND_MESAS; //TODO: coast support - case ETerrainType::GRASS: + if(t.terType == Terrain("grass")) return BFieldType(rand.nextInt(6, 7)); - case ETerrainType::SNOW: + if(t.terType == Terrain("snow")) return BFieldType(rand.nextInt(10, 11)); - case ETerrainType::SWAMP: + if(t.terType == Terrain("swamp")) return BFieldType::SWAMP_TREES; - case ETerrainType::ROUGH: + if(t.terType == Terrain("rough")) return BFieldType::ROUGH; - case ETerrainType::SUBTERRANEAN: + if(t.terType.isUnderground()) return BFieldType::SUBTERRANEAN; - case ETerrainType::LAVA: + if(t.terType == Terrain("lava")) return BFieldType::LAVA; - case ETerrainType::WATER: + if(t.terType.isWater()) return BFieldType::SHIP; - case ETerrainType::ROCK: + if(!t.terType.isPassable()) return BFieldType::ROCKLANDS; - default: - return BFieldType::NONE; - } + + //TODO: STUB, support new battlegrounds + return BFieldType::DIRT_HILLS; + + return BFieldType::NONE; } UpgradeInfo CGameState::getUpgradeInfo(const CStackInstance &stack) @@ -2145,7 +2145,7 @@ void CGameState::updateRumor() rumorId = *RandomGeneratorUtil::nextItem(sRumorTypes, rand); if(rumorId == RumorState::RUMOR_GRAIL) { - rumorExtra = getTile(map->grailPos)->terType; + rumorExtra = getTile(map->grailPos)->terType.id(); break; } diff --git a/lib/CGameState.h b/lib/CGameState.h index 381b29242..d4fba20c9 100644 --- a/lib/CGameState.h +++ b/lib/CGameState.h @@ -227,14 +227,7 @@ public: h & hpool; h & globalEffects; h & rand; - if(version >= 755) //save format backward compatibility - { - h & rumor; - } - else if(!h.saving) - { - rumor = RumorState(); - } + h & rumor; BONUS_TREE_DESERIALIZATION_FIX } diff --git a/lib/CGeneralTextHandler.cpp b/lib/CGeneralTextHandler.cpp index 18f9dcf1e..aa19fdfb3 100644 --- a/lib/CGeneralTextHandler.cpp +++ b/lib/CGeneralTextHandler.cpp @@ -17,6 +17,7 @@ #include "CModHandler.h" #include "GameConstants.h" #include "VCMI_Lib.h" +#include "Terrain.h" size_t Unicode::getCharacterSize(char firstByte) { @@ -309,6 +310,7 @@ void CGeneralTextHandler::readToVector(std::string sourceName, std::vector h3mTerrainNames; readToVector("DATA/VCDESC.TXT", victoryConditions); readToVector("DATA/LCDESC.TXT", lossCondtions); readToVector("DATA/TCOMMAND.TXT", tcommands); @@ -317,7 +319,7 @@ CGeneralTextHandler::CGeneralTextHandler() readToVector("DATA/ADVEVENT.TXT", advobtxt); readToVector("DATA/XTRAINFO.TXT", xtrainfo); readToVector("DATA/RESTYPES.TXT", restypes); - readToVector("DATA/TERRNAME.TXT", terrainNames); + readToVector("DATA/TERRNAME.TXT", h3mTerrainNames); readToVector("DATA/RANDSIGN.TXT", randsign); readToVector("DATA/CRGEN1.TXT", creGens); readToVector("DATA/CRGEN4.TXT", creGens4); @@ -331,6 +333,17 @@ CGeneralTextHandler::CGeneralTextHandler() readToVector("DATA/HEROSCRN.TXT", heroscrn); readToVector("DATA/TENTCOLR.TXT", tentColors); readToVector("DATA/SKILLLEV.TXT", levels); + + for(int i = 0; i < h3mTerrainNames.size(); ++i) + { + terrainNames[Terrain::createTerrainTypeH3M(i)] = h3mTerrainNames[i]; + } + for(auto & terrain : Terrain::Manager::terrains()) + { + if(!Terrain::Manager::getInfo(terrain).terrainText.empty()) + terrainNames[terrain] = Terrain::Manager::getInfo(terrain).terrainText; + } + static const char * QE_MOD_COMMANDS = "DATA/QECOMMANDS.TXT"; if (CResourceHandler::get()->existsResource(ResourceID(QE_MOD_COMMANDS, EResType::TEXT))) diff --git a/lib/CGeneralTextHandler.h b/lib/CGeneralTextHandler.h index 02c2b6385..58feed9ce 100644 --- a/lib/CGeneralTextHandler.h +++ b/lib/CGeneralTextHandler.h @@ -122,7 +122,7 @@ public: std::vector advobtxt; std::vector xtrainfo; std::vector restypes; //names of resources - std::vector terrainNames; + std::map terrainNames; std::vector randsign; std::vector> mines; //first - name; second - event description std::vector seerEmpty; diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index 268929ea3..107fe81cd 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -19,6 +19,7 @@ #include "CCreatureHandler.h" #include "CModHandler.h" #include "CTownHandler.h" +#include "Terrain.h" #include "mapObjects/CObjectHandler.h" //for hero specialty #include "CSkillHandler.h" #include @@ -176,7 +177,7 @@ std::vector CObstacleInfo::getBlocked(BattleHex hex) const return ret; } -bool CObstacleInfo::isAppropriate(ETerrainType terrainType, int specialBattlefield) const +bool CObstacleInfo::isAppropriate(Terrain terrainType, int specialBattlefield) const { if(specialBattlefield != -1) return vstd::contains(allowedSpecialBfields, specialBattlefield); @@ -376,9 +377,9 @@ CHeroHandler::CHeroHandler() { loadObstacles(); loadTerrains(); - for (int i = 0; i < GameConstants::TERRAIN_TYPES; ++i) + for(int i = 0; i < Terrain::Manager::terrains().size(); ++i) { - VLC->modh->identifiers.registerObject("core", "terrain", GameConstants::TERRAIN_NAMES[i], i); + VLC->modh->identifiers.registerObject("core", "terrain", Terrain::Manager::terrains()[i], i); } loadBallistics(); loadExperience(); @@ -826,7 +827,8 @@ void CHeroHandler::loadObstacles() obi.defName = obs["defname"].String(); obi.width = static_cast(obs["width"].Float()); obi.height = static_cast(obs["height"].Float()); - obi.allowedTerrains = obs["allowedTerrain"].convertTo >(); + for(auto & t : obs["allowedTerrain"].Vector()) + obi.allowedTerrains.emplace_back(t.String()); obi.allowedSpecialBfields = obs["specialBattlefields"].convertTo >(); obi.blockedTiles = obs["blockedTiles"].convertTo >(); obi.isAbsoluteObstacle = absolute; @@ -1029,11 +1031,10 @@ ui64 CHeroHandler::reqExp (ui32 level) const void CHeroHandler::loadTerrains() { - const JsonNode config(ResourceID("config/terrains.json")); - - terrCosts.reserve(GameConstants::TERRAIN_TYPES); - for(const std::string & name : GameConstants::TERRAIN_NAMES) - terrCosts.push_back((int)config[name]["moveCost"].Float()); + for(auto & terrain : Terrain::Manager::terrains()) + { + terrCosts[terrain] = Terrain::Manager::getInfo(terrain).moveCost; + } } std::vector CHeroHandler::getDefaultAllowed() const diff --git a/lib/CHeroHandler.h b/lib/CHeroHandler.h index f0fd83031..14d2b1b8a 100644 --- a/lib/CHeroHandler.h +++ b/lib/CHeroHandler.h @@ -26,6 +26,7 @@ struct BattleHex; class JsonNode; class CRandomGenerator; class JsonSerializeFormat; +class Terrain; struct SSpecialtyInfo { si32 type; @@ -119,15 +120,7 @@ public: h & initialArmy; h & heroClass; h & secSkillsInit; - if(version >= 781) - { - h & specialty; - } - else - { - h & specDeprecated; - h & specialtyDeprecated; - } + h & specialty; h & spells; h & haveSpellBook; h & sex; @@ -141,14 +134,8 @@ public: h & iconSpecLarge; h & portraitSmall; h & portraitLarge; - if(version >= 759) - { - h & identifier; - } - if(version >= 790) - { - h & battleImage; - } + h & identifier; + h & battleImage; } }; @@ -211,16 +198,7 @@ public: h & identifier; h & name; h & faction; - if(version >= 800) - { - h & id; - } - else - { - ui8 old_id = 0; - h & old_id; - id = HeroClassID(old_id); - } + h & id; h & defaultTavernChance; h & primarySkillInitial; h & primarySkillLowLevel; @@ -248,7 +226,7 @@ struct DLL_LINKAGE CObstacleInfo { si32 ID; std::string defName; - std::vector allowedTerrains; + std::vector allowedTerrains; std::vector allowedSpecialBfields; ui8 isAbsoluteObstacle; //there may only one such obstacle in battle and its position is always the same @@ -257,7 +235,7 @@ struct DLL_LINKAGE CObstacleInfo std::vector getBlocked(BattleHex hex) const; //returns vector of hexes blocked by obstacle when it's placed on hex 'hex' - bool isAppropriate(ETerrainType terrainType, int specialBattlefield = -1) const; + bool isAppropriate(Terrain terrainType, int specialBattlefield = -1) const; template void serialize(Handler &h, const int version) { @@ -315,7 +293,7 @@ public: CHeroClassHandler classes; //default costs of going through terrains. -1 means terrain is impassable - std::vector terrCosts; + std::map terrCosts; struct SBallisticsLevelInfo { diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 49d8c338d..36c9c1bb6 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -163,6 +163,7 @@ set(lib_SRCS StartInfo.cpp ResourceSet.cpp ScriptHandler.cpp + Terrain.cpp VCMIDirs.cpp VCMI_Lib.cpp @@ -385,6 +386,7 @@ set(lib_HEADERS ScopeGuard.h StartInfo.h StringConstants.h + Terrain.h UnlockGuard.h VCMIDirs.h vcmi_endian.h diff --git a/lib/CModHandler.h b/lib/CModHandler.h index 9cb0474df..5df3dfc38 100644 --- a/lib/CModHandler.h +++ b/lib/CModHandler.h @@ -303,32 +303,9 @@ public: h & ALL_CREATURES_GET_DOUBLE_MONTHS; h & MAX_HEROES_AVAILABLE_PER_PLAYER; h & MAX_HEROES_ON_MAP_PER_PLAYER; - if(version >= 756) - { - h & WINNING_HERO_WITH_NO_TROOPS_RETREATS; - } - else if(!h.saving) - { - WINNING_HERO_WITH_NO_TROOPS_RETREATS = true; - } - - if(version >= 776) - { - h & BLACK_MARKET_MONTHLY_ARTIFACTS_CHANGE; - } - else if(!h.saving) - { - BLACK_MARKET_MONTHLY_ARTIFACTS_CHANGE = true; - } - - if(version >= 791) - { - h & NO_RANDOM_SPECIAL_WEEKS_AND_MONTHS; - } - else if(!h.saving) - { - NO_RANDOM_SPECIAL_WEEKS_AND_MONTHS = false; - } + h & WINNING_HERO_WITH_NO_TROOPS_RETREATS; + h & BLACK_MARKET_MONTHLY_ARTIFACTS_CHANGE; + h & NO_RANDOM_SPECIAL_WEEKS_AND_MONTHS; } } settings; diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 11a86beaf..f1c10ff77 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -46,24 +46,19 @@ void NodeStorage::initialize(const PathfinderOptions & options, const CGameState for(pos.z=0; pos.z < sizes.z; ++pos.z) { const TerrainTile * tile = &gs->map->getTile(pos); - switch(tile->terType) + if(tile->terType.isWater()) { - case ETerrainType::ROCK: - break; - - case ETerrainType::WATER: resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); if(useFlying) resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); if(useWaterWalking) resetTile(pos, ELayer::WATER, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); - break; - - default: + } + if(tile->terType.isLand()) + { resetTile(pos, ELayer::LAND, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); if(useFlying) resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); - break; } } } @@ -1012,8 +1007,7 @@ bool CPathfinderHelper::passOneTurnLimitCheck(const PathNodeInfo & source) const TurnInfo::BonusCache::BonusCache(TConstBonusListPtr bl) { - noTerrainPenalty.reserve(ETerrainType::ROCK); - for(int i = 0; i < ETerrainType::ROCK; i++) + for(int i = 0; i < Terrain::Manager::terrains().size(); ++i) { noTerrainPenalty.push_back(static_cast( bl->getFirst(Selector::type()(Bonus::NO_TERRAIN_PENALTY).And(Selector::subtype()(i))))); @@ -1179,7 +1173,7 @@ void CPathfinderHelper::getNeighbours( continue; const TerrainTile & hlpt = map->getTile(hlp); - if(hlpt.terType == ETerrainType::ROCK) + if(!hlpt.terType.isPassable()) continue; // //we cannot visit things from blocked tiles @@ -1189,18 +1183,18 @@ void CPathfinderHelper::getNeighbours( // } /// Following condition let us avoid diagonal movement over coast when sailing - if(srct.terType == ETerrainType::WATER && limitCoastSailing && hlpt.terType == ETerrainType::WATER && dir.x && dir.y) //diagonal move through water + if(srct.terType.isWater() && limitCoastSailing && hlpt.terType.isWater() && dir.x && dir.y) //diagonal move through water { int3 hlp1 = tile, hlp2 = tile; hlp1.x += dir.x; hlp2.y += dir.y; - if(map->getTile(hlp1).terType != ETerrainType::WATER || map->getTile(hlp2).terType != ETerrainType::WATER) + if(map->getTile(hlp1).terType.isLand() || map->getTile(hlp2).terType.isLand()) continue; } - if(indeterminate(onLand) || onLand == (hlpt.terType != ETerrainType::WATER)) + if(indeterminate(onLand) || onLand == hlpt.terType.isLand()) { vec.push_back(hlp); } @@ -1238,7 +1232,7 @@ int CPathfinderHelper::getMovementCost( { ret = static_cast(ret * (100.0 + ti->valOfBonuses(Bonus::FLYING_MOVEMENT)) / 100.0); } - else if(dt->terType == ETerrainType::WATER) + else if(dt->terType.isWater()) { if(hero->boat && ct->hasFavorableWinds() && dt->hasFavorableWinds()) ret = static_cast(ret * 0.666); @@ -1266,7 +1260,7 @@ int CPathfinderHelper::getMovementCost( { std::vector vec; vec.reserve(8); //optimization - getNeighbours(*dt, dst, vec, ct->terType != ETerrainType::WATER, true); + getNeighbours(*dt, dst, vec, ct->terType.isLand(), true); for(auto & elem : vec) { int fcost = getMovementCost(dst, elem, nullptr, nullptr, left, false); diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 84984842a..7c5388514 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -13,6 +13,7 @@ #include "IGameCallback.h" #include "HeroBonus.h" #include "int3.h" +#include "Terrain.h" #include @@ -521,7 +522,7 @@ struct DLL_LINKAGE TurnInfo TConstBonusListPtr bonuses; mutable int maxMovePointsLand; mutable int maxMovePointsWater; - ETerrainType::EETerrainType nativeTerrain; + Terrain nativeTerrain; TurnInfo(const CGHeroInstance * Hero, const int Turn = 0); bool isLayerAvailable(const EPathfindingLayer layer) const; diff --git a/lib/CPlayerState.h b/lib/CPlayerState.h index dd2b431ca..5a2921ff0 100644 --- a/lib/CPlayerState.h +++ b/lib/CPlayerState.h @@ -67,14 +67,6 @@ public: h & dwellings; h & quests; h & visitedObjects; - - if(version < 760) - { - //was: h & getBonusList(); - BonusList junk; - h & junk; - } - h & status; h & daysWithoutCastle; h & enteredLosingCheatCode; diff --git a/lib/CSkillHandler.h b/lib/CSkillHandler.h index 425eafc38..1f93b4678 100644 --- a/lib/CSkillHandler.h +++ b/lib/CSkillHandler.h @@ -35,12 +35,9 @@ public: template void serialize(Handler & h, const int version) { h & description; - if(version >= 785) - { - h & iconSmall; - h & iconMedium; - h & iconLarge; - } + h & iconSmall; + h & iconMedium; + h & iconLarge; h & effects; } }; @@ -78,10 +75,7 @@ public: h & id; h & identifier; h & name; - if(version >= 785) - { - h & gainChance; - } + h & gainChance; h & levels; } diff --git a/lib/CStack.cpp b/lib/CStack.cpp index f1d9d3c27..64c5e98f8 100644 --- a/lib/CStack.cpp +++ b/lib/CStack.cpp @@ -32,7 +32,7 @@ CStack::CStack(const CStackInstance * Base, PlayerColor O, int I, ui8 Side, Slot slot(S), side(Side), initialPosition(), - nativeTerrain(ETerrainType::WRONG) + nativeTerrain() { health.init(); //??? } @@ -40,7 +40,7 @@ CStack::CStack(const CStackInstance * Base, PlayerColor O, int I, ui8 Side, Slot CStack::CStack() : CBonusSystemNode(STACK_BATTLE), CUnitState(), - nativeTerrain(ETerrainType::WRONG) + nativeTerrain() { base = nullptr; type = nullptr; @@ -328,11 +328,11 @@ bool CStack::canBeHealed() const bool CStack::isOnNativeTerrain() const { //this code is called from CreatureTerrainLimiter::limit on battle start - auto res = nativeTerrain == ETerrainType::ANY_TERRAIN || nativeTerrain == battle->getTerrainType(); + auto res = nativeTerrain == Terrain::ANY || nativeTerrain == battle->getTerrainType(); return res; } -bool CStack::isOnTerrain(int terrain) const +bool CStack::isOnTerrain(const Terrain & terrain) const { return battle->getTerrainType() == terrain; } diff --git a/lib/CStack.h b/lib/CStack.h index 2171a33b2..f969efab3 100644 --- a/lib/CStack.h +++ b/lib/CStack.h @@ -14,6 +14,7 @@ #include "CCreatureHandler.h" //todo: remove #include "battle/BattleHex.h" #include "mapObjects/CGHeroInstance.h" // for commander serialization +#include "Terrain.h" #include "battle/CUnitState.h" @@ -28,7 +29,7 @@ public: ui32 ID; //unique ID of stack const CCreature * type; - ETerrainType::EETerrainType nativeTerrain; //tmp variable to save native terrain value on battle init + Terrain nativeTerrain; //tmp variable to save native terrain value on battle init ui32 baseAmount; PlayerColor owner; //owner - player color (255 for neutrals) @@ -50,7 +51,7 @@ public: bool canBeHealed() const; //for first aid tent - only harmed stacks that are not war machines bool isOnNativeTerrain() const; - bool isOnTerrain(int terrain) const; + bool isOnTerrain(const Terrain & terrain) const; ui32 level() const; si32 magicResistance() const override; //include aura of resistance diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index f057997cb..bb5085f2b 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -26,6 +26,10 @@ const int NAMES_PER_TOWN=16; // number of town names per faction in H3 files. Json can define any number +const Terrain CTownHandler::defaultGoodTerrain{"grass"}; +const Terrain CTownHandler::defaultEvilTerrain{"lava"}; +const Terrain CTownHandler::defaultNeutralTerrain{"rough"}; + const std::map CBuilding::MODES = { { "normal", CBuilding::BUILD_NORMAL }, @@ -77,135 +81,11 @@ si32 CBuilding::getDistance(BuildingID buildID) const return -1; } -void CBuilding::deserializeFix() -{ - //default value for mode was broken, have to fix it here for old saves (v777 and older) - switch(mode) - { - case BUILD_NORMAL: - case BUILD_AUTO: - case BUILD_SPECIAL: - case BUILD_GRAIL: - break; - - default: - mode = BUILD_NORMAL; - break; - } -} - void CBuilding::addNewBonus(std::shared_ptr b, BonusList & bonusList) { bonusList.push_back(b); } -const JsonNode & CBuilding::getCurrentFactionForUpdateRoutine() const -{ - const auto & faction = town->faction->identifier; - const auto & factionsContent = (*VLC->modh->content)["factions"]; - const auto & coreData = factionsContent.modData.at("core"); - const auto & coreFactions = coreData.modData; - const auto & currentFaction = coreFactions[faction]; - - if(currentFaction.isNull()) - { - const auto index = faction.find(':'); - const std::string factionDir = index == std::string::npos ? faction : faction.substr(0, index); - const auto it = factionsContent.modData.find(factionDir); - - if(it == factionsContent.modData.end()) - { - logMod->warn("Warning: Update old save failed: Faction: '%s' is not found.", factionDir); - return currentFaction; - } - const std::string modFaction = index == std::string::npos ? faction : faction.substr(index + 1); - return it->second.modData[modFaction]; - } - return currentFaction; -} - -void CBuilding::update792() -{ - subId = BuildingSubID::NONE; - height = ETowerHeight::HEIGHT_NO_TOWER; - - if(!bid.IsSpecialOrGrail() || town == nullptr || town->faction == nullptr || town->faction->identifier.empty()) - return; - - const auto buildingName = CTownHandler::getMappedValue(bid, std::string(), MappedKeys::BUILDING_TYPES_TO_NAMES); - - if(buildingName.empty()) - return; - - auto & currentFaction = getCurrentFactionForUpdateRoutine(); - - if(!currentFaction.isNull() && currentFaction.getType() == JsonNode::JsonType::DATA_STRUCT) - { - const auto & buildings = currentFaction["town"]["buildings"]; - const auto & currentBuilding = buildings[buildingName]; - - subId = CTownHandler::getMappedValue(currentBuilding["type"], BuildingSubID::NONE, MappedKeys::SPECIAL_BUILDINGS); - height = subId == BuildingSubID::LOOKOUT_TOWER || bid == BuildingID::GRAIL - ? CTownHandler::getMappedValue(currentBuilding["height"], CBuilding::HEIGHT_NO_TOWER, CBuilding::TOWER_TYPES) - : height = CBuilding::HEIGHT_NO_TOWER; - } -} - -void CBuilding::update794() -{ - if(bid == BuildingID::TAVERN || subId == BuildingSubID::BROTHERHOOD_OF_SWORD) - { - VLC->townh->addBonusesForVanilaBuilding(this); - return; - } - if(!bid.IsSpecialOrGrail()) - return; - - VLC->townh->addBonusesForVanilaBuilding(this); - - if(!buildingBonuses.empty() //addBonusesForVanilaBuilding has done all work - || town->faction == nullptr //or faction data is not valid - || town->faction->identifier.empty()) - return; - - const auto buildingName = CTownHandler::getMappedValue(bid, std::string(), MappedKeys::BUILDING_TYPES_TO_NAMES, false); - - if(buildingName.empty()) - return; - - auto & currentFaction = getCurrentFactionForUpdateRoutine(); - - if(currentFaction.isNull() || currentFaction.getType() != JsonNode::JsonType::DATA_STRUCT) - return; - - const auto & buildings = currentFaction["town"]["buildings"]; - const auto & currentBuilding = buildings[buildingName]; - - CTownHandler::loadSpecialBuildingBonuses(currentBuilding["bonuses"], buildingBonuses, this); - CTownHandler::loadSpecialBuildingBonuses(currentBuilding["onVisitBonuses"], onVisitBonuses, this); - - if(!onVisitBonuses.empty()) - { - if(subId == BuildingSubID::NONE) - subId = BuildingSubID::CUSTOM_VISITING_BONUS; - - for(auto & bonus : onVisitBonuses) - bonus->sid = Bonus::getSid32(town->faction->index, bid); - } - const auto & overriddenBids = currentBuilding["overrides"]; - - if(overriddenBids.isNull()) - return; - - auto scope = town->getBuildingScope(); - - for(auto b : overriddenBids.Vector()) - { - auto bid = BuildingID(VLC->modh->identifiers.getIdentifier(scope, b).get()); - overrideBids.insert(bid); - } -} - CFaction::CFaction() { town = nullptr; @@ -1062,9 +942,9 @@ void CTownHandler::loadPuzzle(CFaction &faction, const JsonNode &source) assert(faction.puzzleMap.size() == GameConstants::PUZZLE_MAP_PIECES); } -ETerrainType::EETerrainType CTownHandler::getDefaultTerrainForAlignment(EAlignment::EAlignment alignment) const +Terrain CTownHandler::getDefaultTerrainForAlignment(EAlignment::EAlignment alignment) const { - ETerrainType::EETerrainType terrain = defaultGoodTerrain; + Terrain terrain = defaultGoodTerrain; switch(alignment) { @@ -1095,19 +975,15 @@ CFaction * CTownHandler::loadFromJson(const std::string & scope, const JsonNode faction->alignment = EAlignment::NEUTRAL; else faction->alignment = static_cast(alignment); - - auto nativeTerrain = source["nativeTerrain"]; - int terrainNum = nativeTerrain.isNull() - ? -1 - : vstd::find_pos(GameConstants::TERRAIN_NAMES, nativeTerrain.String()); auto preferUndergound = source["preferUndergroundPlacement"]; faction->preferUndergroundPlacement = preferUndergound.isNull() ? false : preferUndergound.Bool(); //Contructor is not called here, but operator= - faction->nativeTerrain = terrainNum < 0 + auto nativeTerrain = source["nativeTerrain"]; + faction->nativeTerrain = nativeTerrain.isNull() ? getDefaultTerrainForAlignment(faction->alignment) - : static_cast(terrainNum); + : Terrain(nativeTerrain.String()); if (!source["town"].isNull()) { diff --git a/lib/CTownHandler.h b/lib/CTownHandler.h index 7b38870af..7728d2992 100644 --- a/lib/CTownHandler.h +++ b/lib/CTownHandler.h @@ -20,6 +20,7 @@ #include "LogicalExpression.h" #include "battle/BattleHex.h" #include "HeroBonus.h" +#include "Terrain.h" class CLegacyConfigParser; class JsonNode; @@ -111,8 +112,6 @@ public: } void addNewBonus(std::shared_ptr b, BonusList & bonusList); - void update792(); - void update794(); template void serialize(Handler &h, const int version) { @@ -126,33 +125,14 @@ public: h & requirements; h & upgrade; h & mode; - - if(version >= 792) - { - h & subId; - h & height; - } - if(!h.saving && version < 793) - update792(); //adjust height, subId - - if(version >= 794) - { - h & overrideBids; - h & buildingBonuses; - h & onVisitBonuses; - } - else if(!h.saving) - update794(); //populate overrideBids, buildingBonuses, onVisitBonuses - - if(!h.saving) - deserializeFix(); + h & subId; + h & height; + h & overrideBids; + h & buildingBonuses; + h & onVisitBonuses; } friend class CTownHandler; - -private: - void deserializeFix(); - const JsonNode & getCurrentFactionForUpdateRoutine() const; }; /// This is structure used only by client @@ -205,7 +185,7 @@ public: TFaction index; - ETerrainType nativeTerrain; + Terrain nativeTerrain; EAlignment::EAlignment alignment; bool preferUndergroundPlacement; @@ -356,14 +336,7 @@ public: h & warMachine; h & clientInfo; h & moatDamage; - if(version >= 758) - { - h & moatHexes; - } - else if(!h.saving) - { - moatHexes = defaultMoatHexes(); - } + h & moatHexes; h & defaultTavernChance; } @@ -385,9 +358,9 @@ class DLL_LINKAGE CTownHandler : public CHandlerBase requirementsToLoad; std::vector overriddenBidsToLoad; //list of buildings, which bonuses should be overridden. - const static ETerrainType::EETerrainType defaultGoodTerrain = ETerrainType::EETerrainType::GRASS; - const static ETerrainType::EETerrainType defaultEvilTerrain = ETerrainType::EETerrainType::LAVA; - const static ETerrainType::EETerrainType defaultNeutralTerrain = ETerrainType::EETerrainType::ROUGH; + const static Terrain defaultGoodTerrain; + const static Terrain defaultEvilTerrain; + const static Terrain defaultNeutralTerrain; static TPropagatorPtr & emptyPropagator(); @@ -418,7 +391,7 @@ class DLL_LINKAGE CTownHandler : public CHandlerBase void serialize(Handler &h, const int version) { h & objects; - - if(version >= 770) - { - h & randomTown; - } - else if(!h.saving) - { - loadRandomFaction(); - } + h & randomTown; } protected: diff --git a/lib/GameConstants.cpp b/lib/GameConstants.cpp index ff39e5d82..ed73ef621 100644 --- a/lib/GameConstants.cpp +++ b/lib/GameConstants.cpp @@ -236,38 +236,6 @@ std::ostream & operator<<(std::ostream & os, const EActionType actionType) else return os << it->second; } -std::ostream & operator<<(std::ostream & os, const ETerrainType terrainType) -{ - static const std::map terrainTypeToString = - { - #define DEFINE_ELEMENT(element) {ETerrainType::element, #element} - DEFINE_ELEMENT(WRONG), - DEFINE_ELEMENT(BORDER), - DEFINE_ELEMENT(DIRT), - DEFINE_ELEMENT(SAND), - DEFINE_ELEMENT(GRASS), - DEFINE_ELEMENT(SNOW), - DEFINE_ELEMENT(SWAMP), - DEFINE_ELEMENT(ROUGH), - DEFINE_ELEMENT(SUBTERRANEAN), - DEFINE_ELEMENT(LAVA), - DEFINE_ELEMENT(WATER), - DEFINE_ELEMENT(ROCK) - #undef DEFINE_ELEMENT - }; - - auto it = terrainTypeToString.find(terrainType.num); - if (it == terrainTypeToString.end()) return os << ""; - else return os << it->second; -} - -std::string ETerrainType::toString() const -{ - std::stringstream ss; - ss << *this; - return ss.str(); -} - std::ostream & operator<<(std::ostream & os, const EPathfindingLayer pathfindingLayer) { static const std::map pathfinderLayerToString diff --git a/lib/GameConstants.h b/lib/GameConstants.h index ce0236bea..34777ad55 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -61,7 +61,6 @@ namespace GameConstants const int SKILL_QUANTITY=28; const int PRIMARY_SKILLS=4; - const int TERRAIN_TYPES=10; const int RESOURCE_QUANTITY=8; const int HEROES_PER_TYPE=8; //amount of heroes of each type @@ -678,21 +677,8 @@ enum class ETeleportChannelType }; -namespace ERiverType -{ - enum ERiverType - { - NO_RIVER, CLEAR_RIVER, ICY_RIVER, MUDDY_RIVER, LAVA_RIVER - }; -} - -namespace ERoadType -{ - enum ERoadType - { - NO_ROAD, DIRT_ROAD, GRAVEL_ROAD, COBBLESTONE_ROAD - }; -} +static std::vector RIVER_NAMES {"", "rw", "ri", "rm", "rl"}; +static std::vector ROAD_NAMES {"", "pd", "pg", "pc"}; class Obj { @@ -912,36 +898,6 @@ enum class EActionType : int32_t DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EActionType actionType); -class DLL_LINKAGE ETerrainType -{ -public: - enum EETerrainType - { - ANY_TERRAIN = -3, - WRONG = -2, BORDER = -1, DIRT, SAND, GRASS, SNOW, SWAMP, - ROUGH, SUBTERRANEAN, LAVA, WATER, ROCK // ROCK is also intended to be max value. - }; - - ETerrainType(EETerrainType _num = WRONG) : num(_num) - {} - - ETerrainType& operator=(EETerrainType _num) - { - num = _num; - return *this; - } - - ID_LIKE_CLASS_COMMON(ETerrainType, EETerrainType) - - EETerrainType num; - - std::string toString() const; -}; - -DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const ETerrainType terrainType); - -ID_LIKE_OPERATORS(ETerrainType, ETerrainType::EETerrainType) - class DLL_LINKAGE EDiggingStatus { public: diff --git a/lib/HeroBonus.cpp b/lib/HeroBonus.cpp index 5a59862cc..50eaf021f 100644 --- a/lib/HeroBonus.cpp +++ b/lib/HeroBonus.cpp @@ -2078,13 +2078,8 @@ bool CPropagatorNodeType::shouldBeAttached(CBonusSystemNode *dest) return nodeType == dest->getNodeType(); } -CreatureTerrainLimiter::CreatureTerrainLimiter(int TerrainType) - : terrainType(TerrainType) -{ -} - CreatureTerrainLimiter::CreatureTerrainLimiter() - : terrainType(-1) + : terrainType() { } @@ -2094,7 +2089,7 @@ int CreatureTerrainLimiter::limit(const BonusLimitationContext &context) const const CStack *stack = retrieveStackBattle(&context.node); if(stack) { - if(terrainType == -1)//terrainType not specified = native + if(terrainType.isNative())//terrainType not specified = native return !stack->isOnNativeTerrain(); return !stack->isOnTerrain(terrainType); } @@ -2105,7 +2100,7 @@ int CreatureTerrainLimiter::limit(const BonusLimitationContext &context) const std::string CreatureTerrainLimiter::toString() const { boost::format fmt("CreatureTerrainLimiter(terrainType=%s)"); - fmt % (terrainType >= 0 ? GameConstants::TERRAIN_NAMES[terrainType] : "native"); + fmt % (terrainType.isNative() ? "native" : static_cast(terrainType)); return fmt.str(); } @@ -2114,8 +2109,8 @@ JsonNode CreatureTerrainLimiter::toJsonNode() const JsonNode root(JsonNode::JsonType::DATA_STRUCT); root["type"].String() = "CREATURE_TERRAIN_LIMITER"; - if(terrainType >= 0) - root["parameters"].Vector().push_back(JsonUtils::stringNode(GameConstants::TERRAIN_NAMES[terrainType])); + if(!terrainType.isNative()) + root["parameters"].Vector().push_back(JsonUtils::stringNode(terrainType)); return root; } diff --git a/lib/HeroBonus.h b/lib/HeroBonus.h index 1bb218e28..28f00d05f 100644 --- a/lib/HeroBonus.h +++ b/lib/HeroBonus.h @@ -11,6 +11,7 @@ #include "GameConstants.h" #include "JsonNode.h" +#include "Terrain.h" class CCreature; struct Bonus; @@ -438,36 +439,15 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this h & val; h & sid; h & description; - if(version >= 783) - { - h & additionalInfo; - } - else - { - additionalInfo.resize(1, -1); - h & additionalInfo[0]; - } + h & additionalInfo; h & turnsRemain; h & valType; - if(version >= 784) - { - h & stacking; - } + h & stacking; h & effectRange; h & limiter; h & propagator; - if(version >= 781) - { - h & updater; - } - if(version >= 801) - { - h & propagationUpdater; - } - if(version < 801 && !h.saving) //Opposite Side bonuses are introduced - { - updateOppositeBonuses(); - } + h & updater; + h & propagationUpdater; } template @@ -999,10 +979,7 @@ public: template void serialize(Handler & h, const int version) { h & static_cast(*this); - if(version >= 786) - { - h & limiters; - } + h & limiters; } }; @@ -1081,9 +1058,9 @@ public: class DLL_LINKAGE CreatureTerrainLimiter : public ILimiter //applies only to creatures that are on specified terrain, default native terrain { public: - int terrainType; + Terrain terrainType; CreatureTerrainLimiter(); - CreatureTerrainLimiter(int TerrainType); + CreatureTerrainLimiter(const Terrain& terrain); int limit(const BonusLimitationContext &context) const override; virtual std::string toString() const override; diff --git a/lib/IGameCallback.cpp b/lib/IGameCallback.cpp index 2cc8482c2..cc75b45d0 100644 --- a/lib/IGameCallback.cpp +++ b/lib/IGameCallback.cpp @@ -49,7 +49,7 @@ void CPrivilegedInfoCallback::getFreeTiles(std::vector & tiles) const for (int yd = 0; yd < gs->map->height; yd++) { tinfo = getTile(int3 (xd,yd,zd)); - if (tinfo->terType != ETerrainType::WATER && tinfo->terType != ETerrainType::ROCK && !tinfo->blocked) //land and free + if (tinfo->terType.isLand() && tinfo->terType.isPassable() && !tinfo->blocked) //land and free tiles.push_back (int3 (xd,yd,zd)); } } @@ -116,8 +116,8 @@ void CPrivilegedInfoCallback::getAllTiles(std::unordered_set & { for (int yd = 0; yd < gs->map->height; yd++) { - if ((getTile (int3 (xd,yd,zd))->terType == ETerrainType::WATER && water) - || (getTile (int3 (xd,yd,zd))->terType != ETerrainType::WATER && land)) + if ((getTile (int3 (xd,yd,zd))->terType.isWater() && water) + || (getTile (int3 (xd,yd,zd))->terType.isLand() && land)) tiles.insert(int3(xd,yd,zd)); } } diff --git a/lib/JsonDetail.h b/lib/JsonDetail.h index 5095eabd9..89d19d5a6 100644 --- a/lib/JsonDetail.h +++ b/lib/JsonDetail.h @@ -57,7 +57,7 @@ public: }; //Internal class for string -> JsonNode conversion -class JsonParser +class DLL_LINKAGE JsonParser { std::string errors; // Contains description of all encountered errors constString input; // Input data diff --git a/lib/JsonNode.cpp b/lib/JsonNode.cpp index d8a8dbd9a..f80e8cfba 100644 --- a/lib/JsonNode.cpp +++ b/lib/JsonNode.cpp @@ -59,6 +59,15 @@ JsonNode::JsonNode(const ResourceID & fileURI): *this = parser.parse(fileURI.getName()); } +JsonNode::JsonNode(const std::string & idx, const ResourceID & fileURI): +type(JsonType::DATA_NULL) +{ + auto file = CResourceHandler::get(idx)->load(fileURI)->readAll(); + + JsonParser parser(reinterpret_cast(file.first.get()), file.second); + *this = parser.parse(fileURI.getName()); +} + JsonNode::JsonNode(ResourceID && fileURI, bool &isValidSyntax): type(JsonType::DATA_NULL) { @@ -711,7 +720,8 @@ std::shared_ptr JsonUtils::parseLimiter(const JsonNode & limiter) { VLC->modh->identifiers.requestIdentifier("terrain", parameters[0], [=](si32 terrain) { - terrainLimiter->terrainType = terrain; + //TODO: support limiters + //terrainLimiter->terrainType = terrain; }); } return terrainLimiter; diff --git a/lib/JsonNode.h b/lib/JsonNode.h index 6db74d158..e29865f49 100644 --- a/lib/JsonNode.h +++ b/lib/JsonNode.h @@ -60,6 +60,7 @@ public: //Create tree from JSON file explicit JsonNode(ResourceID && fileURI); explicit JsonNode(const ResourceID & fileURI); + explicit JsonNode(const std::string& idx, const ResourceID & fileURI); explicit JsonNode(ResourceID && fileURI, bool & isValidSyntax); //Copy c-tor JsonNode(const JsonNode ©); @@ -127,10 +128,7 @@ public: template void serialize(Handler &h, const int version) { h & meta; - if(version >= 782) - { - h & flags; - } + h & flags; h & type; switch(type) { @@ -152,10 +150,7 @@ public: h & data.Struct; break; case JsonType::DATA_INTEGER: - if(version >= 770) - { - h & data.Integer; - } + h & data.Integer; break; } } diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 249afd680..7efc87096 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -699,13 +699,13 @@ DLL_LINKAGE void GiveHero::applyGs(CGameState *gs) DLL_LINKAGE void NewObject::applyGs(CGameState *gs) { - ETerrainType terrainType; + Terrain terrainType; if(ID == Obj::BOAT && !gs->isInTheMap(pos)) //special handling for bug #3060 - pos outside map but visitablePos is not { CGObjectInstance testObject = CGObjectInstance(); testObject.pos = pos; - testObject.appearance = VLC->objtypeh->getHandlerFor(ID, subID)->getTemplates(ETerrainType::WATER).front(); + testObject.appearance = VLC->objtypeh->getHandlerFor(ID, subID)->getTemplates(Terrain("water")).front(); const int3 previousXAxisTile = int3(pos.x - 1, pos.y, pos.z); assert(gs->isInTheMap(previousXAxisTile) && (testObject.visitablePos() == previousXAxisTile)); @@ -722,7 +722,7 @@ DLL_LINKAGE void NewObject::applyGs(CGameState *gs) { case Obj::BOAT: o = new CGBoat(); - terrainType = ETerrainType::WATER; //TODO: either boat should only spawn on water, or all water objects should be handled this way + terrainType = Terrain("water"); //TODO: either boat should only spawn on water, or all water objects should be handled this way break; case Obj::MONSTER: //probably more options will be needed o = new CGCreature(); diff --git a/lib/PathfinderUtil.h b/lib/PathfinderUtil.h index dc4f9a1d5..7070d82c5 100644 --- a/lib/PathfinderUtil.h +++ b/lib/PathfinderUtil.h @@ -59,13 +59,13 @@ namespace PathfinderUtil break; case ELayer::WATER: - if(tinfo->blocked || tinfo->terType != ETerrainType::WATER) + if(tinfo->blocked || tinfo->terType.isLand()) return CGPathNode::BLOCKED; break; case ELayer::AIR: - if(tinfo->blocked || tinfo->terType == ETerrainType::WATER) + if(tinfo->blocked || tinfo->terType.isLand()) return CGPathNode::FLYABLE; break; diff --git a/lib/StartInfo.h b/lib/StartInfo.h index 913161220..1cd5bb77d 100644 --- a/lib/StartInfo.h +++ b/lib/StartInfo.h @@ -56,19 +56,7 @@ struct DLL_LINKAGE PlayerSettings h & color; h & handicap; h & name; - if(version < 787) - { - ui8 oldConnectedId = 0; - h & oldConnectedId; - if(oldConnectedId) - { - connectedPlayerIDs.insert(oldConnectedId); - } - } - else - { - h & connectedPlayerIDs; - } + h & connectedPlayerIDs; h & team; h & compOnly; } diff --git a/lib/StringConstants.h b/lib/StringConstants.h index e1dcf9e25..721a070ae 100644 --- a/lib/StringConstants.h +++ b/lib/StringConstants.h @@ -16,10 +16,6 @@ /// namespace GameConstants { - const std::string TERRAIN_NAMES [TERRAIN_TYPES] = { - "dirt", "sand", "grass", "snow", "swamp", "rough", "subterra", "lava", "water", "rock" - }; - const std::string RESOURCE_NAMES [RESOURCE_QUANTITY] = { "wood", "mercury", "ore", "sulfur", "crystal", "gems", "gold", "mithril" }; diff --git a/lib/Terrain.cpp b/lib/Terrain.cpp new file mode 100644 index 000000000..17f8de861 --- /dev/null +++ b/lib/Terrain.cpp @@ -0,0 +1,204 @@ +/* + * Terrain.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 "Terrain.h" +#include "VCMI_Lib.h" +#include "CModHandler.h" + +//regular expression to change id for string at config +//("allowedTerrain"\s*:\s*\[.*)9(.*\],\n) +//\1"rock"\2 + +const Terrain Terrain::ANY("ANY"); + +Terrain Terrain::createTerrainTypeH3M(int tId) +{ + static std::array terrainsH3M + { + "dirt", "sand", "grass", "snow", "swamp", "rough", "subterra", "lava", "water", "rock" + }; + return Terrain(terrainsH3M.at(tId)); +} + +Terrain Terrain::createTerrainByCode(const std::string & typeCode) +{ + for(const auto & terrain : Manager::terrains()) + { + if(Manager::getInfo(terrain).typeCode == typeCode) + return terrain; + } + return Terrain::ANY; +} + +Terrain::Manager::Manager() +{ + auto allConfigs = VLC->modh->getActiveMods(); + allConfigs.insert(allConfigs.begin(), "core"); + for(auto & mod : allConfigs) + { + if(!CResourceHandler::get(mod)->existsResource(ResourceID("config/terrains.json"))) + continue; + + JsonNode terrs(mod, ResourceID("config/terrains.json")); + for(auto & terr : terrs.Struct()) + { + Terrain::Info info; + info.moveCost = terr.second["moveCost"].Integer(); + const JsonVector &unblockedVec = terr.second["minimapUnblocked"].Vector(); + info.minimapUnblocked = + { + ui8(unblockedVec[0].Float()), + ui8(unblockedVec[1].Float()), + ui8(unblockedVec[2].Float()) + }; + + const JsonVector &blockedVec = terr.second["minimapBlocked"].Vector(); + info.minimapBlocked = + { + ui8(blockedVec[0].Float()), + ui8(blockedVec[1].Float()), + ui8(blockedVec[2].Float()) + }; + info.musicFilename = terr.second["music"].String(); + info.tilesFilename = terr.second["tiles"].String(); + + if(terr.second["type"].isNull()) + { + info.type = Terrain::Info::Type::Land; + } + else + { + 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(terr.second["horseSoundId"].isNull()) + { + info.horseSoundId = 9; //rock sound as default + } + else + { + info.horseSoundId = terr.second["horseSoundId"].Integer(); + } + + if(!terr.second["text"].isNull()) + { + info.terrainText = terr.second["text"].String(); + } + + if(terr.second["code"].isNull()) + { + info.typeCode = terr.first.substr(0, 2); + } + else + { + info.typeCode = terr.second["code"].String(); + assert(info.typeCode.length() == 2); + } + + + terrainInfo[Terrain(terr.first)] = info; + } + } +} + +Terrain::Manager & Terrain::Manager::get() +{ + static Terrain::Manager manager; + return manager; +} + +std::vector Terrain::Manager::terrains() +{ + std::vector _terrains; + for(const auto & info : Terrain::Manager::get().terrainInfo) + _terrains.push_back(info.first); + return _terrains; +} + +const Terrain::Info & Terrain::Manager::getInfo(const Terrain & terrain) +{ + return Terrain::Manager::get().terrainInfo.at(terrain); +} + +std::ostream & operator<<(std::ostream & os, const Terrain terrainType) +{ + return os << static_cast(terrainType); +} + +Terrain::operator std::string() const +{ + return name; +} + +Terrain::Terrain(const std::string & _name) : name(_name) +{} + +Terrain& Terrain::operator=(const Terrain & _name) +{ + name = _name.name; + return *this; +} + +Terrain& Terrain::operator=(const std::string & _name) +{ + name = _name; + return *this; +} + +bool operator==(const Terrain & l, const Terrain & r) +{ + return l.name == r.name; +} + +bool operator!=(const Terrain & l, const Terrain & r) +{ + return l.name != r.name; +} + +bool operator<(const Terrain & l, const Terrain & r) +{ + return l.name < r.name; +} + +int Terrain::id() const +{ + if(name == "ANY") return -3; + if(name == "WRONG") return -2; + if(name == "BORDER") return -1; + + auto _terrains = Terrain::Manager::terrains(); + auto iter = std::find(_terrains.begin(), _terrains.end(), *this); + return iter - _terrains.begin(); +} + +bool Terrain::isLand() const +{ + return !isWater(); +} +bool Terrain::isWater() const +{ + return Terrain::Manager::getInfo(*this).type == Terrain::Info::Type::Water; +} +bool Terrain::isPassable() const +{ + return Terrain::Manager::getInfo(*this).type != Terrain::Info::Type::Rock; +} +bool Terrain::isUnderground() const +{ + return Terrain::Manager::getInfo(*this).type == Terrain::Info::Type::Subterranean; +} +bool Terrain::isNative() const +{ + return name.empty(); +} diff --git a/lib/Terrain.h b/lib/Terrain.h new file mode 100644 index 000000000..d173f2ab7 --- /dev/null +++ b/lib/Terrain.h @@ -0,0 +1,93 @@ +/* + * Terrain.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 "ConstTransitivePtr.h" +#include "JsonNode.h" + +class DLL_LINKAGE Terrain +{ +public: + + friend class Manager; + + struct Info + { + enum class Type + { + Land, Water, Subterranean, Rock + }; + + int moveCost; + std::array minimapBlocked; + std::array minimapUnblocked; + std::string musicFilename; + std::string tilesFilename; + std::string terrainText; + std::string typeCode; + int horseSoundId; + Type type; + }; + + class DLL_LINKAGE Manager + { + public: + static std::vector terrains(); + static const Info & getInfo(const Terrain &); + + private: + static Manager & get(); + Manager(); + + std::map terrainInfo; + }; + + /*enum EETerrainType + { + ANY_TERRAIN = -3, + WRONG = -2, BORDER = -1, DIRT, SAND, GRASS, SNOW, SWAMP, + ROUGH, SUBTERRANEAN, LAVA, WATER, ROCK // ROCK is also intended to be max value. + };*/ + + Terrain(const std::string & _type = ""); + static Terrain createTerrainTypeH3M(int tId); + static Terrain createTerrainByCode(const std::string & typeCode); + + int id() const; //TODO: has to be completely removed + + Terrain& operator=(const Terrain & _type); + Terrain& operator=(const std::string & _type); + + DLL_LINKAGE friend bool operator==(const Terrain & l, const Terrain & r); + DLL_LINKAGE friend bool operator!=(const Terrain & l, const Terrain & r); + DLL_LINKAGE friend bool operator<(const Terrain & l, const Terrain & r); + + static const Terrain ANY; + + bool isLand() const; + bool isWater() const; + bool isPassable() const; //ROCK + bool isUnderground() const; + bool isNative() const; + + operator std::string() const; + + template void serialize(Handler &h, const int version) + { + h & name; + } + +protected: + + std::string name; +}; + +DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const Terrain terrainType); diff --git a/lib/VCMI_Lib.cpp b/lib/VCMI_Lib.cpp index a558dcf0c..30ba67e41 100644 --- a/lib/VCMI_Lib.cpp +++ b/lib/VCMI_Lib.cpp @@ -288,15 +288,3 @@ void LibClasses::setContent(std::shared_ptr content) { modh->content = content; } - -void LibClasses::restoreAllCreaturesNodeType794() -{ - creh->restoreAllCreaturesNodeType794(); -} - -void LibClasses::update800() -{ - vstd::clear_pointer(scriptHandler); - scriptHandler = new scripting::ScriptHandler(); -} - diff --git a/lib/VCMI_Lib.h b/lib/VCMI_Lib.h index 38b4a6bf5..e982e3cfe 100644 --- a/lib/VCMI_Lib.h +++ b/lib/VCMI_Lib.h @@ -44,7 +44,6 @@ class DLL_LINKAGE LibClasses : public Services void makeNull(); //sets all handler pointers to null std::shared_ptr getContent() const; void setContent(std::shared_ptr content); - void restoreAllCreaturesNodeType794(); public: bool IS_AI_ENABLED; //unused? @@ -91,33 +90,20 @@ public: template void serialize(Handler &h, const int version) { - if(version >= 800) + h & scriptHandler;//must be first (or second after modh), it can modify factories other handlers depends on + if(!h.saving) { - h & scriptHandler;//must be first (or second after modh), it can modify factories other handlers depends on - if(!h.saving) - { - scriptsLoaded(); - } - } - else if(!h.saving) - { - update800(); + scriptsLoaded(); } h & heroh; h & arth; h & creh; - if(!h.saving && version < 794) - restoreAllCreaturesNodeType794(); - h & townh; h & objh; h & objtypeh; h & spellh; - if(version >= 777) - { - h & skillh; - } + h & skillh; if(!h.saving) { //modh will be changed and modh->content will be empty after deserialization diff --git a/lib/battle/BattleInfo.cpp b/lib/battle/BattleInfo.cpp index 12e347cf7..bc119bde7 100644 --- a/lib/battle/BattleInfo.cpp +++ b/lib/battle/BattleInfo.cpp @@ -15,6 +15,7 @@ #include "../filesystem/Filesystem.h" #include "../mapObjects/CGTownInstance.h" #include "../CGeneralTextHandler.h" +#include "../Terrain.h" //TODO: remove #include "../IGameCallback.h" @@ -186,7 +187,7 @@ struct RangeGenerator std::function myRand; }; -BattleInfo * BattleInfo::setupBattle(int3 tile, ETerrainType terrain, BFieldType battlefieldType, const CArmedInstance * armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance * town) +BattleInfo * BattleInfo::setupBattle(int3 tile, Terrain terrain, BFieldType battlefieldType, const CArmedInstance * armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance * town) { CMP_stack cmpst; auto curB = new BattleInfo(); @@ -610,7 +611,7 @@ CStack * BattleInfo::getStack(int stackID, bool onlyAlive) BattleInfo::BattleInfo() : round(-1), activeStack(-1), town(nullptr), tile(-1,-1,-1), - battlefieldType(BFieldType::NONE), terrainType(ETerrainType::WRONG), + battlefieldType(BFieldType::NONE), terrainType(), tacticsSide(0), tacticDistance(0) { setBattle(this); @@ -644,7 +645,7 @@ BFieldType BattleInfo::getBattlefieldType() const return battlefieldType; } -ETerrainType BattleInfo::getTerrainType() const +Terrain BattleInfo::getTerrainType() const { return terrainType; } diff --git a/lib/battle/BattleInfo.h b/lib/battle/BattleInfo.h index e1e85a7da..0fc5a03b9 100644 --- a/lib/battle/BattleInfo.h +++ b/lib/battle/BattleInfo.h @@ -18,6 +18,7 @@ class CStack; class CStackInstance; class CStackBasicDescriptor; +class Terrain; class DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallback, public IBattleState { @@ -36,7 +37,7 @@ public: SiegeInfo si; BFieldType battlefieldType; //like !!BA:B - ETerrainType terrainType; //used for some stack nativity checks (not the bonus limiters though that have their own copy) + Terrain terrainType; //used for some stack nativity checks (not the bonus limiters though that have their own copy) ui8 tacticsSide; //which side is requested to play tactics phase ui8 tacticDistance; //how many hexes we can go forward (1 = only hexes adjacent to margin line) @@ -72,7 +73,7 @@ public: battle::Units getUnitsIf(battle::UnitFilter predicate) const override; BFieldType getBattlefieldType() const override; - ETerrainType getTerrainType() const override; + Terrain getTerrainType() const override; ObstacleCList getAllObstacles() const override; @@ -137,7 +138,7 @@ public: const CGHeroInstance * getHero(PlayerColor player) const; //returns fighting hero that belongs to given player void localInit(); - static BattleInfo * setupBattle(int3 tile, ETerrainType terrain, BFieldType battlefieldType, const CArmedInstance * armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance * town); + static BattleInfo * setupBattle(int3 tile, Terrain terrain, BFieldType battlefieldType, const CArmedInstance * armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance * town); ui8 whatSide(PlayerColor player) const; diff --git a/lib/battle/BattleProxy.cpp b/lib/battle/BattleProxy.cpp index 8ca8a418c..b1d533501 100644 --- a/lib/battle/BattleProxy.cpp +++ b/lib/battle/BattleProxy.cpp @@ -10,6 +10,7 @@ #include "StdInc.h" #include "BattleProxy.h" #include "Unit.h" +#include "Terrain.h" ///BattleProxy @@ -46,7 +47,7 @@ BFieldType BattleProxy::getBattlefieldType() const return subject->battleGetBattlefieldType(); } -ETerrainType BattleProxy::getTerrainType() const +Terrain BattleProxy::getTerrainType() const { return subject->battleTerrainType(); } diff --git a/lib/battle/BattleProxy.h b/lib/battle/BattleProxy.h index d55d469e9..0489d6974 100644 --- a/lib/battle/BattleProxy.h +++ b/lib/battle/BattleProxy.h @@ -30,7 +30,7 @@ public: battle::Units getUnitsIf(battle::UnitFilter predicate) const override; BFieldType getBattlefieldType() const override; - ETerrainType getTerrainType() const override; + Terrain getTerrainType() const override; ObstacleCList getAllObstacles() const override; diff --git a/lib/battle/CBattleInfoEssentials.cpp b/lib/battle/CBattleInfoEssentials.cpp index 589b93ed6..f1b1a55f0 100644 --- a/lib/battle/CBattleInfoEssentials.cpp +++ b/lib/battle/CBattleInfoEssentials.cpp @@ -14,9 +14,9 @@ #include "../NetPacks.h" #include "../mapObjects/CGTownInstance.h" -ETerrainType CBattleInfoEssentials::battleTerrainType() const +Terrain CBattleInfoEssentials::battleTerrainType() const { - RETURN_IF_NOT_BATTLE(ETerrainType::WRONG); + RETURN_IF_NOT_BATTLE(Terrain()); return getBattle()->getTerrainType(); } diff --git a/lib/battle/CBattleInfoEssentials.h b/lib/battle/CBattleInfoEssentials.h index ab8fc1d4b..8d489b998 100644 --- a/lib/battle/CBattleInfoEssentials.h +++ b/lib/battle/CBattleInfoEssentials.h @@ -46,7 +46,7 @@ public: BattlePerspective::BattlePerspective battleGetMySide() const; const IBonusBearer * getBattleNode() const; - ETerrainType battleTerrainType() const override; + Terrain battleTerrainType() const override; BFieldType battleGetBattlefieldType() const override; int32_t battleGetEnchanterCounter(ui8 side) const; diff --git a/lib/battle/IBattleInfoCallback.h b/lib/battle/IBattleInfoCallback.h index 92f50ebe1..999dbc536 100644 --- a/lib/battle/IBattleInfoCallback.h +++ b/lib/battle/IBattleInfoCallback.h @@ -14,7 +14,7 @@ struct CObstacleInstance; class BFieldType; -class ETerrainType; +class Terrain; namespace battle { @@ -34,7 +34,7 @@ class DLL_LINKAGE IBattleInfoCallback public: virtual scripting::Pool * getContextPool() const = 0; - virtual ETerrainType battleTerrainType() const = 0; + virtual Terrain battleTerrainType() const = 0; virtual BFieldType battleGetBattlefieldType() const = 0; ///return none if battle is ongoing; otherwise the victorious side (0/1) or 2 if it is a draw diff --git a/lib/battle/IBattleState.h b/lib/battle/IBattleState.h index 8aa80e4d2..2d96be75f 100644 --- a/lib/battle/IBattleState.h +++ b/lib/battle/IBattleState.h @@ -41,7 +41,7 @@ public: virtual battle::Units getUnitsIf(battle::UnitFilter predicate) const = 0; virtual BFieldType getBattlefieldType() const = 0; - virtual ETerrainType getTerrainType() const = 0; + virtual Terrain getTerrainType() const = 0; virtual ObstacleCList getAllObstacles() const = 0; diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 59ebdd789..5fe402118 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -79,28 +79,28 @@ ui32 CGHeroInstance::getTileCost(const TerrainTile & dest, const TerrainTile & f int64_t ret = GameConstants::BASE_MOVEMENT_COST; //if there is road both on dest and src tiles - use road movement cost - if(dest.roadType != ERoadType::NO_ROAD && from.roadType != ERoadType::NO_ROAD) + if(dest.roadType != ROAD_NAMES[0] && from.roadType != ROAD_NAMES[0]) { - int road = std::min(dest.roadType,from.roadType); //used road ID - switch(road) + int roadPos = std::min(vstd::find_pos(ROAD_NAMES, dest.roadType), vstd::find_pos(ROAD_NAMES, from.roadType)); //used road ID + switch(roadPos) { - case ERoadType::DIRT_ROAD: + case 1: ret = 75; break; - case ERoadType::GRAVEL_ROAD: + case 2: ret = 65; break; - case ERoadType::COBBLESTONE_ROAD: + case 3: ret = 50; break; default: - logGlobal->error("Unknown road type: %d", road); + logGlobal->error("Unknown road type: %d", roadPos); break; } } else if(ti->nativeTerrain != from.terType //the terrain is not native - && ti->nativeTerrain != ETerrainType::ANY_TERRAIN //no special creature bonus - && !ti->hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType) //no special movement bonus + && ti->nativeTerrain != Terrain::ANY //no special creature bonus + && !ti->hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType.id()) //no special movement bonus ) { static const CSelector selectorPATHFINDING = Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::PATHFINDING); @@ -114,7 +114,7 @@ ui32 CGHeroInstance::getTileCost(const TerrainTile & dest, const TerrainTile & f return (ui32)ret; } -ETerrainType::EETerrainType CGHeroInstance::getNativeTerrain() const +Terrain CGHeroInstance::getNativeTerrain() const { // NOTE: in H3 neutral stacks will ignore terrain penalty only if placed as topmost stack(s) in hero army. // This is clearly bug in H3 however intended behaviour is not clear. @@ -122,18 +122,18 @@ ETerrainType::EETerrainType CGHeroInstance::getNativeTerrain() const // will always have best penalty without any influence from player-defined stacks order // TODO: What should we do if all hero stacks are neutral creatures? - ETerrainType::EETerrainType nativeTerrain = ETerrainType::BORDER; + Terrain nativeTerrain("BORDER"); for(auto stack : stacks) { - ETerrainType::EETerrainType stackNativeTerrain = stack.second->type->getNativeTerrain(); //consider terrain bonuses e.g. Lodestar. + Terrain stackNativeTerrain = stack.second->type->getNativeTerrain(); //consider terrain bonuses e.g. Lodestar. - if(stackNativeTerrain == ETerrainType::BORDER) + if(stackNativeTerrain == Terrain("BORDER")) continue; - if(nativeTerrain == ETerrainType::BORDER) + if(nativeTerrain == Terrain("BORDER")) nativeTerrain = stackNativeTerrain; else if(nativeTerrain != stackNativeTerrain) - return ETerrainType::BORDER; + return Terrain("BORDER"); } return nativeTerrain; } @@ -560,23 +560,6 @@ void CGHeroInstance::recreateSecondarySkillsBonuses() updateSkillBonus(SecondarySkill(skill_info.first), skill_info.second); } -void CGHeroInstance::recreateSpecialtyBonuses(std::vector & specialtyDeprecated) -{ - auto HeroSpecialToSpecialtyBonus = [](HeroSpecial & hs) -> SSpecialtyBonus - { - SSpecialtyBonus sb; - sb.growsWithLevel = hs.growsWithLevel; - sb.bonuses = hs.getBonusList(); - return sb; - }; - - for(HeroSpecial * hs : specialtyDeprecated) - { - for(std::shared_ptr b : SpecialtyBonusToBonuses(HeroSpecialToSpecialtyBonus(*hs), type->ID.getNum())) - addNewBonus(b); - } -} - void CGHeroInstance::updateSkillBonus(SecondarySkill which, int val) { removeBonuses(Selector::source(Bonus::SECONDARY_SKILL, which)); diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index 2071d3f5e..02b738c68 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -1,4 +1,4 @@ -/* +/* * CGHeroInstance.h, part of VCMI engine * * Authors: listed in file AUTHORS in main folder @@ -89,15 +89,7 @@ public: template void serialize(Handler &h, const int version) { h & patrolling; - if(version >= 755) //save format backward compatibility - { - h & initialPos; - } - else if(!h.saving) - { - patrolling = false; - initialPos = int3(); - } + h & initialPos; h & patrolRadius; } } patrol; @@ -163,7 +155,7 @@ public: bool needsLastStack()const override; ui32 getTileCost(const TerrainTile &dest, const TerrainTile &from, const TurnInfo * ti) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling - ETerrainType::EETerrainType getNativeTerrain() const; + Terrain getNativeTerrain() const; ui32 getLowestCreatureSpeed() const; int3 getPosition(bool h3m = false) const; //h3m=true - returns position of hero object; h3m=false - returns position of hero 'manifestation' si32 manaRegain() const; //how many points of mana can hero regain "naturally" in one day @@ -287,7 +279,6 @@ protected: private: void levelUpAutomatically(CRandomGenerator & rand); - void recreateSpecialtyBonuses(std::vector & specialtyDeprecated); public: std::string getHeroTypeName() const; @@ -316,18 +307,8 @@ public: h & visitedTown; h & boat; h & type; - if(version < 781) - { - std::vector specialtyDeprecated; - h & specialtyDeprecated; - if(!h.saving) - recreateSpecialtyBonuses(specialtyDeprecated); - } h & commander; h & visitedObjects; BONUS_TREE_DESERIALIZATION_FIX - //visitied town pointer will be restored by map serialization method - if(version < 777 && !h.saving) - recreateSecondarySkillsBonuses(); } }; diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 290be028e..843f21326 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -796,28 +796,6 @@ void CGTownInstance::addTownBonuses() } } -void CGTownInstance::fixBonusingDuplicates() //For versions 794-800 -{ - std::map bids; - - for(auto i = 0; i != bonusingBuildings.size(); i++) - { - auto bid = bonusingBuildings[i]->getBuildingType(); - if(!bids.count(bid)) - bids.insert({ bid, 0 }); - else - bids[bid]++; - } - for(auto & pair : bids) - { - if(!pair.second) - continue; - - for(auto i = 0; i < pair.second; i++) - deleteTownBonus(pair.first); - } -} - void CGTownInstance::deleteTownBonus(BuildingID::EBuildingID bid) { size_t i = 0; @@ -875,90 +853,6 @@ void CGTownInstance::initObj(CRandomGenerator & rand) ///initialize town structu updateAppearance(); } -void CGTownInstance::updateBonusingBuildings() //update to version 792 -{ - if(this->town->faction != nullptr) - { - //firstly, update subtype for the Bonusing objects, which are already stored in the bonusing list - for(auto building : bonusingBuildings) //no garrison bonuses here, only week and visiting bonuses - { - switch(this->town->faction->index) - { - case ETownType::CASTLE: - building->setBuildingSubtype(BuildingSubID::STABLES); - break; - - case ETownType::DUNGEON: - if(building->getBuildingType() == BuildingID::SPECIAL_2) - building->setBuildingSubtype(BuildingSubID::MANA_VORTEX); - else if(building->getBuildingType() == BuildingID::SPECIAL_4) - building->setBuildingSubtype(BuildingSubID::EXPERIENCE_VISITING_BONUS); - break; - - case ETownType::TOWER: - building->setBuildingSubtype(BuildingSubID::KNOWLEDGE_VISITING_BONUS); - break; - - case ETownType::STRONGHOLD: - building->setBuildingSubtype(BuildingSubID::ATTACK_VISITING_BONUS); - break; - - case ETownType::INFERNO: - building->setBuildingSubtype(BuildingSubID::SPELL_POWER_VISITING_BONUS); - break; - - case ETownType::FORTRESS: - building->setBuildingSubtype(BuildingSubID::DEFENSE_VISITING_BONUS); - break; - } - } - } - //secondly, supplement bonusing buildings list and active bonuses; subtypes for these objects are already set in update792 - for(auto & kvp : town->buildings) - { - auto & building = kvp.second; - - if(building->subId == BuildingSubID::PORTAL_OF_SUMMONING) - { - if(!hasBuiltInOldWay(ETownType::DUNGEON, BuildingID::PORTAL_OF_SUMMON)) - creatures.resize(GameConstants::CREATURES_PER_TOWN + 1); - continue; - } - if(!building->IsVisitingBonus() && !building->IsWeekBonus()) //it's not bonusing => nothing to handle - continue; - - if(getBonusingBuilding(building->subId) != nullptr) //it's already added => already handled - continue; - - ///'hasBuilt' checking for bonuses is in the onHeroVisit handler - if(building->IsWeekBonus()) - tryAddOnePerWeekBonus(building->subId); - - if(building->IsVisitingBonus()) - tryAddVisitingBonus(building->subId); - } - recreateBuildingsBonuses(); ///Clear all bonuses and recreate -} - -void CGTownInstance::updateTown794() -{ - for(auto builtBuilding : builtBuildings) - { - auto building = town->buildings.at(builtBuilding); - - for(auto overriddenBid : building->overrideBids) - overriddenBuildings.insert(overriddenBid); - } - for(auto & kvp : town->buildings) - { - auto & building = kvp.second; - //The building acts as a visiting bonus and it has not been overridden. - if(building->IsVisitingBonus() && overriddenBuildings.find(kvp.first) == overriddenBuildings.end()) - tryAddVisitingBonus(building->subId); - } - recreateBuildingsBonuses(); -} - bool CGTownInstance::hasBuiltInOldWay(ETownType::ETownType type, BuildingID bid) const { return (this->town->faction != nullptr && this->town->faction->index == type && hasBuilt(bid)); diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index 66c1a48c5..5a0153f9c 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -130,9 +130,7 @@ public: { h & bID; h & indexOnTV; - - if(version >= 792) - h & bType; + h & bType; } protected: @@ -267,16 +265,7 @@ public: return false; }); - if(!h.saving && version < 793) - updateBonusingBuildings(); - - if(version >= 794) - h & overriddenBuildings; - else if(!h.saving) - updateTown794(); - - if(!h.saving && (version >= 794 && version < 801)) - fixBonusingDuplicates(); + h & overriddenBuildings; if(!h.saving) this->setNodeType(CBonusSystemNode::TOWN); @@ -367,7 +356,6 @@ private: void setOwner(const PlayerColor owner) const; void onTownCaptured(const PlayerColor winner) const; int getDwellingBonus(const std::vector& creatureIds, const std::vector >& dwellings) const; - void updateBonusingBuildings(); bool hasBuiltInOldWay(ETownType::ETownType type, BuildingID bid) const; bool townEnvisagesBuilding(BuildingSubID::EBuildingSubID bid) const; bool isBonusingBuildingAdded(BuildingID::EBuildingID bid) const; @@ -375,6 +363,4 @@ private: void tryAddVisitingBonus(BuildingSubID::EBuildingSubID subID); void initOverriddenBids(); void addTownBonuses(); - void updateTown794(); //populate overriddenBuildings and vanila bonuses for old saves - void fixBonusingDuplicates(); //For versions 794-800. }; diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index a2cd7d555..c7944b940 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -1,4 +1,4 @@ -/* +/* * CObjectClassesHandler.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder @@ -195,7 +195,7 @@ void CObjectClassesHandler::loadObjectEntry(const std::string & identifier, cons else handler->init(entry); - if (handler->getTemplates().empty()) + //if (handler->getTemplates().empty()) { auto range = legacyTemplates.equal_range(std::make_pair(obj->id, id)); for (auto & templ : boost::make_iterator_range(range.first, range.second)) @@ -569,14 +569,14 @@ std::vector AObjectTypeHandler::getTemplates() const return templates; } -std::vector AObjectTypeHandler::getTemplates(si32 terrainType) const// FIXME: replace with ETerrainType +std::vector AObjectTypeHandler::getTemplates(const Terrain & terrainType) const { std::vector templates = getTemplates(); std::vector filtered; std::copy_if(templates.begin(), templates.end(), std::back_inserter(filtered), [&](const ObjectTemplate & obj) { - return obj.canBePlacedAt(ETerrainType(terrainType)); + return obj.canBePlacedAt(terrainType); }); // H3 defines allowed terrains in a weird way - artifacts, monsters and resources have faulty masks here // Perhaps we should re-define faulty templates and remove this workaround (already done for resources) @@ -586,7 +586,7 @@ std::vector AObjectTypeHandler::getTemplates(si32 terrainType) c return filtered; } -boost::optional AObjectTypeHandler::getOverride(si32 terrainType, const CGObjectInstance * object) const +boost::optional AObjectTypeHandler::getOverride(const Terrain & terrainType, const CGObjectInstance * object) const { std::vector ret = getTemplates(terrainType); for (auto & tmpl : ret) diff --git a/lib/mapObjects/CObjectClassesHandler.h b/lib/mapObjects/CObjectClassesHandler.h index 89647230a..cc737fc99 100644 --- a/lib/mapObjects/CObjectClassesHandler.h +++ b/lib/mapObjects/CObjectClassesHandler.h @@ -178,11 +178,11 @@ public: /// returns all templates matching parameters std::vector getTemplates() const; - std::vector getTemplates(si32 terrainType) const; + std::vector getTemplates(const Terrain & terrainType) const; /// returns preferred template for this object, if present (e.g. one of 3 possible templates for town - village, fort and castle) /// note that appearance will not be changed - this must be done separately (either by assignment or via pack from server) - boost::optional getOverride(si32 terrainType, const CGObjectInstance * object) const; + boost::optional getOverride(const Terrain & terrainType, const CGObjectInstance * object) const; const RandomMapInfo & getRMGInfo(); @@ -210,19 +210,10 @@ public: h & templates; h & rmgInfo; h & objectName; - if(version >= 759) - { - h & typeName; - h & subTypeName; - } - if(version >= 778) - { - h & sounds; - } - if(version >= 789) - { - h & aiValue; - } + h & typeName; + h & subTypeName; + h & sounds; + h & aiValue; } }; @@ -253,19 +244,10 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase h & handlerName; h & base; h & subObjects; - if(version >= 759) - { - h & identifier; - h & subIds; - } - if(version >= 778) - { - h & sounds; - } - if(version >= 789) - { - h & groupDefaultAiValue; - } + h & identifier; + h & subIds; + h & sounds; + h & groupDefaultAiValue; } }; diff --git a/lib/mapObjects/CObjectHandler.cpp b/lib/mapObjects/CObjectHandler.cpp index 411296ea9..7a0f84d64 100644 --- a/lib/mapObjects/CObjectHandler.cpp +++ b/lib/mapObjects/CObjectHandler.cpp @@ -419,7 +419,7 @@ int3 IBoatGenerator::bestLocation() const { if(const TerrainTile *tile = IObjectInterface::cb->getTile(o->pos + offset, false)) //tile is in the map { - if(tile->terType == ETerrainType::WATER && (!tile->blocked || tile->blockingObjects.front()->ID == Obj::BOAT)) //and is water and is not blocked or is blocked by boat + if(tile->terType.isWater() && (!tile->blocked || tile->blockingObjects.front()->ID == Obj::BOAT)) //and is water and is not blocked or is blocked by boat return o->pos + offset; } } diff --git a/lib/mapObjects/CObjectHandler.h b/lib/mapObjects/CObjectHandler.h index 42f46bfe9..2897606cc 100644 --- a/lib/mapObjects/CObjectHandler.h +++ b/lib/mapObjects/CObjectHandler.h @@ -213,13 +213,9 @@ public: ///Entry point of binary (de-)serialization template void serialize(Handler &h, const int version) { - if(version >= 759) - { - h & instanceName; - h & typeName; - h & subTypeName; - } - + h & instanceName; + h & typeName; + h & subTypeName; h & pos; h & ID; h & subID; diff --git a/lib/mapObjects/CQuest.h b/lib/mapObjects/CQuest.h index 0e46d45c1..3fff59254 100644 --- a/lib/mapObjects/CQuest.h +++ b/lib/mapObjects/CQuest.h @@ -87,14 +87,7 @@ public: h & isCustomFirst; h & isCustomNext; h & isCustomComplete; - if(version >= 757) - { - h & completedOption; - } - else if(!h.saving) - { - completedOption = 1; - } + h & completedOption; } void serializeJson(JsonSerializeFormat & handler, const std::string & fieldName); diff --git a/lib/mapObjects/CRewardableObject.h b/lib/mapObjects/CRewardableObject.h index 7dc2b4556..223f6a03f 100644 --- a/lib/mapObjects/CRewardableObject.h +++ b/lib/mapObjects/CRewardableObject.h @@ -269,11 +269,6 @@ public: h & onVisited; h & onEmpty; h & visitMode; - if(version < 778) - { - ui16 soundID = 0; - h & soundID; - } h & selectMode; h & selectedReward; } diff --git a/lib/mapObjects/ObjectTemplate.cpp b/lib/mapObjects/ObjectTemplate.cpp index 5fd9d24a0..deaa1f63a 100644 --- a/lib/mapObjects/ObjectTemplate.cpp +++ b/lib/mapObjects/ObjectTemplate.cpp @@ -19,6 +19,7 @@ #include "CObjectHandler.h" #include "../CModHandler.h" #include "../JsonNode.h" +#include "../Terrain.h" #include "CRewardableConstructor.h" @@ -143,7 +144,17 @@ void ObjectTemplate::readTxt(CLegacyConfigParser & parser) for (size_t i=0; i<9; i++) { if (terrStr[8-i] == '1') - allowedTerrains.insert(ETerrainType((si32)i)); + allowedTerrains.insert(Terrain::createTerrainTypeH3M(i)); + } + + //assuming that object can be placed on other land terrains + if(allowedTerrains.size() >= 8 && !allowedTerrains.count(Terrain("water"))) + { + for(auto & terrain : Terrain::Manager::terrains()) + { + if(terrain.isLand() && terrain.isPassable()) + allowedTerrains.insert(terrain); + } } id = Obj(boost::lexical_cast(strings[5])); @@ -205,7 +216,17 @@ void ObjectTemplate::readMap(CBinaryReader & reader) for (size_t i=0; i<9; i++) { if (((terrMask >> i) & 1 ) != 0) - allowedTerrains.insert(ETerrainType((si32)i)); + allowedTerrains.insert(Terrain::createTerrainTypeH3M(i)); + } + + //assuming that object can be placed on other land terrains + if(allowedTerrains.size() >= 8 && !allowedTerrains.count(Terrain("water"))) + { + for(auto & terrain : Terrain::Manager::terrains()) + { + if(terrain.isLand() && terrain.isPassable()) + allowedTerrains.insert(terrain); + } } id = Obj(reader.readUInt32()); @@ -247,15 +268,16 @@ void ObjectTemplate::readJson(const JsonNode &node, const bool withTerrain) if(withTerrain && !node["allowedTerrains"].isNull()) { for (auto & entry : node["allowedTerrains"].Vector()) - allowedTerrains.insert(ETerrainType(vstd::find_pos(GameConstants::TERRAIN_NAMES, entry.String()))); + allowedTerrains.insert(entry.String()); } else { - for (size_t i=0; i< GameConstants::TERRAIN_TYPES; i++) - allowedTerrains.insert(ETerrainType((si32)i)); - - allowedTerrains.erase(ETerrainType::ROCK); - allowedTerrains.erase(ETerrainType::WATER); + for(auto & i : Terrain::Manager::terrains()) + { + if(!i.isPassable() || i.isWater()) + continue; + allowedTerrains.insert(i); + } } if(withTerrain && allowedTerrains.empty()) @@ -329,14 +351,14 @@ void ObjectTemplate::writeJson(JsonNode & node, const bool withTerrain) const if(withTerrain) { //assumed that ROCK and WATER terrains are not included - if(allowedTerrains.size() < (GameConstants::TERRAIN_TYPES - 2)) + if(allowedTerrains.size() < (Terrain::Manager::terrains().size() - 2)) { JsonVector & data = node["allowedTerrains"].Vector(); for(auto type : allowedTerrains) { JsonNode value(JsonNode::JsonType::DATA_STRING); - value.String() = GameConstants::TERRAIN_NAMES[type.num]; + value.String() = type; data.push_back(value); } } @@ -511,7 +533,7 @@ bool ObjectTemplate::isVisitableFromTop() const //return isVisitableFrom (0, 1); } -bool ObjectTemplate::canBePlacedAt(ETerrainType terrain) const +bool ObjectTemplate::canBePlacedAt(Terrain terrain) const { return allowedTerrains.count(terrain) != 0; } diff --git a/lib/mapObjects/ObjectTemplate.h b/lib/mapObjects/ObjectTemplate.h index 723511979..080561f2d 100644 --- a/lib/mapObjects/ObjectTemplate.h +++ b/lib/mapObjects/ObjectTemplate.h @@ -15,6 +15,7 @@ class CBinaryReader; class CLegacyConfigParser; class JsonNode; class int3; +class Terrain; class DLL_LINKAGE ObjectTemplate { @@ -30,7 +31,7 @@ class DLL_LINKAGE ObjectTemplate /// directions from which object can be entered, format same as for moveDir in CGHeroInstance(but 0 - 7) ui8 visitDir; /// list of terrains on which this object can be placed - std::set allowedTerrains; + std::set allowedTerrains; void afterLoadFixup(); @@ -70,7 +71,7 @@ public: bool isVisitableFromTop() const; // Checks if object can be placed on specific terrain - bool canBePlacedAt(ETerrainType terrain) const; + bool canBePlacedAt(Terrain terrain) const; ObjectTemplate(); //custom copy constructor is required @@ -96,10 +97,7 @@ public: h & subid; h & printPriority; h & visitDir; - if(version >= 770) - { - h & editorAnimationFile; - } + h & editorAnimationFile; } }; diff --git a/lib/mapping/CCampaignHandler.cpp b/lib/mapping/CCampaignHandler.cpp index b86e5052d..71c130383 100644 --- a/lib/mapping/CCampaignHandler.cpp +++ b/lib/mapping/CCampaignHandler.cpp @@ -422,17 +422,6 @@ std::vector CCampaignScenario::getLostCrossoverHeroes() return lostCrossoverHeroes; } -std::vector CCampaignScenario::update787(std::vector & heroes) -{ - static_assert(MINIMAL_SERIALIZATION_VERSION < 787, "No longer needed CCampaignScenario::update787"); - std::vector heroesNew; - for(auto hero : heroes) - { - heroesNew.push_back(CCampaignState::crossoverSerialize(hero)); - } - return heroesNew; -} - void CCampaignState::setCurrentMapAsConquered(const std::vector & heroes) { camp->scenarios[*currentMap].crossoverHeroes.clear(); diff --git a/lib/mapping/CCampaignHandler.h b/lib/mapping/CCampaignHandler.h index d977881d9..ea1dd6f7f 100644 --- a/lib/mapping/CCampaignHandler.h +++ b/lib/mapping/CCampaignHandler.h @@ -146,8 +146,7 @@ public: // FIXME: due to usage of JsonNode I can't make these methods const const CGHeroInstance * strongestHero(PlayerColor owner); std::vector getLostCrossoverHeroes(); /// returns a list of crossover heroes which started the scenario, but didn't complete it - std::vector update787(std::vector & heroes); - + CCampaignScenario(); template void serialize(Handler &h, const int formatVersion) @@ -163,19 +162,8 @@ public: h & prolog; h & epilog; h & travelOptions; - if(formatVersion < 787) - { - std::vector crossoverHeroesOld, placedCrossoverHeroesOld; - h & crossoverHeroesOld; - h & placedCrossoverHeroesOld; - crossoverHeroes = update787(crossoverHeroesOld); - placedCrossoverHeroes = update787(placedCrossoverHeroesOld); - } - else - { - h & crossoverHeroes; - h & placedCrossoverHeroes; - } + h & crossoverHeroes; + h & placedCrossoverHeroes; h & keepHeroes; } }; diff --git a/lib/mapping/CDrawRoadsOperation.cpp b/lib/mapping/CDrawRoadsOperation.cpp index b39f8418c..ea7398a4f 100644 --- a/lib/mapping/CDrawRoadsOperation.cpp +++ b/lib/mapping/CDrawRoadsOperation.cpp @@ -148,7 +148,7 @@ static bool ruleIsAny(const std::string & rule) #endif ///CDrawRoadsOperation -CDrawRoadsOperation::CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, ERoadType::ERoadType roadType, CRandomGenerator * gen): +CDrawRoadsOperation::CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, const std::string & roadType, CRandomGenerator * gen): CMapOperation(map),terrainSel(terrainSel), roadType(roadType), gen(gen) { @@ -225,7 +225,7 @@ void CDrawRoadsOperation::flipPattern(RoadPattern& pattern, int flip) const bool CDrawRoadsOperation::needUpdateTile(const TerrainTile & tile) const { - return tile.roadType != ERoadType::NO_ROAD; //TODO: this method should be virtual for river support + return tile.roadType != ROAD_NAMES[0]; //TODO: this method should be virtual for river support } void CDrawRoadsOperation::updateTiles(std::set & invalidated) @@ -263,7 +263,7 @@ bool CDrawRoadsOperation::tileHasSomething(const int3& pos) const { //TODO: this method should be virtual for river support - return map->getTile(pos).roadType != ERoadType::NO_ROAD; + return map->getTile(pos).roadType != ROAD_NAMES[0]; } diff --git a/lib/mapping/CDrawRoadsOperation.h b/lib/mapping/CDrawRoadsOperation.h index de74a0c23..3b7c98ed8 100644 --- a/lib/mapping/CDrawRoadsOperation.h +++ b/lib/mapping/CDrawRoadsOperation.h @@ -18,7 +18,7 @@ struct TerrainTile; class CDrawRoadsOperation : public CMapOperation { public: - CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, ERoadType::ERoadType roadType, CRandomGenerator * gen); + CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, const std::string & roadType, CRandomGenerator * gen); void execute() override; void undo() override; void redo() override; @@ -53,6 +53,6 @@ private: bool tileHasSomething(const int3 & pos) const; CTerrainSelection terrainSel; - ERoadType::ERoadType roadType; + std::string roadType; CRandomGenerator * gen; }; diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index 014745e3c..749a2e5b0 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -123,8 +123,8 @@ CCastleEvent::CCastleEvent() : town(nullptr) } -TerrainTile::TerrainTile() : terType(ETerrainType::BORDER), terView(0), riverType(ERiverType::NO_RIVER), - riverDir(0), roadType(ERoadType::NO_ROAD), roadDir(0), extTileFlags(0), visitable(false), +TerrainTile::TerrainTile() : terType("BORDER"), terView(0), riverType(RIVER_NAMES[0]), + riverDir(0), roadType(ROAD_NAMES[0]), roadDir(0), extTileFlags(0), visitable(false), blocked(false) { @@ -132,13 +132,13 @@ TerrainTile::TerrainTile() : terType(ETerrainType::BORDER), terView(0), riverTyp bool TerrainTile::entrableTerrain(const TerrainTile * from) const { - return entrableTerrain(from ? from->terType != ETerrainType::WATER : true, from ? from->terType == ETerrainType::WATER : true); + return entrableTerrain(from ? from->terType.isLand() : true, from ? from->terType.isWater() : true); } bool TerrainTile::entrableTerrain(bool allowLand, bool allowSea) const { - return terType != ETerrainType::ROCK - && ((allowSea && terType == ETerrainType::WATER) || (allowLand && terType != ETerrainType::WATER)); + return terType.isPassable() + && ((allowSea && terType.isWater()) || (allowLand && terType.isLand())); } bool TerrainTile::isClear(const TerrainTile * from) const @@ -164,7 +164,7 @@ CGObjectInstance * TerrainTile::topVisitableObj(bool excludeTop) const EDiggingStatus TerrainTile::getDiggingStatus(const bool excludeTop) const { - if(terType == ETerrainType::WATER || terType == ETerrainType::ROCK) + if(terType.isWater() || !terType.isPassable()) return EDiggingStatus::WRONG_TERRAIN; int allowedBlocked = excludeTop ? 1 : 0; @@ -181,7 +181,7 @@ bool TerrainTile::hasFavorableWinds() const bool TerrainTile::isWater() const { - return terType == ETerrainType::WATER; + return terType.isWater(); } void CMapHeader::setupEvents() diff --git a/lib/mapping/CMap.h b/lib/mapping/CMap.h index 7ff0fc5de..c13bb4e93 100644 --- a/lib/mapping/CMap.h +++ b/lib/mapping/CMap.h @@ -110,11 +110,7 @@ struct DLL_LINKAGE PlayerInfo h & posOfMainTown; h & team; h & generateHero; - - if(version >= 770) - { - h & mainHeroInstance; - } + h & mainHeroInstance; } }; @@ -164,16 +160,9 @@ struct DLL_LINKAGE EventCondition h & objectType; h & position; h & condition; - //(!!!) should be `version >= 759` here, but do not try to "fix" it - if(version > 759) - { - h & objectSubtype; - h & objectInstanceName; - } - if(version >= 770) - { - h & metaType; - } + h & objectSubtype; + h & objectInstanceName; + h & metaType; } }; @@ -495,9 +484,6 @@ public: h & CGTownInstance::merchantArtifacts; h & CGTownInstance::universitySkills; - if(formatVersion >= 759) - { - h & instanceNames; - } + h & instanceNames; } }; diff --git a/lib/mapping/CMapDefines.h b/lib/mapping/CMapDefines.h index 9514dcbb6..3016f968f 100644 --- a/lib/mapping/CMapDefines.h +++ b/lib/mapping/CMapDefines.h @@ -80,11 +80,11 @@ struct DLL_LINKAGE TerrainTile EDiggingStatus getDiggingStatus(const bool excludeTop = true) const; bool hasFavorableWinds() const; - ETerrainType terType; + Terrain terType; ui8 terView; - ERiverType::ERiverType riverType; + std::string riverType; ui8 riverDir; - ERoadType::ERoadType roadType; + std::string roadType; ui8 roadDir; /// first two bits - how to rotate terrain graphic (next two - river graphic, next two - road); /// 7th bit - whether tile is coastal (allows disembarking if land or block movement if water); 8th bit - Favorable Winds effect diff --git a/lib/mapping/CMapEditManager.cpp b/lib/mapping/CMapEditManager.cpp index fd632fb87..d23b1100d 100644 --- a/lib/mapping/CMapEditManager.cpp +++ b/lib/mapping/CMapEditManager.cpp @@ -243,13 +243,13 @@ void CMapEditManager::clearTerrain(CRandomGenerator * gen) execute(make_unique(map, gen ? gen : &(this->gen))); } -void CMapEditManager::drawTerrain(ETerrainType terType, CRandomGenerator * gen) +void CMapEditManager::drawTerrain(Terrain terType, CRandomGenerator * gen) { execute(make_unique(map, terrainSel, terType, gen ? gen : &(this->gen))); terrainSel.clearSelection(); } -void CMapEditManager::drawRoad(ERoadType::ERoadType roadType, CRandomGenerator* gen) +void CMapEditManager::drawRoad(const std::string & roadType, CRandomGenerator* gen) { execute(make_unique(map, terrainSel, roadType, gen ? gen : &(this->gen))); terrainSel.clearSelection(); @@ -534,7 +534,7 @@ void CTerrainViewPatternConfig::flipPattern(TerrainViewPattern & pattern, int fl } -CDrawTerrainOperation::CDrawTerrainOperation(CMap * map, const CTerrainSelection & terrainSel, ETerrainType terType, CRandomGenerator * gen) +CDrawTerrainOperation::CDrawTerrainOperation(CMap * map, const CTerrainSelection & terrainSel, Terrain terType, CRandomGenerator * gen) : CMapOperation(map), terrainSel(terrainSel), terType(terType), gen(gen) { @@ -760,21 +760,17 @@ void CDrawTerrainOperation::updateTerrainViews() } } -ETerrainGroup::ETerrainGroup CDrawTerrainOperation::getTerrainGroup(ETerrainType terType) const +ETerrainGroup::ETerrainGroup CDrawTerrainOperation::getTerrainGroup(Terrain terType) const { - switch(terType) - { - case ETerrainType::DIRT: + if(terType == Terrain("dirt")) return ETerrainGroup::DIRT; - case ETerrainType::SAND: + if(terType == Terrain("sand")) return ETerrainGroup::SAND; - case ETerrainType::WATER: + if(terType.isWater()) return ETerrainGroup::WATER; - case ETerrainType::ROCK: + if(!terType.isPassable()) return ETerrainGroup::ROCK; - default: - return ETerrainGroup::NORMAL; - } + return ETerrainGroup::NORMAL; } CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainView(const int3 & pos, const std::vector * pattern, int recDepth) const @@ -811,7 +807,7 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi int cy = pos.y + (i / 3) - 1; int3 currentPos(cx, cy, pos.z); bool isAlien = false; - ETerrainType terType; + Terrain terType; if(!map->isInTheMap(currentPos)) { // position is not in the map, so take the ter type from the neighbor tile @@ -949,17 +945,11 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi } } -bool CDrawTerrainOperation::isSandType(ETerrainType terType) const +bool CDrawTerrainOperation::isSandType(Terrain terType) const { - switch(terType) - { - case ETerrainType::WATER: - case ETerrainType::SAND: - case ETerrainType::ROCK: + if(terType.isWater() || terType == Terrain("sand") || !terType.isPassable()) return true; - default: - return false; - } + return false; } void CDrawTerrainOperation::invalidateTerrainViews(const int3 & centerPos) @@ -986,7 +976,7 @@ CDrawTerrainOperation::InvalidTiles CDrawTerrainOperation::getInvalidTiles(const auto valid = validateTerrainView(pos, ptrConfig->getTerrainTypePatternById("n1")).result; // Special validity check for rock & water - if(valid && (terType == ETerrainType::WATER || terType == ETerrainType::ROCK)) + if(valid && (terType.isWater() || !terType.isPassable())) { static const std::string patternIds[] = { "s1", "s2" }; for(auto & patternId : patternIds) @@ -996,7 +986,7 @@ CDrawTerrainOperation::InvalidTiles CDrawTerrainOperation::getInvalidTiles(const } } // Additional validity check for non rock OR water - else if(!valid && (terType != ETerrainType::WATER && terType != ETerrainType::ROCK)) + else if(!valid && (terType.isLand() && terType.isPassable())) { static const std::string patternIds[] = { "n2", "n3" }; for(auto & patternId : patternIds) @@ -1040,7 +1030,7 @@ void CTerrainViewPatternUtils::printDebuggingInfoAboutTile(const CMap * map, int { auto debugTile = map->getTile(debugPos); - std::string terType = debugTile.terType.toString().substr(0, 6); + std::string terType = static_cast(debugTile.terType).substr(0, 6); line += terType; line.insert(line.end(), PADDED_LENGTH - terType.size(), ' '); } @@ -1059,12 +1049,12 @@ CClearTerrainOperation::CClearTerrainOperation(CMap * map, CRandomGenerator * ge { CTerrainSelection terrainSel(map); terrainSel.selectRange(MapRect(int3(0, 0, 0), map->width, map->height)); - addOperation(make_unique(map, terrainSel, ETerrainType::WATER, gen)); + addOperation(make_unique(map, terrainSel, Terrain("water"), gen)); if(map->twoLevel) { terrainSel.clearSelection(); terrainSel.selectRange(MapRect(int3(0, 0, 1), map->width, map->height)); - addOperation(make_unique(map, terrainSel, ETerrainType::ROCK, gen)); + addOperation(make_unique(map, terrainSel, Terrain("rock"), gen)); } } diff --git a/lib/mapping/CMapEditManager.h b/lib/mapping/CMapEditManager.h index 96cad1557..5e18cf969 100644 --- a/lib/mapping/CMapEditManager.h +++ b/lib/mapping/CMapEditManager.h @@ -13,6 +13,7 @@ #include "../CRandomGenerator.h" #include "../int3.h" #include "../GameConstants.h" +#include "Terrain.h" class CGObjectInstance; class CTerrainViewPatternConfig; @@ -168,10 +169,10 @@ public: void clearTerrain(CRandomGenerator * gen = nullptr); /// Draws terrain at the current terrain selection. The selection will be cleared automatically. - void drawTerrain(ETerrainType terType, CRandomGenerator * gen = nullptr); + void drawTerrain(Terrain terType, CRandomGenerator * gen = nullptr); /// Draws roads at the current terrain selection. The selection will be cleared automatically. - void drawRoad(ERoadType::ERoadType roadType, CRandomGenerator * gen = nullptr); + void drawRoad(const std::string & roadType, CRandomGenerator * gen = nullptr); void insertObject(CGObjectInstance * obj); @@ -353,7 +354,7 @@ private: class CDrawTerrainOperation : public CMapOperation { public: - CDrawTerrainOperation(CMap * map, const CTerrainSelection & terrainSel, ETerrainType terType, CRandomGenerator * gen); + CDrawTerrainOperation(CMap * map, const CTerrainSelection & terrainSel, Terrain terType, CRandomGenerator * gen); void execute() override; void undo() override; @@ -384,16 +385,16 @@ private: InvalidTiles getInvalidTiles(const int3 & centerPos) const; void updateTerrainViews(); - ETerrainGroup::ETerrainGroup getTerrainGroup(ETerrainType terType) const; + ETerrainGroup::ETerrainGroup getTerrainGroup(Terrain terType) const; /// Validates the terrain view of the given position and with the given pattern. The first method wraps the /// second method to validate the terrain view with the given pattern in all four flip directions(horizontal, vertical). ValidationResult validateTerrainView(const int3 & pos, const std::vector * pattern, int recDepth = 0) const; ValidationResult validateTerrainViewInner(const int3 & pos, const TerrainViewPattern & pattern, int recDepth = 0) const; /// Tests whether the given terrain type is a sand type. Sand types are: Water, Sand and Rock - bool isSandType(ETerrainType terType) const; + bool isSandType(Terrain terType) const; CTerrainSelection terrainSel; - ETerrainType terType; + Terrain terType; CRandomGenerator * gen; std::set invalidatedTerViews; }; diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 09cdb5ef7..9030e9737 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -935,14 +935,14 @@ void CMapLoaderH3M::readTerrain() for(int z = 0; z < map->height; z++) { auto & tile = map->getTile(int3(z, c, a)); - tile.terType = ETerrainType(reader.readUInt8()); + tile.terType = Terrain::createTerrainTypeH3M(reader.readUInt8()); tile.terView = reader.readUInt8(); - tile.riverType = static_cast(reader.readUInt8()); + tile.riverType = RIVER_NAMES[reader.readUInt8()]; tile.riverDir = reader.readUInt8(); - tile.roadType = static_cast(reader.readUInt8()); + tile.roadType = ROAD_NAMES[reader.readUInt8()]; tile.roadDir = reader.readUInt8(); tile.extTileFlags = reader.readUInt8(); - tile.blocked = ((tile.terType == ETerrainType::ROCK || tile.terType == ETerrainType::BORDER ) ? true : false); //underground tiles are always blocked + tile.blocked = ((!tile.terType.isPassable() || tile.terType == Terrain("BORDER") ) ? true : false); //underground tiles are always blocked tile.visitable = 0; } } diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index 6d956f42b..ff08ba2c5 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -323,20 +323,6 @@ namespace TriggeredEventsDetail namespace TerrainDetail { - static const std::array terrainCodes = - { - "dt", "sa", "gr", "sn", "sw", "rg", "sb", "lv", "wt", "rc" - }; - static const std::array roadCodes = - { - "", "pd", "pg", "pc" - }; - - static const std::array riverCodes = - { - "", "rw", "ri", "rm", "rl" - }; - static const std::array flipCodes = { '_', '-', '|', '+' @@ -959,13 +945,7 @@ void CMapLoaderJson::readTerrainTile(const std::string & src, TerrainTile & tile using namespace TerrainDetail; {//terrain type const std::string typeCode = src.substr(0, 2); - - int rawType = vstd::find_pos(terrainCodes, typeCode); - - if(rawType < 0) - throw std::runtime_error("Invalid terrain type code in "+src); - - tile.terType = ETerrainType(rawType); + tile.terType = Terrain::createTerrainByCode(typeCode); } int startPos = 2; //0+typeCode fixed length {//terrain view @@ -992,20 +972,18 @@ void CMapLoaderJson::readTerrainTile(const std::string & src, TerrainTile & tile {//road type const std::string typeCode = src.substr(startPos, 2); startPos+=2; - int rawType = vstd::find_pos(roadCodes, typeCode); - if(rawType < 0) + if(vstd::find_pos(ROAD_NAMES, typeCode) < 0) { - rawType = vstd::find_pos(riverCodes, typeCode); - if(rawType < 0) + if(vstd::find_pos(RIVER_NAMES, typeCode) < 0) throw std::runtime_error("Invalid river type in "+src); else { - tile.riverType = ERiverType::ERiverType(rawType); + tile.riverType = typeCode; hasRoad = false; } } else - tile.roadType = ERoadType::ERoadType(rawType); + tile.roadType = typeCode; } if(hasRoad) {//road dir @@ -1033,10 +1011,9 @@ void CMapLoaderJson::readTerrainTile(const std::string & src, TerrainTile & tile {//river type const std::string typeCode = src.substr(startPos, 2); startPos+=2; - int rawType = vstd::find_pos(riverCodes, typeCode); - if(rawType < 0) + if(vstd::find_pos(RIVER_NAMES, typeCode) < 0) throw std::runtime_error("Invalid river type in "+src); - tile.riverType = ERiverType::ERiverType(rawType); + tile.riverType = typeCode; } {//river dir int pos = startPos; @@ -1298,13 +1275,13 @@ std::string CMapSaverJson::writeTerrainTile(const TerrainTile & tile) out.setf(std::ios::dec, std::ios::basefield); out.unsetf(std::ios::showbase); - out << terrainCodes.at(int(tile.terType)) << (int)tile.terView << flipCodes[tile.extTileFlags % 4]; + out << Terrain::Manager::getInfo(tile.terType).typeCode << (int)tile.terView << flipCodes[tile.extTileFlags % 4]; - if(tile.roadType != ERoadType::NO_ROAD) - out << roadCodes.at(int(tile.roadType)) << (int)tile.roadDir << flipCodes[(tile.extTileFlags >> 4) % 4]; + if(tile.roadType != ROAD_NAMES[0]) + out << tile.roadType << (int)tile.roadDir << flipCodes[(tile.extTileFlags >> 4) % 4]; - if(tile.riverType != ERiverType::NO_RIVER) - out << riverCodes.at(int(tile.riverType)) << (int)tile.riverDir << flipCodes[(tile.extTileFlags >> 2) % 4]; + if(tile.riverType != RIVER_NAMES[0]) + out << tile.riverType << (int)tile.riverDir << flipCodes[(tile.extTileFlags >> 2) % 4]; return out.str(); } diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index f8440c651..5736f8855 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -69,17 +69,6 @@ CMapGenerator::CMapGenerator(CMapGenOptions& mapGenOptions, int RandomSeed) : void CMapGenerator::loadConfig() { - static std::map terrainMap - { - {"dirt", ETerrainType::DIRT}, - {"sand", ETerrainType::SAND}, - {"grass", ETerrainType::GRASS}, - {"snow", ETerrainType::SNOW}, - {"swamp", ETerrainType::SWAMP}, - {"subterranean", ETerrainType::SUBTERRANEAN}, - {"lava", ETerrainType::LAVA}, - {"rough", ETerrainType::ROUGH} - }; static const std::map resMap { {"wood", Res::ERes::WOOD}, @@ -90,23 +79,17 @@ void CMapGenerator::loadConfig() {"sulfur", Res::ERes::SULFUR}, {"gold", Res::ERes::GOLD}, }; - static std::map roadTypeMap - { - {"dirt_road", ERoadType::DIRT_ROAD}, - {"gravel_road", ERoadType::GRAVEL_ROAD}, - {"cobblestone_road", ERoadType::COBBLESTONE_ROAD} - }; static const ResourceID path("config/randomMap.json"); JsonNode randomMapJson(path); for(auto& s : randomMapJson["terrain"]["undergroundAllow"].Vector()) { if(!s.isNull()) - config.terrainUndergroundAllowed.push_back(terrainMap[s.String()]); + config.terrainUndergroundAllowed.emplace_back(s.String()); } for(auto& s : randomMapJson["terrain"]["groundProhibit"].Vector()) { if(!s.isNull()) - config.terrainGroundProhibit.push_back(terrainMap[s.String()]); + config.terrainGroundProhibit.emplace_back(s.String()); } config.shipyardGuard = randomMapJson["waterZone"]["shipyard"]["value"].Integer(); for(auto & treasure : randomMapJson["waterZone"]["treasure"].Vector()) @@ -119,7 +102,7 @@ void CMapGenerator::loadConfig() } config.mineExtraResources = randomMapJson["mines"]["extraResourcesLimit"].Integer(); config.minGuardStrength = randomMapJson["minGuardStrength"].Integer(); - config.defaultRoadType = roadTypeMap[randomMapJson["defaultRoadType"].String()]; + config.defaultRoadType = randomMapJson["defaultRoadType"].String(); config.treasureValueLimit = randomMapJson["treasureValueLimit"].Integer(); for(auto & i : randomMapJson["prisons"]["experience"].Vector()) config.prisonExperience.push_back(i.Integer()); @@ -357,7 +340,7 @@ void CMapGenerator::genZones() { getEditManager()->clearTerrain(&rand); getEditManager()->getTerrainSelection().selectRange(MapRect(int3(0, 0, 0), mapGenOptions.getWidth(), mapGenOptions.getHeight())); - getEditManager()->drawTerrain(ETerrainType::GRASS, &rand); + getEditManager()->drawTerrain(Terrain("grass"), &rand); auto tmpl = mapGenOptions.getMapTemplate(); zones.clear(); @@ -530,14 +513,14 @@ void CMapGenerator::fillZones() void CMapGenerator::createObstaclesCommon1() { - if (map->twoLevel) //underground + if(map->twoLevel) //underground { //negative approach - create rock tiles first, then make sure all accessible tiles have no rock std::vector rockTiles; - for (int x = 0; x < map->width; x++) + for(int x = 0; x < map->width; x++) { - for (int y = 0; y < map->height; y++) + for(int y = 0; y < map->height; y++) { int3 tile(x, y, 1); if (shouldBeBlocked(tile)) @@ -547,21 +530,29 @@ void CMapGenerator::createObstaclesCommon1() } } getEditManager()->getTerrainSelection().setSelection(rockTiles); - getEditManager()->drawTerrain(ETerrainType::ROCK, &rand); + + //collect all rock terrain types + std::vector 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) + if(map->twoLevel) { //finally mark rock tiles as occupied, spawn no obstacles there - for (int x = 0; x < map->width; x++) + for(int x = 0; x < map->width; x++) { - for (int y = 0; y < map->height; y++) + for(int y = 0; y < map->height; y++) { int3 tile(x, y, 1); - if (map->getTile(tile).terType == ETerrainType::ROCK) + if(!map->getTile(tile).terType.isPassable()) { setOccupied(tile, ETileType::USED); } @@ -904,7 +895,7 @@ void CMapGenerator::setOccupied(const int3 &tile, ETileType::ETileType state) tiles[tile.x][tile.y][tile.z].setOccupied(state); } -void CMapGenerator::setRoad(const int3& tile, ERoadType::ERoadType roadType) +void CMapGenerator::setRoad(const int3& tile, const std::string & roadType) { checkIsOnMap(tile); diff --git a/lib/rmg/CMapGenerator.h b/lib/rmg/CMapGenerator.h index 3469af69f..f0e67c81b 100644 --- a/lib/rmg/CMapGenerator.h +++ b/lib/rmg/CMapGenerator.h @@ -26,8 +26,6 @@ class JsonNode; class CMapGenerator; class CTileInfo; -//#define _BETA - typedef std::vector JsonVector; class rmgException : public std::exception @@ -54,14 +52,14 @@ class DLL_LINKAGE CMapGenerator public: struct Config { - std::vector terrainUndergroundAllowed; - std::vector terrainGroundProhibit; + std::vector terrainUndergroundAllowed; + std::vector terrainGroundProhibit; std::vector waterTreasure; int shipyardGuard; int mineExtraResources; std::map mineValues; int minGuardStrength; - ERoadType::ERoadType defaultRoadType; + std::string defaultRoadType; int treasureValueLimit; std::vector prisonExperience, prisonValues; std::vector scrollValues; @@ -101,7 +99,7 @@ public: bool isRoad(const int3 &tile) const; void setOccupied(const int3 &tile, ETileType::ETileType state); - void setRoad(const int3 &tile, ERoadType::ERoadType roadType); + void setRoad(const int3 &tile, const std::string & roadType); CTileInfo getTile(const int3 & tile) const; bool isAllowedSpell(SpellID sid) const; diff --git a/lib/rmg/CRmgTemplate.cpp b/lib/rmg/CRmgTemplate.cpp index 35ce71dc7..5dcf46732 100644 --- a/lib/rmg/CRmgTemplate.cpp +++ b/lib/rmg/CRmgTemplate.cpp @@ -16,6 +16,7 @@ #include "../mapping/CMap.h" #include "../VCMI_Lib.h" #include "../CTownHandler.h" +#include "../Terrain.h" #include "../serializer/JsonSerializeFormat.h" #include "../StringConstants.h" @@ -66,12 +67,12 @@ class TerrainEncoder public: static si32 decode(const std::string & identifier) { - return vstd::find_pos(GameConstants::TERRAIN_NAMES, identifier); + return vstd::find_pos(Terrain::Manager::terrains(), identifier); } static std::string encode(const si32 index) { - return (index >=0 && index < GameConstants::TERRAIN_TYPES) ? GameConstants::TERRAIN_NAMES[index] : ""; + return (index >=0 && index < Terrain::Manager::terrains().size()) ? static_cast(Terrain::Manager::terrains()[index]) : ""; } }; @@ -89,18 +90,6 @@ public: } }; -const std::set ZoneOptions::DEFAULT_TERRAIN_TYPES = -{ - ETerrainType::DIRT, - ETerrainType::SAND, - ETerrainType::GRASS, - ETerrainType::SNOW, - ETerrainType::SWAMP, - ETerrainType::ROUGH, - ETerrainType::SUBTERRANEAN, - ETerrainType::LAVA -}; - const TRmgTemplateZoneId ZoneOptions::NO_ZONE = -1; ZoneOptions::CTownInfo::CTownInfo() @@ -149,7 +138,6 @@ ZoneOptions::ZoneOptions() playerTowns(), neutralTowns(), matchTerrainToTown(true), - terrainTypes(DEFAULT_TERRAIN_TYPES), townsAreSameType(false), townTypes(), monsterTypes(), @@ -161,7 +149,9 @@ ZoneOptions::ZoneOptions() terrainTypeLikeZone(NO_ZONE), treasureLikeZone(NO_ZONE) { - + for(auto & terr : Terrain::Manager::terrains()) + if(terr.isLand() && terr.isPassable()) + terrainTypes.insert(terr); } ZoneOptions & ZoneOptions::operator=(const ZoneOptions & other) @@ -224,15 +214,15 @@ boost::optional ZoneOptions::getOwner() const return owner; } -const std::set & ZoneOptions::getTerrainTypes() const +const std::set & ZoneOptions::getTerrainTypes() const { return terrainTypes; } -void ZoneOptions::setTerrainTypes(const std::set & value) +void ZoneOptions::setTerrainTypes(const std::set & value) { - assert(value.find(ETerrainType::WRONG) == value.end() && value.find(ETerrainType::BORDER) == value.end() && - value.find(ETerrainType::WATER) == value.end() && value.find(ETerrainType::ROCK) == value.end()); + //assert(value.find(ETerrainType::WRONG) == value.end() && value.find(ETerrainType::BORDER) == value.end() && + // value.find(ETerrainType::WATER) == value.end() && value.find(ETerrainType::ROCK) == value.end()); terrainTypes = value; } @@ -339,7 +329,31 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler) #undef SERIALIZE_ZONE_LINK if(terrainTypeLikeZone == NO_ZONE) - handler.serializeIdArray("terrainTypes", terrainTypes, DEFAULT_TERRAIN_TYPES); + { + JsonNode node; + if(handler.saving) + { + node.setType(JsonNode::JsonType::DATA_VECTOR); + for(auto & ttype : terrainTypes) + { + JsonNode n; + n.String() = ttype; + node.Vector().push_back(n); + } + } + handler.serializeRaw("terrainTypes", node, boost::none); + if(!handler.saving) + { + if(!node.Vector().empty()) + { + terrainTypes.clear(); + for(auto ttype : node.Vector()) + { + terrainTypes.emplace(ttype.String()); + } + } + } + } handler.serializeBool("townsAreSameType", townsAreSameType, false); handler.serializeIdArray("allowedMonsters", monsterTypes, VLC->townh->getAllowedFactions(false)); diff --git a/lib/rmg/CRmgTemplate.h b/lib/rmg/CRmgTemplate.h index 0053e57c8..24bfe6ecb 100644 --- a/lib/rmg/CRmgTemplate.h +++ b/lib/rmg/CRmgTemplate.h @@ -13,9 +13,11 @@ #include "../int3.h" #include "../GameConstants.h" #include "../ResourceSet.h" +#include "../Terrain.h" #include "CMapGenOptions.h" class JsonSerializeFormat; +class Terrain; namespace ETemplateZoneType { @@ -65,7 +67,6 @@ private: class DLL_LINKAGE ZoneOptions { public: - static const std::set DEFAULT_TERRAIN_TYPES; static const TRmgTemplateZoneId NO_ZONE; class DLL_LINKAGE CTownInfo @@ -101,8 +102,8 @@ public: void setSize(int value); boost::optional getOwner() const; - const std::set & getTerrainTypes() const; - void setTerrainTypes(const std::set & value); + const std::set & getTerrainTypes() const; + void setTerrainTypes(const std::set & value); std::set getDefaultTownTypes() const; const std::set & getTownTypes() const; @@ -134,7 +135,7 @@ protected: CTownInfo playerTowns; CTownInfo neutralTowns; bool matchTerrainToTown; - std::set terrainTypes; + std::set terrainTypes; bool townsAreSameType; std::set townTypes; diff --git a/lib/rmg/CRmgTemplateZone.cpp b/lib/rmg/CRmgTemplateZone.cpp index f359ed28a..2600fdc55 100644 --- a/lib/rmg/CRmgTemplateZone.cpp +++ b/lib/rmg/CRmgTemplateZone.cpp @@ -36,7 +36,7 @@ void CRmgTemplateZone::addRoadNode(const int3& node) roadNodes.insert(node); } -CTileInfo::CTileInfo():nearestObjectDistance(float(INT_MAX)), terrain(ETerrainType::WRONG),roadType(ERoadType::NO_ROAD) +CTileInfo::CTileInfo():nearestObjectDistance(float(INT_MAX)), terrain() { occupied = ETileType::POSSIBLE; //all tiles are initially possible to place objects or passages } @@ -69,7 +69,7 @@ bool CTileInfo::isFree() const bool CTileInfo::isRoad() const { - return roadType != ERoadType::NO_ROAD; + return roadType != ROAD_NAMES[0]; } bool CTileInfo::isUsed() const @@ -86,17 +86,17 @@ ETileType::ETileType CTileInfo::getTileType() const return occupied; } -ETerrainType CTileInfo::getTerrainType() const +Terrain CTileInfo::getTerrainType() const { return terrain; } -void CTileInfo::setTerrainType(ETerrainType value) +void CTileInfo::setTerrainType(Terrain value) { terrain = value; } -void CTileInfo::setRoadType(ERoadType::ERoadType value) +void CTileInfo::setRoadType(const std::string & value) { roadType = value; // setOccupied(ETileType::FREE); @@ -106,7 +106,7 @@ void CTileInfo::setRoadType(ERoadType::ERoadType value) CRmgTemplateZone::CRmgTemplateZone(CMapGenerator * Gen) : ZoneOptions(), townType(ETownType::NEUTRAL), - terrainType (ETerrainType::GRASS), + terrainType (Terrain("grass")), minGuardedValue(0), questArtZone(), gen(Gen) @@ -989,7 +989,7 @@ bool CRmgTemplateZone::createRoad(const int3& src, const int3& dst) std::map cameFrom; // The map of navigated nodes. std::map distances; - gen->setRoad (src, ERoadType::NO_ROAD); //just in case zone guard already has road under it. Road under nodes will be added at very end + gen->setRoad (src, ROAD_NAMES[0]); //just in case zone guard already has road under it. Road under nodes will be added at very end cameFrom[src] = int3(-1, -1, -1); //first node points to finish condition pq.push(std::make_pair(src, 0.f)); @@ -1012,8 +1012,8 @@ bool CRmgTemplateZone::createRoad(const int3& src, const int3& dst) while (cameFrom[backTracking].valid()) { // add node to path - roads.insert (backTracking); - gen->setRoad (backTracking, ERoadType::COBBLESTONE_ROAD); + roads.insert(backTracking); + gen->setRoad(backTracking, gen->getConfig().defaultRoadType); //logGlobal->trace("Setting road at tile %s", backTracking); // do the same for the predecessor backTracking = cameFrom[backTracking]; @@ -1250,6 +1250,17 @@ void CRmgTemplateZone::addToConnectLater(const int3& src) tilesToConnectLater.insert(src); } +int CRmgTemplateZone::chooseRandomAppearance(si32 ObjID) const +{ + auto factories = VLC->objtypeh->knownSubObjects(ObjID); + vstd::erase_if(factories, [this, ObjID](si32 f) + { + return VLC->objtypeh->getHandlerFor(ObjID, f)->getTemplates(terrainType).empty(); + }); + + return *RandomGeneratorUtil::nextItem(factories, gen->rand); +} + bool CRmgTemplateZone::addMonster(int3 &pos, si32 strength, bool clearSurroundingTiles, bool zoneGuard) { //precalculate actual (randomized) monster strength based on this post @@ -1731,33 +1742,51 @@ void CRmgTemplateZone::initTerrainType () { if (type==ETemplateZoneType::WATER) { - terrainType = ETerrainType::WATER; + //collect all water terrain types + std::vector waterTerrains; + for(auto & terrain : Terrain::Manager::terrains()) + if(terrain.isWater()) + waterTerrains.push_back(terrain); + + terrainType = *RandomGeneratorUtil::nextItem(waterTerrains, gen->rand); } else { if (matchTerrainToTown && townType != ETownType::NEUTRAL) + { terrainType = (*VLC->townh)[townType]->nativeTerrain; + } else + { terrainType = *RandomGeneratorUtil::nextItem(terrainTypes, gen->rand); + } //TODO: allow new types of terrain? { if(isUnderground()) { if(!vstd::contains(gen->getConfig().terrainUndergroundAllowed, terrainType)) - terrainType = ETerrainType::SUBTERRANEAN; + { + //collect all underground terrain types + std::vector undegroundTerrains; + for(auto & terrain : Terrain::Manager::terrains()) + if(terrain.isUnderground()) + undegroundTerrains.push_back(terrain); + + terrainType = *RandomGeneratorUtil::nextItem(undegroundTerrains, gen->rand); + } } else { - if(vstd::contains(gen->getConfig().terrainGroundProhibit, terrainType)) - terrainType = ETerrainType::DIRT; + if(vstd::contains(gen->getConfig().terrainGroundProhibit, terrainType) || terrainType.isUnderground()) + terrainType = Terrain("dirt"); } } } paintZoneTerrain (terrainType); } -void CRmgTemplateZone::paintZoneTerrain (ETerrainType terrainType) +void CRmgTemplateZone::paintZoneTerrain (Terrain terrainType) { std::vector tiles(tileinfo.begin(), tileinfo.end()); gen->getEditManager()->getTerrainSelection().setSelection(tiles); @@ -1837,6 +1866,9 @@ bool CRmgTemplateZone::createRequiredObjects() for(const auto &object : requiredObjects) { auto obj = object.first; + if (!obj->appearance.canBePlacedAt(terrainType)) + continue; + int3 pos; while (true) { @@ -1845,6 +1877,7 @@ bool CRmgTemplateZone::createRequiredObjects() logGlobal->error("Failed to fill zone %d due to lack of space", id); return false; } + if (tryToPlaceObjectAndConnectToPath(obj, pos) == EObjectPlacingResult::SUCCESS) { //paths to required objects constitute main paths of zone. otherwise they just may lead to middle and create dead zones @@ -1858,6 +1891,9 @@ bool CRmgTemplateZone::createRequiredObjects() for (const auto &obj : closeObjects) { setTemplateForObject(obj.first); + if (!obj.first->appearance.canBePlacedAt(terrainType)) + continue; + auto tilesBlockedByObject = obj.first->getBlockedOffsets(); bool finished = false; @@ -2069,8 +2105,8 @@ int3 CRmgTemplateZone::createShipyard(const std::set & lake, si32 guardStr bool CRmgTemplateZone::createShipyard(const int3 & position, si32 guardStrength) { - auto subObjects = VLC->objtypeh->knownSubObjects(Obj::SHIPYARD); - auto shipyard = (CGShipyard*) VLC->objtypeh->getHandlerFor(Obj::SHIPYARD, *RandomGeneratorUtil::nextItem(subObjects, gen->rand))->create(ObjectTemplate()); + int subtype = chooseRandomAppearance(Obj::SHIPYARD); + auto shipyard = (CGShipyard*) VLC->objtypeh->getHandlerFor(Obj::SHIPYARD, subtype)->create(ObjectTemplate()); shipyard->tempOwner = PlayerColor::NEUTRAL; setTemplateForObject(shipyard); @@ -2569,7 +2605,8 @@ void CRmgTemplateZone::checkAndPlaceObject(CGObjectInstance* object, const int3 if (object->appearance.id == Obj::NO_OBJ) { auto terrainType = gen->map->getTile(pos).terType; - auto templates = VLC->objtypeh->getHandlerFor(object->ID, object->subID)->getTemplates(terrainType); + auto h = VLC->objtypeh->getHandlerFor(object->ID, object->subID); + auto templates = h->getTemplates(terrainType); if (templates.empty()) throw rmgException(boost::to_string(boost::format("Did not find graphics for object (%d,%d) at %s (terrain %d)") % object->ID % object->subID % pos.toString() % terrainType)); @@ -3239,7 +3276,7 @@ void CRmgTemplateZone::addAllPossibleObjects() if (!creaturesAmount) continue; - int randomAppearance = *RandomGeneratorUtil::nextItem(VLC->objtypeh->knownSubObjects(Obj::SEER_HUT), gen->rand); + int randomAppearance = chooseRandomAppearance(Obj::SEER_HUT); oi.generateObject = [creature, creaturesAmount, randomAppearance, this, generateArtInfo]() -> CGObjectInstance * { @@ -3270,7 +3307,7 @@ void CRmgTemplateZone::addAllPossibleObjects() static int seerLevels = std::min(gen->getConfig().questValues.size(), gen->getConfig().questRewardValues.size()); for(int i = 0; i < seerLevels; i++) //seems that code for exp and gold reward is similiar { - int randomAppearance = *RandomGeneratorUtil::nextItem(VLC->objtypeh->knownSubObjects(Obj::SEER_HUT), gen->rand); + int randomAppearance = chooseRandomAppearance(Obj::SEER_HUT); oi.setTemplate(Obj::SEER_HUT, randomAppearance, terrainType); oi.value = gen->getConfig().questValues[i]; @@ -3332,7 +3369,7 @@ ObjectInfo::ObjectInfo() } -void ObjectInfo::setTemplate (si32 type, si32 subtype, ETerrainType terrainType) +void ObjectInfo::setTemplate (si32 type, si32 subtype, Terrain terrainType) { auto templHandler = VLC->objtypeh->getHandlerFor(type, subtype); if(!templHandler) diff --git a/lib/rmg/CRmgTemplateZone.h b/lib/rmg/CRmgTemplateZone.h index 83a7778c2..24f006c9a 100644 --- a/lib/rmg/CRmgTemplateZone.h +++ b/lib/rmg/CRmgTemplateZone.h @@ -17,6 +17,7 @@ #include "CRmgTemplate.h" #include "../mapObjects/ObjectTemplate.h" #include //A* +#include "Terrain.h" class CMapGenerator; class CTileInfo; @@ -48,16 +49,16 @@ public: bool isUsed() const; bool isRoad() const; void setOccupied(ETileType::ETileType value); - ETerrainType getTerrainType() const; + Terrain getTerrainType() const; ETileType::ETileType getTileType() const; - void setTerrainType(ETerrainType value); + void setTerrainType(Terrain value); - void setRoadType(ERoadType::ERoadType value); + void setRoadType(const std::string & value); private: float nearestObjectDistance; ETileType::ETileType occupied; - ETerrainType terrain; - ERoadType::ERoadType roadType; + Terrain terrain; + std::string roadType; }; struct DLL_LINKAGE ObjectInfo @@ -69,7 +70,7 @@ struct DLL_LINKAGE ObjectInfo //ui32 maxPerMap; //unused std::function generateObject; - void setTemplate (si32 type, si32 subtype, ETerrainType terrain); + void setTemplate (si32 type, si32 subtype, Terrain terrain); ObjectInfo(); @@ -120,7 +121,7 @@ public: bool fill (); bool placeMines (); void initTownType (); - void paintZoneTerrain (ETerrainType terrainType); + void paintZoneTerrain (Terrain terrainType); void randomizeTownType(bool matchUndergroundType = false); //helper function void initTerrainType (); void createBorder(); @@ -192,7 +193,7 @@ private: //template info si32 townType; - ETerrainType terrainType; + Terrain terrainType; std::weak_ptr questArtZone; //artifacts required for Seer Huts will be placed here - or not if null std::vector possibleObjects; @@ -230,6 +231,7 @@ private: 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); }; diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 567d7723c..bae33194f 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -184,28 +184,26 @@ void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const zonesToPlace.push_back(zone); else { - switch ((*VLC->townh)[faction]->nativeTerrain) + auto & tt = (*VLC->townh)[faction]->nativeTerrain; + if(tt == Terrain("dirt")) { - case ETerrainType::GRASS: - case ETerrainType::SWAMP: - case ETerrainType::SNOW: - case ETerrainType::SAND: - case ETerrainType::ROUGH: - //surface - zonesOnLevel[0]++; - levels[zone.first] = 0; - break; - case ETerrainType::LAVA: - case ETerrainType::SUBTERRANEAN: - //underground - zonesOnLevel[1]++; - levels[zone.first] = 1; - break; - case ETerrainType::DIRT: - default: //any / random zonesToPlace.push_back(zone); - break; + } + else + { + if(tt.isUnderground()) + { + //underground + zonesOnLevel[1]++; + levels[zone.first] = 1; + } + else + { + //surface + zonesOnLevel[0]++; + levels[zone.first] = 0; + } } } } @@ -564,7 +562,7 @@ void CZonePlacer::assignZones() //make sure that terrain inside zone is not a rock //FIXME: reorder actions? - zone.second->paintZoneTerrain (ETerrainType::SUBTERRANEAN); + zone.second->paintZoneTerrain (Terrain("subterra")); } } logGlobal->info("Finished zone colouring"); diff --git a/lib/serializer/CSerializer.h b/lib/serializer/CSerializer.h index 33579ad84..15c84f5d8 100644 --- a/lib/serializer/CSerializer.h +++ b/lib/serializer/CSerializer.h @@ -12,8 +12,8 @@ #include "../ConstTransitivePtr.h" #include "../GameConstants.h" -const ui32 SERIALIZATION_VERSION = 801; -const ui32 MINIMAL_SERIALIZATION_VERSION = 753; +const ui32 SERIALIZATION_VERSION = 802; +const ui32 MINIMAL_SERIALIZATION_VERSION = 802; const std::string SAVEGAME_MAGIC = "VCMISVG"; class CHero; diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index 82c1c7714..358a91bef 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -1018,54 +1018,3 @@ std::vector CSpellHandler::getDefaultAllowed() const return allowedSpells; } - -void CSpellHandler::update780() -{ - static_assert(MINIMAL_SERIALIZATION_VERSION < 780, "No longer needed CSpellHandler::update780"); - - auto spellsContent = (*VLC->modh->content)["spells"]; - - const ContentTypeHandler::ModInfo & coreData = spellsContent.modData.at("core"); - - const JsonNode & coreSpells = coreData.modData; - - const int levelsCount = GameConstants::SPELL_SCHOOL_LEVELS; - - for(CSpell * spell : objects) - { - auto identifier = spell->identifier; - size_t colonPos = identifier.find(':'); - if(colonPos != std::string::npos) - continue; - - const JsonNode & actualConfig = coreSpells[spell->identifier]; - - if(actualConfig.getType() != JsonNode::JsonType::DATA_STRUCT) - { - logGlobal->error("Spell not found %s", spell->identifier); - continue; - } - - if(actualConfig["targetCondition"].getType() == JsonNode::JsonType::DATA_STRUCT && !actualConfig["targetCondition"].Struct().empty()) - { - spell->targetCondition = actualConfig["targetCondition"]; - } - - for(int levelIndex = 0; levelIndex < levelsCount; levelIndex++) - { - const JsonNode & levelNode = actualConfig["levels"][SpellConfig::LEVEL_NAMES[levelIndex]]; - - logGlobal->debug(levelNode.toJson()); - - CSpell::LevelInfo & levelObject = spell->levels[levelIndex]; - - if(levelNode["battleEffects"].getType() == JsonNode::JsonType::DATA_STRUCT && !levelNode["battleEffects"].Struct().empty()) - { - levelObject.battleEffects = levelNode["battleEffects"]; - - logGlobal->trace("Updated special effects for level %d of spell %s", levelIndex, spell->identifier); - } - } - - } -} diff --git a/lib/spells/CSpellHandler.h b/lib/spells/CSpellHandler.h index a25d4de2c..f821abc46 100644 --- a/lib/spells/CSpellHandler.h +++ b/lib/spells/CSpellHandler.h @@ -83,14 +83,7 @@ public: { h & resourceName; h & verticalPosition; - if(version >= 754) - { - h & pause; - } - else if(!h.saving) - { - pause = 0; - } + h & pause; } }; @@ -120,10 +113,7 @@ public: h & projectile; h & hit; h & cast; - if(version >= 762) - { - h & affect; - } + h & affect; } std::string selectProjectile(const double angle) const; @@ -158,32 +148,11 @@ public: h & AIValue; h & smartTarget; h & range; - - if(version >= 773) - { - h & effects; - h & cumulativeEffects; - } - else - { - //all old effects treated as not cumulative, special cases handled by CSpell::serialize - std::vector old; - h & old; - - if(!h.saving) - { - effects.clear(); - cumulativeEffects.clear(); - for(const Bonus & oldBonus : old) - effects.push_back(std::make_shared(oldBonus)); - } - } - + h & effects; + h & cumulativeEffects; h & clearTarget; h & clearAffected; - - if(version >= 780) - h & battleEffects; + h & battleEffects; } }; @@ -316,27 +285,7 @@ public: h & damage; h & offensive; h & targetType; - - if(version >= 780) - { - h & targetCondition; - } - else - { - BTVector immunities; - BTVector absoluteImmunities; - BTVector limiters; - BTVector absoluteLimiters; - - h & immunities; - h & limiters; - h & absoluteImmunities; - h & absoluteLimiters; - - if(!h.saving) - targetCondition = convertTargetCondition(immunities, absoluteImmunities, limiters, absoluteLimiters); - } - + h & targetCondition; h & iconImmune; h & defaultProbability; h & special; @@ -348,16 +297,6 @@ public: h & levels; h & school; h & animationInfo; - - //backward compatibility - //can not be added to level structure as level structure does not know spell id - if(!h.saving && version < 773) - { - if(id == SpellID::DISRUPTING_RAY || id == SpellID::ACID_BREATH_DEFENSE) - for(auto & level : levels) - std::swap(level.effects, level.cumulativeEffects); - } - } friend class CSpellHandler; friend class Graphics; @@ -442,11 +381,6 @@ public: template void serialize(Handler & h, const int version) { h & objects; - if(!h.saving && version < 780) - { - update780(); - } - if(!h.saving) { afterLoadFinalization(); @@ -456,6 +390,4 @@ public: protected: const std::vector & getTypeNames() const override; CSpell * loadFromJson(const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index) override; -private: - void update780(); }; diff --git a/scripting/lua/api/BattleCb.cpp b/scripting/lua/api/BattleCb.cpp index 38d242e80..779289014 100644 --- a/scripting/lua/api/BattleCb.cpp +++ b/scripting/lua/api/BattleCb.cpp @@ -84,9 +84,7 @@ int BattleCbProxy::getTerrainType(lua_State * L) if(!S.tryGet(1, object)) return S.retVoid(); - auto ret = object->battleTerrainType(); - - return LuaStack::quickRetInt(L, static_cast(ret.num)); + return LuaStack::quickRetStr(L, object->battleTerrainType()); } int BattleCbProxy::getUnitByPos(lua_State * L) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index dcb46375a..62e992919 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1911,7 +1911,7 @@ void CGameHandler::newTurn() hth.id = h->id; auto ti = make_unique(h, 1); // TODO: this code executed when bonuses of previous day not yet updated (this happen in NewTurn::applyGs). See issue 2356 - hth.move = h->maxMovePointsCached(gs->map->getTile(h->getPosition(false)).terType != ETerrainType::WATER, ti.get()); + hth.move = h->maxMovePointsCached(gs->map->getTile(h->getPosition(false)).terType.isLand(), ti.get()); hth.mana = h->getManaNewTurn(); n.heroes.insert(hth); @@ -2215,9 +2215,9 @@ void CGameHandler::setupBattle(int3 tile, const CArmedInstance *armies[2], const battleResult.set(nullptr); const auto t = getTile(tile); - ETerrainType terrain = t->terType; + Terrain terrain = t->terType; if (gs->map->isCoastalTile(tile)) //coastal tile is always ground - terrain = ETerrainType::SAND; + terrain = Terrain("sand"); BFieldType terType = gs->battleGetBattlefieldType(tile, getRandomGenerator()); if (heroes[0] && heroes[0]->boat && heroes[1] && heroes[1]->boat) @@ -2314,7 +2314,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo const int3 guardPos = gs->guardingCreaturePosition(hmpos); const bool embarking = !h->boat && !t.visitableObjects.empty() && t.visitableObjects.back()->ID == Obj::BOAT; - const bool disembarking = h->boat && t.terType != ETerrainType::WATER && !t.blocked; + const bool disembarking = h->boat && t.terType.isLand() && !t.blocked; //result structure for start - movement failed, no move points used TryMoveHero tmh; @@ -2336,11 +2336,11 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo //it's a rock or blocked and not visitable tile //OR hero is on land and dest is water and (there is not present only one object - boat) - if (((t.terType == ETerrainType::ROCK || (t.blocked && !t.visitable && !canFly)) + if (((!t.terType.isPassable() || (t.blocked && !t.visitable && !canFly)) && complain("Cannot move hero, destination tile is blocked!")) - || ((!h->boat && !canWalkOnSea && !canFly && t.terType == ETerrainType::WATER && (t.visitableObjects.size() < 1 || (t.visitableObjects.back()->ID != Obj::BOAT && t.visitableObjects.back()->ID != Obj::HERO))) //hero is not on boat/water walking and dst water tile doesn't contain boat/hero (objs visitable from land) -> we test back cause boat may be on top of another object (#276) + || ((!h->boat && !canWalkOnSea && !canFly && t.terType.isWater() && (t.visitableObjects.size() < 1 || (t.visitableObjects.back()->ID != Obj::BOAT && t.visitableObjects.back()->ID != Obj::HERO))) //hero is not on boat/water walking and dst water tile doesn't contain boat/hero (objs visitable from land) -> we test back cause boat may be on top of another object (#276) && complain("Cannot move hero, destination tile is on water!")) - || ((h->boat && t.terType != ETerrainType::WATER && t.blocked) + || ((h->boat && t.terType.isLand() && t.blocked) && complain("Cannot disembark hero, tile is blocked!")) || ((distance(h->pos, dst) >= 1.5 && !teleporting) && complain("Tiles are not neighboring!")) diff --git a/server/CGameHandler.h b/server/CGameHandler.h index 98454cac5..2c90edf6e 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -272,21 +272,14 @@ public: h & QID; h & states; h & finishingBattle; + h & getRandomGenerator(); - if(version >= 761) - { - h & getRandomGenerator(); - } - - if(version >= 800) - { - JsonNode scriptsState; - if(h.saving) - serverScripts->serializeState(h.saving, scriptsState); - h & scriptsState; - if(!h.saving) - serverScripts->serializeState(h.saving, scriptsState); - } + JsonNode scriptsState; + if(h.saving) + serverScripts->serializeState(h.saving, scriptsState); + h & scriptsState; + if(!h.saving) + serverScripts->serializeState(h.saving, scriptsState); } void sendMessageToAll(const std::string &message); @@ -314,11 +307,6 @@ public: h & loserHero; h & victor; h & loser; - if(version < 774 && !h.saving) - { - bool duel; - h & duel; - } h & remainingBattleQueriesCount; } }; diff --git a/test/game/CGameStateTest.cpp b/test/game/CGameStateTest.cpp index 9b15f9971..a31d8eb25 100644 --- a/test/game/CGameStateTest.cpp +++ b/test/game/CGameStateTest.cpp @@ -193,7 +193,7 @@ public: const auto t = gameCallback->getTile(tile); - ETerrainType terrain = t->terType; + Terrain terrain = t->terType; BFieldType terType = BFieldType::GRASS_HILLS; //send info about battles diff --git a/test/googletest b/test/googletest index e2239ee60..4bab34d20 160000 --- a/test/googletest +++ b/test/googletest @@ -1 +1 @@ -Subproject commit e2239ee6043f73722e7aa812a459f54a28552929 +Subproject commit 4bab34d2084259cba67f3bfb51217c10d606e175 diff --git a/test/map/CMapEditManagerTest.cpp b/test/map/CMapEditManagerTest.cpp index 572b7e0cd..8283bccde 100644 --- a/test/map/CMapEditManagerTest.cpp +++ b/test/map/CMapEditManagerTest.cpp @@ -33,30 +33,30 @@ TEST(MapManager, DrawTerrain_Type) // 1x1 Blow up editManager->getTerrainSelection().select(int3(5, 5, 0)); - editManager->drawTerrain(ETerrainType::GRASS); + editManager->drawTerrain(Terrain("grass")); static const int3 squareCheck[] = { int3(5,5,0), int3(5,4,0), int3(4,4,0), int3(4,5,0) }; for(int i = 0; i < ARRAY_COUNT(squareCheck); ++i) { - EXPECT_EQ(map->getTile(squareCheck[i]).terType, ETerrainType::GRASS); + EXPECT_EQ(map->getTile(squareCheck[i]).terType, Terrain("grass")); } // Concat to square editManager->getTerrainSelection().select(int3(6, 5, 0)); - editManager->drawTerrain(ETerrainType::GRASS); - EXPECT_EQ(map->getTile(int3(6, 4, 0)).terType, ETerrainType::GRASS); + editManager->drawTerrain(Terrain("grass")); + EXPECT_EQ(map->getTile(int3(6, 4, 0)).terType, Terrain("grass")); editManager->getTerrainSelection().select(int3(6, 5, 0)); - editManager->drawTerrain(ETerrainType::LAVA); - EXPECT_EQ(map->getTile(int3(4, 4, 0)).terType, ETerrainType::GRASS); - EXPECT_EQ(map->getTile(int3(7, 4, 0)).terType, ETerrainType::LAVA); + editManager->drawTerrain(Terrain("lava")); + EXPECT_EQ(map->getTile(int3(4, 4, 0)).terType, Terrain("grass")); + EXPECT_EQ(map->getTile(int3(7, 4, 0)).terType, Terrain("lava")); // Special case water,rock editManager->getTerrainSelection().selectRange(MapRect(int3(10, 10, 0), 10, 5)); - editManager->drawTerrain(ETerrainType::GRASS); + editManager->drawTerrain(Terrain("grass")); editManager->getTerrainSelection().selectRange(MapRect(int3(15, 17, 0), 10, 5)); - editManager->drawTerrain(ETerrainType::GRASS); + editManager->drawTerrain(Terrain("grass")); editManager->getTerrainSelection().select(int3(21, 16, 0)); - editManager->drawTerrain(ETerrainType::GRASS); - EXPECT_EQ(map->getTile(int3(20, 15, 0)).terType, ETerrainType::GRASS); + editManager->drawTerrain(Terrain("grass")); + EXPECT_EQ(map->getTile(int3(20, 15, 0)).terType, Terrain("grass")); // Special case non water,rock static const int3 diagonalCheck[] = { int3(31,42,0), int3(32,42,0), int3(32,43,0), int3(33,43,0), int3(33,44,0), @@ -66,17 +66,17 @@ TEST(MapManager, DrawTerrain_Type) { editManager->getTerrainSelection().select(diagonalCheck[i]); } - editManager->drawTerrain(ETerrainType::GRASS); - EXPECT_EQ(map->getTile(int3(35, 44, 0)).terType, ETerrainType::WATER); + editManager->drawTerrain(Terrain("grass")); + EXPECT_EQ(map->getTile(int3(35, 44, 0)).terType, Terrain("water")); // Rock case editManager->getTerrainSelection().selectRange(MapRect(int3(1, 1, 1), 15, 15)); - editManager->drawTerrain(ETerrainType::SUBTERRANEAN); + editManager->drawTerrain(Terrain("subterra")); std::vector vec({ int3(6, 6, 1), int3(7, 6, 1), int3(8, 6, 1), int3(5, 7, 1), int3(6, 7, 1), int3(7, 7, 1), int3(8, 7, 1), int3(4, 8, 1), int3(5, 8, 1), int3(6, 8, 1)}); editManager->getTerrainSelection().setSelection(vec); - editManager->drawTerrain(ETerrainType::ROCK); - EXPECT_TRUE(map->getTile(int3(5, 6, 1)).terType == ETerrainType::ROCK || map->getTile(int3(7, 8, 1)).terType == ETerrainType::ROCK); + editManager->drawTerrain(Terrain("rock")); + EXPECT_TRUE(!map->getTile(int3(5, 6, 1)).terType.isPassable() || !map->getTile(int3(7, 8, 1)).terType.isPassable()); //todo: add checks here and enable, also use smaller size #if 0 @@ -89,13 +89,13 @@ TEST(MapManager, DrawTerrain_Type) auto editManager2 = map2->getEditManager(); editManager2->getTerrainSelection().selectRange(MapRect(int3(0, 0, 1), 128, 128)); - editManager2->drawTerrain(ETerrainType::SUBTERRANEAN); + editManager2->drawTerrain(CTerrainType("subterra")); std::vector selection({ int3(95, 43, 1), int3(95, 44, 1), int3(94, 45, 1), int3(95, 45, 1), int3(96, 45, 1), int3(93, 46, 1), int3(94, 46, 1), int3(95, 46, 1), int3(96, 46, 1), int3(97, 46, 1), int3(98, 46, 1), int3(99, 46, 1)}); editManager2->getTerrainSelection().setSelection(selection); - editManager2->drawTerrain(ETerrainType::ROCK); + editManager2->drawTerrain(CTerrainType("rock")); #endif // 0 } diff --git a/test/mock/mock_IBattleInfoCallback.h b/test/mock/mock_IBattleInfoCallback.h index 4f4765f25..ff7904b3c 100644 --- a/test/mock/mock_IBattleInfoCallback.h +++ b/test/mock/mock_IBattleInfoCallback.h @@ -17,7 +17,7 @@ class IBattleInfoCallbackMock : public IBattleInfoCallback { public: MOCK_CONST_METHOD0(getContextPool, scripting::Pool *()); - MOCK_CONST_METHOD0(battleTerrainType, ETerrainType()); + MOCK_CONST_METHOD0(battleTerrainType, Terrain()); MOCK_CONST_METHOD0(battleGetBattlefieldType, BFieldType()); MOCK_CONST_METHOD0(battleIsFinished, boost::optional()); diff --git a/test/mock/mock_battle_IBattleState.h b/test/mock/mock_battle_IBattleState.h index 36dba2262..3493b703d 100644 --- a/test/mock/mock_battle_IBattleState.h +++ b/test/mock/mock_battle_IBattleState.h @@ -19,7 +19,7 @@ public: MOCK_CONST_METHOD1(getStacksIf, TStacks(TStackFilter)); MOCK_CONST_METHOD1(getUnitsIf, battle::Units(battle::UnitFilter)); MOCK_CONST_METHOD0(getBattlefieldType, BFieldType()); - MOCK_CONST_METHOD0(getTerrainType, ETerrainType()); + MOCK_CONST_METHOD0(getTerrainType, Terrain()); MOCK_CONST_METHOD0(getAllObstacles, IBattleInfo::ObstacleCList()); MOCK_CONST_METHOD0(getDefendedTown, const CGTownInstance *()); MOCK_CONST_METHOD1(getWallState, si8(int)); From 9241cd1e62a7988d77cfb27e1eef0a77484b9229 Mon Sep 17 00:00:00 2001 From: Nordsoft91 Date: Wed, 22 Jun 2022 11:41:02 +0300 Subject: [PATCH 02/15] New battlegrounds (#758) --- client/Graphics.cpp | 46 ++++---- client/Graphics.h | 2 +- client/battle/CBattleInterface.cpp | 13 ++- config/battles_graphics.json | 54 +++++----- config/obstacles.json | 138 ++++++++++++------------ config/terrains.json | 37 +++++-- lib/CGameState.cpp | 54 +++------- lib/CGameState.h | 2 +- lib/CHeroHandler.cpp | 31 +++--- lib/CHeroHandler.h | 11 +- lib/GameConstants.h | 43 -------- lib/Terrain.cpp | 49 +++++++++ lib/Terrain.h | 40 +++++++ lib/battle/BattleInfo.cpp | 155 +++++++++++---------------- lib/battle/BattleInfo.h | 9 +- lib/battle/BattleProxy.cpp | 2 +- lib/battle/BattleProxy.h | 2 +- lib/battle/CBattleInfoCallback.cpp | 2 +- lib/battle/CBattleInfoEssentials.cpp | 4 +- lib/battle/CBattleInfoEssentials.h | 2 +- lib/battle/IBattleInfoCallback.h | 4 +- lib/battle/IBattleState.h | 3 +- lib/mapping/CMapEditManager.cpp | 102 ++++++------------ lib/mapping/CMapEditManager.h | 24 +---- lib/serializer/CSerializer.h | 4 +- lib/spells/ISpellMechanics.cpp | 2 +- scripting/lua/api/BattleCb.cpp | 2 +- server/CGameHandler.cpp | 4 +- test/erm/ERM_BU.cpp | 4 +- test/game/CGameStateTest.cpp | 2 +- test/map/CMapEditManagerTest.cpp | 3 +- test/mock/BattleFake.cpp | 2 +- test/mock/mock_IBattleInfoCallback.h | 2 +- test/mock/mock_battle_IBattleState.h | 2 +- 34 files changed, 412 insertions(+), 444 deletions(-) diff --git a/client/Graphics.cpp b/client/Graphics.cpp index 93351c81e..fe64be0f9 100644 --- a/client/Graphics.cpp +++ b/client/Graphics.cpp @@ -24,6 +24,7 @@ #include "gui/CAnimation.h" #include #include "../lib/CThreadHelper.h" +#include "../lib/CModHandler.h" #include "CGameInfo.h" #include "../lib/VCMI_Lib.h" #include "../CCallback.h" @@ -99,28 +100,35 @@ void Graphics::loadPaletteAndColors() void Graphics::initializeBattleGraphics() { - const JsonNode config(ResourceID("config/battles_graphics.json")); + auto allConfigs = VLC->modh->getActiveMods(); + allConfigs.insert(allConfigs.begin(), "core"); + for(auto & mod : allConfigs) + { + if(!CResourceHandler::get(mod)->existsResource(ResourceID("config/battles_graphics.json"))) + continue; + + const JsonNode config(mod, ResourceID("config/battles_graphics.json")); - // Reserve enough space for the terrains - int idx = static_cast(config["backgrounds"].Vector().size()); - battleBacks.resize(idx+1); // 1 to idx, 0 is unused - - idx = 1; - for(const JsonNode &t : config["backgrounds"].Vector()) { - battleBacks[idx].push_back(t.String()); - idx++; - } - - //initialization of AC->def name mapping - for(const JsonNode &ac : config["ac_mapping"].Vector()) { - int ACid = static_cast(ac["id"].Float()); - std::vector< std::string > toAdd; - - for(const JsonNode &defname : ac["defnames"].Vector()) { - toAdd.push_back(defname.String()); + if(!config["backgrounds"].isNull()) + for(auto & t : config["backgrounds"].Struct()) + { + battleBacks[t.first] = t.second.String(); } - battleACToDef[ACid] = toAdd; + //initialization of AC->def name mapping + if(!config["ac_mapping"].isNull()) + for(const JsonNode &ac : config["ac_mapping"].Vector()) + { + int ACid = static_cast(ac["id"].Float()); + std::vector< std::string > toAdd; + + for(const JsonNode &defname : ac["defnames"].Vector()) + { + toAdd.push_back(defname.String()); + } + + battleACToDef[ACid] = toAdd; + } } } Graphics::Graphics() diff --git a/client/Graphics.h b/client/Graphics.h index 9cc4fd92a..d59ae3fb2 100644 --- a/client/Graphics.h +++ b/client/Graphics.h @@ -87,7 +87,7 @@ public: //towns std::map ERMUtoPicture[GameConstants::F_NUMBER]; //maps building ID to it's picture's name for each town type //for battles - std::vector< std::vector< std::string > > battleBacks; //battleBacks[terType] - vector of possible names for certain terrain type + std::map battleBacks; //maps BattleField to it's picture's name std::map< int, std::vector < std::string > > battleACToDef; //maps AC format to vector of appropriate def names //functions diff --git a/client/battle/CBattleInterface.cpp b/client/battle/CBattleInterface.cpp index e04f36b7a..f1de912ec 100644 --- a/client/battle/CBattleInterface.cpp +++ b/client/battle/CBattleInterface.cpp @@ -200,15 +200,14 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet } else { - auto bfieldType = (int)curInt->cb->battleGetBattlefieldType(); - if (graphics->battleBacks.size() <= bfieldType || bfieldType < 0) - logGlobal->error("%d is not valid battlefield type index!", bfieldType); - else if (graphics->battleBacks[bfieldType].empty()) - logGlobal->error("%d battlefield type does not have any backgrounds!", bfieldType); + auto bfieldType = curInt->cb->battleGetBattlefieldType(); + if(!vstd::contains(graphics->battleBacks, bfieldType)) + { + logGlobal->error("%s is not valid battlefield type!", static_cast(bfieldType)); + } else { - const std::string bgName = *RandomGeneratorUtil::nextItem(graphics->battleBacks[bfieldType], CRandomGenerator::getDefault()); - background = BitmapHandler::loadBitmap(bgName, false); + background = BitmapHandler::loadBitmap(graphics->battleBacks[bfieldType], false); } } diff --git a/config/battles_graphics.json b/config/battles_graphics.json index f07e72ec6..8e37ae7eb 100644 --- a/config/battles_graphics.json +++ b/config/battles_graphics.json @@ -1,32 +1,32 @@ { // backgrounds of terrains battles can be fought on - "backgrounds": [ - "CMBKBCH.BMP", - "CMBKDES.BMP", - "CMBKDRTR.BMP", - "CMBKDRMT.BMP", - "CMBKDRDD.BMP", - "CMBKGRMT.BMP", - "CMBKGRTR.BMP", - "CMBKLAVA.BMP", - "CMBKMAG.BMP", - "CMBKSNMT.BMP", - "CMBKSNTR.BMP", - "CMBKSUB.BMP", - "CMBKSWMP.BMP", - "CMBKFF.BMP", - "CMBKRK.BMP", - "CMBKMC.BMP", - "CMBKLP.BMP", - "CMBKHG.BMP", - "CMBKCF.BMP", - "CMBKEF.BMP", - "CMBKFW.BMP", - "CMBKCUR.BMP", - "CMBKRGH.BMP", - "CMBKBOAT.BMP", - "CMBKDECK.BMP" - ], + "backgrounds": { + "sand_shore": "CMBKBCH.BMP", + "sand_mesas": "CMBKDES.BMP", + "dirt_birches": "CMBKDRTR.BMP", + "dirt_hills": "CMBKDRMT.BMP", + "dirt_pines": "CMBKDRDD.BMP", + "grass_hills": "CMBKGRMT.BMP", + "grass_pines": "CMBKGRTR.BMP", + "lava": "CMBKLAVA.BMP", + "magic_plains": "CMBKMAG.BMP", + "snow_mountains": "CMBKSNMT.BMP", + "snow_trees": "CMBKSNTR.BMP", + "subterranean": "CMBKSUB.BMP", + "swamp_trees": "CMBKSWMP.BMP", + "fiery_fields": "CMBKFF.BMP", + "rocklands": "CMBKRK.BMP", + "magic_clouds": "CMBKMC.BMP", + "lucid_pools": "CMBKLP.BMP", + "holy_ground": "CMBKHG.BMP", + "clover_field": "CMBKCF.BMP", + "evil_fog": "CMBKEF.BMP", + "favorable_winds": "CMBKFW.BMP", + "cursed_ground": "CMBKCUR.BMP", + "rough": "CMBKRGH.BMP", + "ship_to_ship": "CMBKBOAT.BMP", + "ship": "CMBKDECK.BMP" + }, // WoG_Ac_format_to_def_names_mapping "ac_mapping": [ diff --git a/config/obstacles.json b/config/obstacles.json index bee4258e4..22a944f3c 100644 --- a/config/obstacles.json +++ b/config/obstacles.json @@ -25,7 +25,7 @@ { "id" : 1, "allowedTerrain" : ["dirt", "sand", "rough", "subterra"], - "specialBattlefields" : [0], + "specialBattlefields" : ["sand_shore"], "width" : 3, "height" : 2, "blockedTiles" : [0, 1, 2], @@ -45,7 +45,7 @@ { "id" : 3, "allowedTerrain" : ["dirt", "rough"], - "specialBattlefields" : [1], + "specialBattlefields" : ["cursed_ground"], "width" : 2, "height" : 1, "blockedTiles" : [0, 1], @@ -55,7 +55,7 @@ { "id" : 4, "allowedTerrain" : ["dirt", "rough", "subterra"], - "specialBattlefields" : [0, 1], + "specialBattlefields" : ["sand_shore", "cursed_ground"], "width" : 2, "height" : 1, "blockedTiles" : [0, 1], @@ -135,7 +135,7 @@ { "id" : 12, "allowedTerrain" : ["dirt", "rough"], - "specialBattlefields" : [1], + "specialBattlefields" : ["cursed_ground"], "width" : 3, "height" : 3, "blockedTiles" : [0, 1, 2, 3], @@ -145,7 +145,7 @@ { "id" : 13, "allowedTerrain" : ["dirt", "rough"], - "specialBattlefields" : [1], + "specialBattlefields" : ["cursed_ground"], "width" : 3, "height" : 2, "blockedTiles" : [1, 2, -15], @@ -155,7 +155,7 @@ { "id" : 14, "allowedTerrain" : ["dirt", "rough"], - "specialBattlefields" : [1], + "specialBattlefields" : ["cursed_ground"], "width" : 3, "height" : 2, "blockedTiles" : [2, -15, -16], @@ -165,7 +165,7 @@ { "id" : 15, "allowedTerrain" : ["dirt", "rough"], - "specialBattlefields" : [1], + "specialBattlefields" : ["cursed_ground"], "width" : 3, "height" : 3, "blockedTiles" : [1, -16, -33], @@ -215,7 +215,7 @@ { "id" : 20, "allowedTerrain" : ["grass", "swamp"], - "specialBattlefields" : [2], + "specialBattlefields" : ["magic_plains"], "width" : 2, "height" : 2, "blockedTiles" : [0, 1], @@ -235,7 +235,7 @@ { "id" : 22, "allowedTerrain" : ["grass"], - "specialBattlefields" : [2], + "specialBattlefields" : ["magic_plains"], "width" : 6, "height" : 2, "blockedTiles" : [1, 2, 3, 4, -13, -14, -15, -16], @@ -415,7 +415,7 @@ { "id" : 40, "allowedTerrain" : ["rough"], - "specialBattlefields" : [1], + "specialBattlefields" : ["cursed_ground"], "width" : 2, "height" : 2, "blockedTiles" : [0, 1, -16], @@ -425,7 +425,7 @@ { "id" : 41, "allowedTerrain" : ["rough"], - "specialBattlefields" : [1], + "specialBattlefields" : ["cursed_ground"], "width" : 4, "height" : 3, "blockedTiles" : [-14, -15, -16, -32, -33], @@ -435,7 +435,7 @@ { "id" : 42, "allowedTerrain" : ["rough"], - "specialBattlefields" : [1], + "specialBattlefields" : ["cursed_ground"], "width" : 3, "height" : 2, "blockedTiles" : [1, 2, -15, -16], @@ -445,7 +445,7 @@ { "id" : 43, "allowedTerrain" : ["rough"], - "specialBattlefields" : [1], + "specialBattlefields" : ["cursed_ground"], "width" : 3, "height" : 3, "blockedTiles" : [-16, -32, -33], @@ -455,7 +455,7 @@ { "id" : 44, "allowedTerrain" : ["rough"], - "specialBattlefields" : [1], + "specialBattlefields" : ["cursed_ground"], "width" : 3, "height" : 3, "blockedTiles" : [-15, -16, -32], @@ -575,7 +575,7 @@ { "id" : 56, "allowedTerrain" : [], - "specialBattlefields" : [0], + "specialBattlefields" : ["sand_shore"], "width" : 3, "height" : 2, "blockedTiles" : [1, -15, -16], @@ -585,7 +585,7 @@ { "id" : 57, "allowedTerrain" : [], - "specialBattlefields" : [0], + "specialBattlefields" : ["sand_shore"], "width" : 3, "height" : 2, "blockedTiles" : [0, 1, 2], @@ -595,7 +595,7 @@ { "id" : 58, "allowedTerrain" : [], - "specialBattlefields" : [0], + "specialBattlefields" : ["sand_shore"], "width" : 5, "height" : 2, "blockedTiles" : [1, 2, 3, -14, -15, -16], @@ -605,7 +605,7 @@ { "id" : 59, "allowedTerrain" : [], - "specialBattlefields" : [0], + "specialBattlefields" : ["sand_shore"], "width" : 4, "height" : 2, "blockedTiles" : [1, 2, -14, -15], @@ -615,7 +615,7 @@ { "id" : 60, "allowedTerrain" : [], - "specialBattlefields" : [0], + "specialBattlefields" : ["sand_shore"], "width" : 2, "height" : 2, "blockedTiles" : [0, 1, -16], @@ -625,7 +625,7 @@ { "id" : 61, "allowedTerrain" : [], - "specialBattlefields" : [3], + "specialBattlefields" : ["holy_ground"], "width" : 1, "height" : 1, "blockedTiles" : [0], @@ -635,7 +635,7 @@ { "id" : 62, "allowedTerrain" : [], - "specialBattlefields" : [3], + "specialBattlefields" : ["holy_ground"], "width" : 2, "height" : 1, "blockedTiles" : [0, 1], @@ -645,7 +645,7 @@ { "id" : 63, "allowedTerrain" : [], - "specialBattlefields" : [3], + "specialBattlefields" : ["holy_ground"], "width" : 3, "height" : 3, "blockedTiles" : [1], @@ -655,7 +655,7 @@ { "id" : 64, "allowedTerrain" : [], - "specialBattlefields" : [3], + "specialBattlefields" : ["holy_ground"], "width" : 3, "height" : 2, "blockedTiles" : [0, 1, 2], @@ -665,7 +665,7 @@ { "id" : 65, "allowedTerrain" : [], - "specialBattlefields" : [3], + "specialBattlefields" : ["holy_ground"], "width" : 4, "height" : 3, "blockedTiles" : [0, 1, 2, 3], @@ -675,7 +675,7 @@ { "id" : 66, "allowedTerrain" : [], - "specialBattlefields" : [4], + "specialBattlefields" : ["evil_fog"], "width" : 1, "height" : 1, "blockedTiles" : [0], @@ -685,7 +685,7 @@ { "id" : 67, "allowedTerrain" : [], - "specialBattlefields" : [4], + "specialBattlefields" : ["evil_fog"], "width" : 2, "height" : 1, "blockedTiles" : [0, 1], @@ -695,7 +695,7 @@ { "id" : 68, "allowedTerrain" : [], - "specialBattlefields" : [4], + "specialBattlefields" : ["evil_fog"], "width" : 3, "height" : 2, "blockedTiles" : [0, 1, 2], @@ -705,7 +705,7 @@ { "id" : 69, "allowedTerrain" : [], - "specialBattlefields" : [4], + "specialBattlefields" : ["evil_fog"], "width" : 4, "height" : 2, "blockedTiles" : [1, 2], @@ -715,7 +715,7 @@ { "id" : 70, "allowedTerrain" : [], - "specialBattlefields" : [4], + "specialBattlefields" : ["evil_fog"], "width" : 6, "height" : 2, "blockedTiles" : [1, 2, 3, -12, -13], @@ -725,7 +725,7 @@ { "id" : 71, "allowedTerrain" : [], - "specialBattlefields" : [5], + "specialBattlefields" : ["clover_field"], "width" : 1, "height" : 1, "blockedTiles" : [0], @@ -735,7 +735,7 @@ { "id" : 72, "allowedTerrain" : [], - "specialBattlefields" : [5], + "specialBattlefields" : ["clover_field"], "width" : 3, "height" : 1, "blockedTiles" : [0, 1, 2], @@ -745,7 +745,7 @@ { "id" : 73, "allowedTerrain" : [], - "specialBattlefields" : [5], + "specialBattlefields" : ["clover_field"], "width" : 3, "height" : 2, "blockedTiles" : [1, 2, -15, -16], @@ -755,7 +755,7 @@ { "id" : 74, "allowedTerrain" : [], - "specialBattlefields" : [5], + "specialBattlefields" : ["clover_field"], "width" : 4, "height" : 2, "blockedTiles" : [0, 1, 2, -14, -15, -16], @@ -765,7 +765,7 @@ { "id" : 75, "allowedTerrain" : [], - "specialBattlefields" : [6], + "specialBattlefields" : ["lucid_pools"], "width" : 1, "height" : 1, "blockedTiles" : [0], @@ -775,7 +775,7 @@ { "id" : 76, "allowedTerrain" : [], - "specialBattlefields" : [6], + "specialBattlefields" : ["lucid_pools"], "width" : 2, "height" : 1, "blockedTiles" : [0, 1], @@ -785,7 +785,7 @@ { "id" : 77, "allowedTerrain" : [], - "specialBattlefields" : [6], + "specialBattlefields" : ["lucid_pools"], "width" : 3, "height" : 2, "blockedTiles" : [0, -15, -16], @@ -795,7 +795,7 @@ { "id" : 78, "allowedTerrain" : [], - "specialBattlefields" : [6], + "specialBattlefields" : ["lucid_pools"], "width" : 5, "height" : 2, "blockedTiles" : [1, 2, 3, -13, -14, -15, -16], @@ -805,7 +805,7 @@ { "id" : 79, "allowedTerrain" : [], - "specialBattlefields" : [7], + "specialBattlefields" : ["fiery_fields"], "width" : 1, "height" : 1, "blockedTiles" : [0], @@ -815,7 +815,7 @@ { "id" : 80, "allowedTerrain" : [], - "specialBattlefields" : [7], + "specialBattlefields" : ["fiery_fields"], "width" : 2, "height" : 1, "blockedTiles" : [0, 1], @@ -825,7 +825,7 @@ { "id" : 81, "allowedTerrain" : [], - "specialBattlefields" : [7], + "specialBattlefields" : ["fiery_fields"], "width" : 3, "height" : 2, "blockedTiles" : [0, 1, 2, -15], @@ -835,7 +835,7 @@ { "id" : 82, "allowedTerrain" : [], - "specialBattlefields" : [7], + "specialBattlefields" : ["fiery_fields"], "width" : 4, "height" : 2, "blockedTiles" : [1, 2, 3, -15, -16], @@ -845,7 +845,7 @@ { "id" : 83, "allowedTerrain" : [], - "specialBattlefields" : [7], + "specialBattlefields" : ["fiery_fields"], "width" : 3, "height" : 3, "blockedTiles" : [0, 1, 2, 3, -14, -15, -16], @@ -855,7 +855,7 @@ { "id" : 84, "allowedTerrain" : [], - "specialBattlefields" : [8], + "specialBattlefields" : ["rocklands"], "width" : 1, "height" : 1, "blockedTiles" : [0], @@ -865,7 +865,7 @@ { "id" : 85, "allowedTerrain" : [], - "specialBattlefields" : [8], + "specialBattlefields" : ["rocklands"], "width" : 2, "height" : 1, "blockedTiles" : [0, 1], @@ -875,7 +875,7 @@ { "id" : 86, "allowedTerrain" : [], - "specialBattlefields" : [8], + "specialBattlefields" : ["rocklands"], "width" : 3, "height" : 1, "blockedTiles" : [0, 1, 2], @@ -885,7 +885,7 @@ { "id" : 87, "allowedTerrain" : [], - "specialBattlefields" : [8], + "specialBattlefields" : ["rocklands"], "width" : 4, "height" : 2, "blockedTiles" : [1, 2, 3, -15, -16], @@ -895,7 +895,7 @@ { "id" : 88, "allowedTerrain" : [], - "specialBattlefields" : [9], + "specialBattlefields" : ["magic_clouds"], "width" : 1, "height" : 1, "blockedTiles" : [0], @@ -905,7 +905,7 @@ { "id" : 89, "allowedTerrain" : [], - "specialBattlefields" : [9], + "specialBattlefields" : ["magic_clouds"], "width" : 2, "height" : 2, "blockedTiles" : [1, -16], @@ -915,7 +915,7 @@ { "id" : 90, "allowedTerrain" : [], - "specialBattlefields" : [9], + "specialBattlefields" : ["magic_clouds"], "width" : 4, "height" : 2, "blockedTiles" : [0, 1, -14, -15], @@ -1053,7 +1053,7 @@ { "id" : 14, "allowedTerrain" : ["rough"], - "specialBattlefields" : [1], + "specialBattlefields" : ["cursed_ground"], "width" : 186, "height" : 212, "blockedTiles" : [55, 72, 90, 107, 125, 126, 127, 128, 129, 130, 131, 132], @@ -1062,7 +1062,7 @@ { "id" : 15, "allowedTerrain" : ["rough"], - "specialBattlefields" : [1], + "specialBattlefields" : ["cursed_ground"], "width" : 347, "height" : 174, "blockedTiles" : [41, 59, 76, 94, 111, 129, 143, 144, 145], @@ -1071,7 +1071,7 @@ { "id" : 16, "allowedTerrain" : ["rough"], - "specialBattlefields" : [1], + "specialBattlefields" : ["cursed_ground"], "width" : 294, "height" : 169, "blockedTiles" : [40, 41, 42, 43, 58, 75, 93, 110, 128, 145], @@ -1080,7 +1080,7 @@ { "id" : 17, "allowedTerrain" : ["rough"], - "specialBattlefields" : [1], + "specialBattlefields" : ["cursed_ground"], "width" : 165, "height" : 257, "blockedTiles" : [72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 89, 105], @@ -1089,7 +1089,7 @@ { "id" : 18, "allowedTerrain" : ["rough"], - "specialBattlefields" : [1], + "specialBattlefields" : ["cursed_ground"], "width" : 208, "height" : 268, "blockedTiles" : [72, 73, 74, 75, 76, 77, 78, 79, 80, 90, 91, 92, 93, 94, 95, 96, 97], @@ -1098,7 +1098,7 @@ { "id" : 19, "allowedTerrain" : ["rough"], - "specialBattlefields" : [1], + "specialBattlefields" : ["cursed_ground"], "width" : 252, "height" : 254, "blockedTiles" : [73, 74, 75, 76, 77, 78, 91, 92, 93, 94], @@ -1107,7 +1107,7 @@ { "id" : 20, "allowedTerrain" : ["rough"], - "specialBattlefields" : [1], + "specialBattlefields" : ["cursed_ground"], "width" : 278, "height" : 128, "blockedTiles" : [23, 40, 58, 75, 93, 110, 128, 145, 163], @@ -1116,7 +1116,7 @@ { "id" : 21, "allowedTerrain" : ["rough"], - "specialBattlefields" : [1], + "specialBattlefields" : ["cursed_ground"], "width" : 208, "height" : 268, "blockedTiles" : [72, 73, 74, 75, 76, 77, 78, 79, 80, 90, 91, 92, 93, 94, 95, 96, 97], @@ -1125,7 +1125,7 @@ { "id" : 22, "allowedTerrain" : ["rough"], - "specialBattlefields" : [1], + "specialBattlefields" : ["cursed_ground"], "width" : 168, "height" : 212, "blockedTiles" : [73, 74, 75, 76, 77, 78, 79, 90, 91, 92, 93, 94, 95, 96, 97, 106, 107, 108, 109, 110, 111, 112], @@ -1134,7 +1134,7 @@ { "id" : 23, "allowedTerrain" : [], - "specialBattlefields" : [0], + "specialBattlefields" : ["sand_shore"], "width" : 147, "height" : 264, "blockedTiles" : [72, 73, 74, 75, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98], @@ -1143,7 +1143,7 @@ { "id" : 24, "allowedTerrain" : [], - "specialBattlefields" : [0], + "specialBattlefields" : ["sand_shore"], "width" : 178, "height" : 262, "blockedTiles" : [71, 72, 73, 74, 75, 76, 77, 78, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98], @@ -1152,7 +1152,7 @@ { "id" : 25, "allowedTerrain" : [], - "specialBattlefields" : [0], + "specialBattlefields" : ["sand_shore"], "width" : 173, "height" : 257, "blockedTiles" : [72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 89, 90, 105, 106], @@ -1161,7 +1161,7 @@ { "id" : 26, "allowedTerrain" : [], - "specialBattlefields" : [0], + "specialBattlefields" : ["sand_shore"], "width" : 241, "height" : 272, "blockedTiles" : [73, 91, 108, 109, 110, 111, 112, 113], @@ -1170,7 +1170,7 @@ { "id" : 27, "allowedTerrain" : [], - "specialBattlefields" : [0], + "specialBattlefields" : ["sand_shore"], "width" : 261, "height" : 129, "blockedTiles" : [27, 28, 43, 44, 60, 61, 76, 77, 93, 94, 109, 110, 126, 127, 142, 143, 159], @@ -1179,7 +1179,7 @@ { "id" : 28, "allowedTerrain" : [], - "specialBattlefields" : [0], + "specialBattlefields" : ["sand_shore"], "width" : 180, "height" : 154, "blockedTiles" : [22, 38, 39, 40, 44, 45, 46, 55, 56, 57, 62, 63, 123, 124, 125, 130, 131, 140, 141, 146, 147, 148], @@ -1188,7 +1188,7 @@ { "id" : 29, "allowedTerrain" : [], - "specialBattlefields" : [5], + "specialBattlefields" : ["clover_field"], "width" : 304, "height" : 264, "blockedTiles" : [76, 77, 92, 93, 94, 95, 109, 110, 111], @@ -1197,7 +1197,7 @@ { "id" : 30, "allowedTerrain" : [], - "specialBattlefields" : [6], + "specialBattlefields" : ["lucid_pools"], "width" : 256, "height" : 257, "blockedTiles" : [76, 77, 78, 92, 93, 94, 107, 108, 109], @@ -1206,7 +1206,7 @@ { "id" : 31, "allowedTerrain" : [], - "specialBattlefields" : [7], + "specialBattlefields" : ["fiery_fields"], "width" : 257, "height" : 255, "blockedTiles" : [76, 77, 91, 92, 93, 94, 95, 108, 109, 110, 111], @@ -1215,7 +1215,7 @@ { "id" : 32, "allowedTerrain" : [], - "specialBattlefields" : [8], + "specialBattlefields" : ["rocklands"], "width" : 277, "height" : 218, "blockedTiles" : [60, 61, 75, 76, 77, 91, 92, 93, 94, 95], @@ -1224,7 +1224,7 @@ { "id" : 33, "allowedTerrain" : [], - "specialBattlefields" : [9], + "specialBattlefields" : ["magic_clouds"], "width" : 300, "height" : 214, "blockedTiles" : [59, 60, 74, 75, 76, 93, 94, 95, 111, 112], diff --git a/config/terrains.json b/config/terrains.json index b6c1b0bf6..c62e2ee36 100644 --- a/config/terrains.json +++ b/config/terrains.json @@ -6,7 +6,9 @@ "minimapBlocked" : [ 57, 40, 8 ], "music" : "Dirt.mp3", "tiles" : "DIRTTL", - "code" : "dt" + "code" : "dt", + "battleFields" : ["dirt_birches", "dirt_hills", "dirt_pines"], + "terrainViewPatterns" : "dirt" }, "sand" : { @@ -15,7 +17,10 @@ "minimapBlocked" : [ 165, 158, 107 ], "music" : "Sand.mp3", "tiles" : "SANDTL", - "code" : "sa" + "code" : "sa", + "battleFields" : ["sand_mesas"], + "transitionRequired" : true, + "terrainViewPatterns" : "sand" }, "grass" : { @@ -24,7 +29,8 @@ "minimapBlocked" : [ 0, 48, 0 ], "music" : "Grass.mp3", "tiles" : "GRASTL", - "code" : "gr" + "code" : "gr", + "battleFields" : ["grass_hills", "grass_pines"] }, "snow" : { @@ -33,7 +39,8 @@ "minimapBlocked" : [ 140, 158, 156 ], "music" : "Snow.mp3", "tiles" : "SNOWTL", - "code" : "sn" + "code" : "sn", + "battleFields" : ["snow_mountains", "snow_trees"] }, "swamp" : { @@ -42,7 +49,8 @@ "minimapBlocked" : [ 33, 89, 66 ], "music" : "Swamp.mp3", "tiles" : "SWMPTL", - "code" : "sw" + "code" : "sw", + "battleFields" : ["swamp_trees"] }, "rough" : { @@ -51,7 +59,8 @@ "minimapBlocked" : [ 99, 81, 33 ], "music" : "Rough.mp3", "tiles" : "ROUGTL", - "code" : "rg" + "code" : "rg", + "battleFields" : ["rough"] }, "subterra" : { @@ -61,7 +70,8 @@ "music" : "Underground.mp3", "tiles" : "SUBBTL", "type" : "SUB", - "code" : "sb" + "code" : "sb", + "battleFields" : ["subterranean"] }, "lava" : { @@ -70,7 +80,8 @@ "minimapBlocked" : [ 41, 40, 41 ], "music" : "Lava.mp3", "tiles" : "LAVATL", - "code" : "lv" + "code" : "lv", + "battleFields" : ["lava"] }, "water" : { @@ -80,7 +91,10 @@ "music" : "Water.mp3", "tiles" : "WATRTL", "type" : "WATER", - "code" : "wt" + "code" : "wt", + "battleFields" : ["ship"], + "transitionRequired" : true, + "terrainViewPatterns" : "water" }, "rock" : { @@ -90,6 +104,9 @@ "music" : "Underground.mp3", // Impossible in H3 "tiles" : "ROCKTL", "type" : "ROCK", - "code" : "rc" + "code" : "rc", + "battleFields" : ["rocklands"], + "transitionRequired" : true, + "terrainViewPatterns" : "rock" } } diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index a70f7a998..abbf72ce8 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -1894,17 +1894,17 @@ void CGameState::initVisitingAndGarrisonedHeroes() } } -BFieldType CGameState::battleGetBattlefieldType(int3 tile, CRandomGenerator & rand) +BattleField CGameState::battleGetBattlefieldType(int3 tile, CRandomGenerator & rand) { if(!tile.valid() && curB) tile = curB->tile; else if(!tile.valid() && !curB) - return BFieldType::NONE; + return BattleField::NONE; const TerrainTile &t = map->getTile(tile); //fight in mine -> subterranean if(dynamic_cast(t.visitableObjects.front())) - return BFieldType::SUBTERRANEAN; + return BattleField("subterranean"); for(auto &obj : map->objects) { @@ -1915,56 +1915,32 @@ BFieldType CGameState::battleGetBattlefieldType(int3 tile, CRandomGenerator & ra switch(obj->ID) { case Obj::CLOVER_FIELD: - return BFieldType::CLOVER_FIELD; + return BattleField("clover_field"); case Obj::CURSED_GROUND1: case Obj::CURSED_GROUND2: - return BFieldType::CURSED_GROUND; + return BattleField("cursed_ground"); case Obj::EVIL_FOG: - return BFieldType::EVIL_FOG; + return BattleField("evil_fog"); case Obj::FAVORABLE_WINDS: - return BFieldType::FAVORABLE_WINDS; + return BattleField("favorable_winds"); case Obj::FIERY_FIELDS: - return BFieldType::FIERY_FIELDS; + return BattleField("fiery_fields"); case Obj::HOLY_GROUNDS: - return BFieldType::HOLY_GROUND; + return BattleField("holy_ground"); case Obj::LUCID_POOLS: - return BFieldType::LUCID_POOLS; + return BattleField("lucid_pools"); case Obj::MAGIC_CLOUDS: - return BFieldType::MAGIC_CLOUDS; + return BattleField("magic_clouds"); case Obj::MAGIC_PLAINS1: case Obj::MAGIC_PLAINS2: - return BFieldType::MAGIC_PLAINS; + return BattleField("magic_plains"); case Obj::ROCKLANDS: - return BFieldType::ROCKLANDS; + return BattleField("rocklands"); } } if(map->isCoastalTile(tile)) //coastal tile is always ground - return BFieldType::SAND_SHORE; - - if(t.terType == Terrain("dirt")) - return BFieldType(rand.nextInt(3, 5)); - if(t.terType == Terrain("sand")) - return BFieldType::SAND_MESAS; //TODO: coast support - if(t.terType == Terrain("grass")) - return BFieldType(rand.nextInt(6, 7)); - if(t.terType == Terrain("snow")) - return BFieldType(rand.nextInt(10, 11)); - if(t.terType == Terrain("swamp")) - return BFieldType::SWAMP_TREES; - if(t.terType == Terrain("rough")) - return BFieldType::ROUGH; - if(t.terType.isUnderground()) - return BFieldType::SUBTERRANEAN; - if(t.terType == Terrain("lava")) - return BFieldType::LAVA; - if(t.terType.isWater()) - return BFieldType::SHIP; - if(!t.terType.isPassable()) - return BFieldType::ROCKLANDS; + return BattleField("sand_shore"); - //TODO: STUB, support new battlegrounds - return BFieldType::DIRT_HILLS; - - return BFieldType::NONE; + return *RandomGeneratorUtil::nextItem(Terrain::Manager::getInfo(t.terType).battleFields, rand); } UpgradeInfo CGameState::getUpgradeInfo(const CStackInstance &stack) diff --git a/lib/CGameState.h b/lib/CGameState.h index d4fba20c9..84e3077b3 100644 --- a/lib/CGameState.h +++ b/lib/CGameState.h @@ -179,7 +179,7 @@ public: void giveHeroArtifact(CGHeroInstance *h, ArtifactID aid); void apply(CPack *pack); - BFieldType battleGetBattlefieldType(int3 tile, CRandomGenerator & rand); + BattleField battleGetBattlefieldType(int3 tile, CRandomGenerator & rand); UpgradeInfo getUpgradeInfo(const CStackInstance &stack); PlayerRelations::PlayerRelations getPlayerRelations(PlayerColor color1, PlayerColor color2); bool checkForVisitableDir(const int3 & src, const int3 & dst) const; //check if src tile is visitable from dst tile diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index 107fe81cd..74093e959 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -177,9 +177,9 @@ std::vector CObstacleInfo::getBlocked(BattleHex hex) const return ret; } -bool CObstacleInfo::isAppropriate(Terrain terrainType, int specialBattlefield) const +bool CObstacleInfo::isAppropriate(const Terrain & terrainType, const BattleField & specialBattlefield) const { - if(specialBattlefield != -1) + if(!allowedSpecialBfields.empty() && specialBattlefield != BattleField::NONE) return vstd::contains(allowedSpecialBfields, specialBattlefield); return vstd::contains(allowedTerrains, terrainType); @@ -817,28 +817,35 @@ void CHeroHandler::loadExperience() void CHeroHandler::loadObstacles() { - auto loadObstacles = [](const JsonNode &node, bool absolute, std::map &out) + auto loadObstacles = [](const JsonNode & node, bool absolute, std::vector & out) { for(const JsonNode &obs : node.Vector()) { - int ID = static_cast(obs["id"].Float()); - CObstacleInfo & obi = out[ID]; - obi.ID = ID; + out.emplace_back(); + CObstacleInfo & obi = out.back(); obi.defName = obs["defname"].String(); obi.width = static_cast(obs["width"].Float()); obi.height = static_cast(obs["height"].Float()); for(auto & t : obs["allowedTerrain"].Vector()) obi.allowedTerrains.emplace_back(t.String()); - obi.allowedSpecialBfields = obs["specialBattlefields"].convertTo >(); + for(auto & t : obs["specialBattlefields"].Vector()) + obi.allowedSpecialBfields.emplace_back(t.String()); obi.blockedTiles = obs["blockedTiles"].convertTo >(); obi.isAbsoluteObstacle = absolute; } }; - - const JsonNode config(ResourceID("config/obstacles.json")); - loadObstacles(config["obstacles"], false, obstacles); - loadObstacles(config["absoluteObstacles"], true, absoluteObstacles); - //loadObstacles(config["moats"], true, moats); + + auto allConfigs = VLC->modh->getActiveMods(); + allConfigs.insert(allConfigs.begin(), "core"); + for(auto & mod : allConfigs) + { + if(!CResourceHandler::get(mod)->existsResource(ResourceID("config/obstacles.json"))) + continue; + + const JsonNode config(mod, ResourceID("config/obstacles.json")); + loadObstacles(config["obstacles"], false, obstacles); + loadObstacles(config["absoluteObstacles"], true, absoluteObstacles); + } } /// convert h3-style ID (e.g. Gobin Wolf Rider) to vcmi (e.g. goblinWolfRider) diff --git a/lib/CHeroHandler.h b/lib/CHeroHandler.h index 14d2b1b8a..674dabb0a 100644 --- a/lib/CHeroHandler.h +++ b/lib/CHeroHandler.h @@ -27,6 +27,7 @@ class JsonNode; class CRandomGenerator; class JsonSerializeFormat; class Terrain; +class BattleField; struct SSpecialtyInfo { si32 type; @@ -224,10 +225,9 @@ public: struct DLL_LINKAGE CObstacleInfo { - si32 ID; std::string defName; std::vector allowedTerrains; - std::vector allowedSpecialBfields; + std::vector allowedSpecialBfields; ui8 isAbsoluteObstacle; //there may only one such obstacle in battle and its position is always the same si32 width, height; //how much space to the right and up is needed to place obstacle (affects only placement algorithm) @@ -235,11 +235,10 @@ struct DLL_LINKAGE CObstacleInfo std::vector getBlocked(BattleHex hex) const; //returns vector of hexes blocked by obstacle when it's placed on hex 'hex' - bool isAppropriate(Terrain terrainType, int specialBattlefield = -1) const; + bool isAppropriate(const Terrain & terrainType, const BattleField & specialBattlefield) const; template void serialize(Handler &h, const int version) { - h & ID; h & defName; h & allowedTerrains; h & allowedSpecialBfields; @@ -316,8 +315,8 @@ public: }; std::vector ballistics; //info about ballistics ability per level; [0] - none; [1] - basic; [2] - adv; [3] - expert - std::map obstacles; //info about obstacles that may be placed on battlefield - std::map absoluteObstacles; //info about obstacles that may be placed on battlefield + std::vector obstacles; //info about obstacles that may be placed on battlefield + std::vector absoluteObstacles; //info about obstacles that may be placed on battlefield ui32 level(ui64 experience) const; //calculates level corresponding to given experience amount ui64 reqExp(ui32 level) const; //calculates experience required for given level diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 34777ad55..636766ed9 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -844,26 +844,6 @@ namespace SecSkillLevel }; } - -//follows ERM BI (battle image) format -namespace BattlefieldBI -{ - enum BattlefieldBI - { - NONE = -1, - COASTAL, - CURSED_GROUND, - MAGIC_PLAINS, - HOLY_GROUND, - EVIL_FOG, - CLOVER_FIELD, - LUCID_POOLS, - FIERY_FIELDS, - ROCKLANDS, - MAGIC_CLOUDS - }; -} - namespace Date { enum EDateType @@ -940,29 +920,6 @@ DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EPathfindingLayer ID_LIKE_OPERATORS(EPathfindingLayer, EPathfindingLayer::EEPathfindingLayer) -class BFieldType -{ -public: - // 1. sand/shore 2. sand/mesas 3. dirt/birches 4. dirt/hills 5. dirt/pines 6. grass/hills 7. grass/pines - //8. lava 9. magic plains 10. snow/mountains 11. snow/trees 12. subterranean 13. swamp/trees 14. fiery fields - //15. rock lands 16. magic clouds 17. lucid pools 18. holy ground 19. clover field 20. evil fog - //21. "favorable winds" text on magic plains background 22. cursed ground 23. rough 24. ship to ship 25. ship - enum EBFieldType {NONE = -1, NONE2, SAND_SHORE, SAND_MESAS, DIRT_BIRCHES, DIRT_HILLS, DIRT_PINES, GRASS_HILLS, - GRASS_PINES, LAVA, MAGIC_PLAINS, SNOW_MOUNTAINS, SNOW_TREES, SUBTERRANEAN, SWAMP_TREES, FIERY_FIELDS, - ROCKLANDS, MAGIC_CLOUDS, LUCID_POOLS, HOLY_GROUND, CLOVER_FIELD, EVIL_FOG, FAVORABLE_WINDS, CURSED_GROUND, - ROUGH, SHIP_TO_SHIP, SHIP - }; - - BFieldType(EBFieldType _num = NONE) : num(_num) - {} - - ID_LIKE_CLASS_COMMON(BFieldType, EBFieldType) - - EBFieldType num; -}; - -ID_LIKE_OPERATORS(BFieldType, BFieldType::EBFieldType) - namespace EPlayerStatus { enum EStatus {WRONG = -1, INGAME, LOSER, WINNER}; diff --git a/lib/Terrain.cpp b/lib/Terrain.cpp index 17f8de861..4a669fc86 100644 --- a/lib/Terrain.cpp +++ b/lib/Terrain.cpp @@ -18,6 +18,8 @@ const Terrain Terrain::ANY("ANY"); +const BattleField BattleField::NONE(""); + Terrain Terrain::createTerrainTypeH3M(int tId) { static std::array terrainsH3M @@ -106,6 +108,25 @@ Terrain::Manager::Manager() assert(info.typeCode.length() == 2); } + if(!terr.second["battleFields"].isNull()) + { + for(auto & t : terr.second["battleFields"].Vector()) + { + info.battleFields.emplace_back(t.String()); + } + } + + info.transitionRequired = false; + if(!terr.second["transitionRequired"].isNull()) + { + info.transitionRequired = terr.second["transitionRequired"].Bool(); + } + + info.terrainViewPatterns = "normal"; + if(!terr.second["terrainViewPatterns"].isNull()) + { + info.terrainViewPatterns = terr.second["terrainViewPatterns"].String(); + } terrainInfo[Terrain(terr.first)] = info; } @@ -202,3 +223,31 @@ bool Terrain::isNative() const { return name.empty(); } +bool Terrain::isTransitionRequired() const +{ + return Terrain::Manager::getInfo(*this).transitionRequired; +} + +bool operator==(const BattleField & l, const BattleField & r) +{ + return l.name == r.name; +} + +bool operator!=(const BattleField & l, const BattleField & r) +{ + return l.name != r.name; +} + +bool operator<(const BattleField & l, const BattleField & r) +{ + return l.name < r.name; +} +BattleField::operator std::string() const +{ + return name; +} + +int BattleField::hash() const +{ + return std::hash{}(name); +} diff --git a/lib/Terrain.h b/lib/Terrain.h index d173f2ab7..5e69fa384 100644 --- a/lib/Terrain.h +++ b/lib/Terrain.h @@ -13,6 +13,41 @@ #include "ConstTransitivePtr.h" #include "JsonNode.h" +class DLL_LINKAGE BattleField +{ +public: + // 1. sand/shore 2. sand/mesas 3. dirt/birches 4. dirt/hills 5. dirt/pines 6. grass/hills 7. grass/pines + //8. lava 9. magic plains 10. snow/mountains 11. snow/trees 12. subterranean 13. swamp/trees 14. fiery fields + //15. rock lands 16. magic clouds 17. lucid pools 18. holy ground 19. clover field 20. evil fog + //21. "favorable winds" text on magic plains background 22. cursed ground 23. rough 24. ship to ship 25. ship + /*enum EBFieldType {NONE = -1, NONE2, SAND_SHORE, SAND_MESAS, DIRT_BIRCHES, DIRT_HILLS, DIRT_PINES, GRASS_HILLS, + GRASS_PINES, LAVA, MAGIC_PLAINS, SNOW_MOUNTAINS, SNOW_TREES, SUBTERRANEAN, SWAMP_TREES, FIERY_FIELDS, + ROCKLANDS, MAGIC_CLOUDS, LUCID_POOLS, HOLY_GROUND, CLOVER_FIELD, EVIL_FOG, FAVORABLE_WINDS, CURSED_GROUND, + ROUGH, SHIP_TO_SHIP, SHIP + };*/ + + BattleField(const std::string & type = "") : name(type) + {} + + static const BattleField NONE; + + DLL_LINKAGE friend bool operator==(const BattleField & l, const BattleField & r); + DLL_LINKAGE friend bool operator!=(const BattleField & l, const BattleField & r); + DLL_LINKAGE friend bool operator<(const BattleField & l, const BattleField & r); + + operator std::string() const; + int hash() const; + + template void serialize(Handler &h, const int version) + { + h & name; + } + +protected: + + std::string name; +}; + class DLL_LINKAGE Terrain { public: @@ -27,14 +62,17 @@ public: }; int moveCost; + bool transitionRequired; std::array minimapBlocked; std::array minimapUnblocked; std::string musicFilename; std::string tilesFilename; std::string terrainText; std::string typeCode; + std::string terrainViewPatterns; int horseSoundId; Type type; + std::vector battleFields; }; class DLL_LINKAGE Manager @@ -77,6 +115,8 @@ public: bool isPassable() const; //ROCK bool isUnderground() const; bool isNative() const; + bool isTransitionRequired() const; + operator std::string() const; diff --git a/lib/battle/BattleInfo.cpp b/lib/battle/BattleInfo.cpp index bc119bde7..2dd2b173c 100644 --- a/lib/battle/BattleInfo.cpp +++ b/lib/battle/BattleInfo.cpp @@ -187,7 +187,7 @@ struct RangeGenerator std::function myRand; }; -BattleInfo * BattleInfo::setupBattle(int3 tile, Terrain terrain, BFieldType battlefieldType, const CArmedInstance * armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance * town) +BattleInfo * BattleInfo::setupBattle(const int3 & tile, const Terrain & terrain, const BattleField & battlefieldType, const CArmedInstance * armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance * town) { CMP_stack cmpst; auto curB = new BattleInfo(); @@ -239,24 +239,24 @@ BattleInfo * BattleInfo::setupBattle(int3 tile, Terrain terrain, BFieldType batt //randomize obstacles if (town == nullptr && !creatureBank) //do it only when it's not siege and not creature bank { - const int ABSOLUTE_OBSTACLES_COUNT = 34, USUAL_OBSTACLES_COUNT = 91; //shouldn't be changes if we want H3-like obstacle placement + const int ABSOLUTE_OBSTACLES_COUNT = VLC->heroh->absoluteObstacles.size(); + const int USUAL_OBSTACLES_COUNT = VLC->heroh->obstacles.size(); //shouldn't be changes if we want H3-like obstacle placement RandGen r; auto ourRand = [&](){ return r.rand(); }; r.srand(tile); r.rand(1,8); //battle sound ID to play... can't do anything with it here int tilesToBlock = r.rand(5,12); - const int specialBattlefield = battlefieldTypeToBI(battlefieldType); std::vector blockedTiles; auto appropriateAbsoluteObstacle = [&](int id) { - return VLC->heroh->absoluteObstacles[id].isAppropriate(curB->terrainType, specialBattlefield); + return VLC->heroh->absoluteObstacles[id].isAppropriate(curB->terrainType, battlefieldType); }; auto appropriateUsualObstacle = [&](int id) -> bool { - return VLC->heroh->obstacles[id].isAppropriate(curB->terrainType, specialBattlefield); + return VLC->heroh->obstacles[id].isAppropriate(curB->terrainType, battlefieldType); }; if(r.rand(1,100) <= 40) //put cliff-like obstacle @@ -460,71 +460,60 @@ BattleInfo * BattleInfo::setupBattle(int3 tile, Terrain terrain, BFieldType batt //giving terrain overlay premies int bonusSubtype = -1; - switch(battlefieldType) - { - case BFieldType::MAGIC_PLAINS: - { - bonusSubtype = 0; - } - FALLTHROUGH - case BFieldType::FIERY_FIELDS: - { - if(bonusSubtype == -1) bonusSubtype = 1; - } - FALLTHROUGH - case BFieldType::ROCKLANDS: - { - if(bonusSubtype == -1) bonusSubtype = 8; - } - FALLTHROUGH - case BFieldType::MAGIC_CLOUDS: - { - if(bonusSubtype == -1) bonusSubtype = 2; - } - FALLTHROUGH - case BFieldType::LUCID_POOLS: - { - if(bonusSubtype == -1) bonusSubtype = 4; - } - { //common part for cases 9, 14, 15, 16, 17 - curB->addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::MAGIC_SCHOOL_SKILL, Bonus::TERRAIN_OVERLAY, 3, battlefieldType, bonusSubtype)); - break; - } - case BFieldType::HOLY_GROUND: - { - std::string goodArmyDesc = VLC->generaltexth->arraytxt[123]; - goodArmyDesc.erase(goodArmyDesc.size() - 2, 2); //omitting hardcoded +1 in description - std::string evilArmyDesc = VLC->generaltexth->arraytxt[124]; - evilArmyDesc.erase(evilArmyDesc.size() - 2, 2); - curB->addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::TERRAIN_OVERLAY, +1, battlefieldType, goodArmyDesc, 0)->addLimiter(good)); - curB->addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::TERRAIN_OVERLAY, -1, battlefieldType, evilArmyDesc, 0)->addLimiter(evil)); - break; - } - case BFieldType::CLOVER_FIELD: - { //+2 luck bonus for neutral creatures - std::string desc = VLC->generaltexth->arraytxt[83]; - desc.erase(desc.size() - 2, 2); - curB->addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::LUCK, Bonus::TERRAIN_OVERLAY, +2, battlefieldType, desc, 0)->addLimiter(neutral)); - break; - } - case BFieldType::EVIL_FOG: - { - std::string goodArmyDesc = VLC->generaltexth->arraytxt[126]; - goodArmyDesc.erase(goodArmyDesc.size() - 2, 2); - std::string evilArmyDesc = VLC->generaltexth->arraytxt[125]; - evilArmyDesc.erase(evilArmyDesc.size() - 2, 2); - curB->addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::TERRAIN_OVERLAY, -1, battlefieldType, goodArmyDesc, 0)->addLimiter(good)); - curB->addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::TERRAIN_OVERLAY, +1, battlefieldType, evilArmyDesc, 0)->addLimiter(evil)); - break; - } - case BFieldType::CURSED_GROUND: - { - curB->addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::NO_MORALE, Bonus::TERRAIN_OVERLAY, 0, battlefieldType, VLC->generaltexth->arraytxt[112], 0)); - curB->addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::NO_LUCK, Bonus::TERRAIN_OVERLAY, 0, battlefieldType, VLC->generaltexth->arraytxt[81], 0)); - curB->addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::BLOCK_MAGIC_ABOVE, Bonus::TERRAIN_OVERLAY, 1, battlefieldType, 0, Bonus::INDEPENDENT_MIN)); - break; - } + if(battlefieldType == BattleField("magic_plains")) + { + bonusSubtype = 0; + } + if(battlefieldType == BattleField("fiery_fields")) + { + if(bonusSubtype == -1) bonusSubtype = 1; + } + if(battlefieldType == BattleField("rocklands")) + { + if(bonusSubtype == -1) bonusSubtype = 8; + } + if(battlefieldType == BattleField("magic_clouds")) + { + if(bonusSubtype == -1) bonusSubtype = 2; + } + if(battlefieldType == BattleField("lucid_pools")) + { + if(bonusSubtype == -1) bonusSubtype = 4; + } + if(bonusSubtype == -1) + { //common part for cases 9, 14, 15, 16, 17 + curB->addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::MAGIC_SCHOOL_SKILL,Bonus::TERRAIN_OVERLAY, 3, battlefieldType.hash(), bonusSubtype)); + } + else if(battlefieldType == BattleField("holy_ground")) + { + std::string goodArmyDesc = VLC->generaltexth->arraytxt[123]; + goodArmyDesc.erase(goodArmyDesc.size() - 2, 2); //omitting hardcoded +1 in description + std::string evilArmyDesc = VLC->generaltexth->arraytxt[124]; + evilArmyDesc.erase(evilArmyDesc.size() - 2, 2); + curB->addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::TERRAIN_OVERLAY, +1, battlefieldType.hash(), goodArmyDesc, 0)->addLimiter(good)); + curB->addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::TERRAIN_OVERLAY, -1, battlefieldType.hash(), evilArmyDesc, 0)->addLimiter(evil)); + } + else if(battlefieldType == BattleField("clover_field")) + { //+2 luck bonus for neutral creatures + std::string desc = VLC->generaltexth->arraytxt[83]; + desc.erase(desc.size() - 2, 2); + curB->addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::LUCK, Bonus::TERRAIN_OVERLAY, +2, battlefieldType.hash(), desc, 0)->addLimiter(neutral)); + } + else if(battlefieldType == BattleField("evil_fog")) + { + std::string goodArmyDesc = VLC->generaltexth->arraytxt[126]; + goodArmyDesc.erase(goodArmyDesc.size() - 2, 2); + std::string evilArmyDesc = VLC->generaltexth->arraytxt[125]; + evilArmyDesc.erase(evilArmyDesc.size() - 2, 2); + curB->addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::TERRAIN_OVERLAY, -1, battlefieldType.hash(), goodArmyDesc, 0)->addLimiter(good)); + curB->addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::TERRAIN_OVERLAY, +1, battlefieldType.hash(), evilArmyDesc, 0)->addLimiter(evil)); + } + else if(battlefieldType == BattleField("cursed_ground")) + { + curB->addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::NO_MORALE, Bonus::TERRAIN_OVERLAY, 0, battlefieldType.hash(), VLC->generaltexth->arraytxt[112], 0)); + curB->addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::NO_LUCK, Bonus::TERRAIN_OVERLAY, 0, battlefieldType.hash(), VLC->generaltexth->arraytxt[81], 0)); + curB->addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::BLOCK_MAGIC_ABOVE, Bonus::TERRAIN_OVERLAY, 1, battlefieldType.hash(), 0, Bonus::INDEPENDENT_MIN)); } //overlay premies given @@ -580,30 +569,6 @@ ui8 BattleInfo::whatSide(PlayerColor player) const return -1; } -BattlefieldBI::BattlefieldBI BattleInfo::battlefieldTypeToBI(BFieldType bfieldType) -{ - static const std::map theMap = - { - {BFieldType::CLOVER_FIELD, BattlefieldBI::CLOVER_FIELD}, - {BFieldType::CURSED_GROUND, BattlefieldBI::CURSED_GROUND}, - {BFieldType::EVIL_FOG, BattlefieldBI::EVIL_FOG}, - {BFieldType::FAVORABLE_WINDS, BattlefieldBI::NONE}, - {BFieldType::FIERY_FIELDS, BattlefieldBI::FIERY_FIELDS}, - {BFieldType::HOLY_GROUND, BattlefieldBI::HOLY_GROUND}, - {BFieldType::LUCID_POOLS, BattlefieldBI::LUCID_POOLS}, - {BFieldType::MAGIC_CLOUDS, BattlefieldBI::MAGIC_CLOUDS}, - {BFieldType::MAGIC_PLAINS, BattlefieldBI::MAGIC_PLAINS}, - {BFieldType::ROCKLANDS, BattlefieldBI::ROCKLANDS}, - {BFieldType::SAND_SHORE, BattlefieldBI::COASTAL} - }; - - auto itr = theMap.find(bfieldType); - if(itr != theMap.end()) - return itr->second; - - return BattlefieldBI::NONE; -} - CStack * BattleInfo::getStack(int stackID, bool onlyAlive) { return const_cast(battleGetStackByID(stackID, onlyAlive)); @@ -611,7 +576,7 @@ CStack * BattleInfo::getStack(int stackID, bool onlyAlive) BattleInfo::BattleInfo() : round(-1), activeStack(-1), town(nullptr), tile(-1,-1,-1), - battlefieldType(BFieldType::NONE), terrainType(), + battlefieldType(BattleField::NONE), terrainType(), tacticsSide(0), tacticDistance(0) { setBattle(this); @@ -640,7 +605,7 @@ battle::Units BattleInfo::getUnitsIf(battle::UnitFilter predicate) const } -BFieldType BattleInfo::getBattlefieldType() const +BattleField BattleInfo::getBattlefieldType() const { return battlefieldType; } diff --git a/lib/battle/BattleInfo.h b/lib/battle/BattleInfo.h index 0fc5a03b9..1d0c128cc 100644 --- a/lib/battle/BattleInfo.h +++ b/lib/battle/BattleInfo.h @@ -19,6 +19,7 @@ class CStack; class CStackInstance; class CStackBasicDescriptor; class Terrain; +class BattleField; class DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallback, public IBattleState { @@ -36,7 +37,7 @@ public: std::vector > obstacles; SiegeInfo si; - BFieldType battlefieldType; //like !!BA:B + BattleField battlefieldType; //like !!BA:B Terrain terrainType; //used for some stack nativity checks (not the bonus limiters though that have their own copy) ui8 tacticsSide; //which side is requested to play tactics phase @@ -72,7 +73,7 @@ public: battle::Units getUnitsIf(battle::UnitFilter predicate) const override; - BFieldType getBattlefieldType() const override; + BattleField getBattlefieldType() const override; Terrain getTerrainType() const override; ObstacleCList getAllObstacles() const override; @@ -138,12 +139,10 @@ public: const CGHeroInstance * getHero(PlayerColor player) const; //returns fighting hero that belongs to given player void localInit(); - static BattleInfo * setupBattle(int3 tile, Terrain terrain, BFieldType battlefieldType, const CArmedInstance * armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance * town); + static BattleInfo * setupBattle(const int3 & tile, const Terrain & terrain, const BattleField & battlefieldType, const CArmedInstance * armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance * town); ui8 whatSide(PlayerColor player) const; - static BattlefieldBI::BattlefieldBI battlefieldTypeToBI(BFieldType bfieldType); //converts above to ERM BI format - protected: scripting::Pool * getContextPool() const override; }; diff --git a/lib/battle/BattleProxy.cpp b/lib/battle/BattleProxy.cpp index b1d533501..83df1520a 100644 --- a/lib/battle/BattleProxy.cpp +++ b/lib/battle/BattleProxy.cpp @@ -42,7 +42,7 @@ battle::Units BattleProxy::getUnitsIf(battle::UnitFilter predicate) const return subject->battleGetUnitsIf(predicate); } -BFieldType BattleProxy::getBattlefieldType() const +BattleField BattleProxy::getBattlefieldType() const { return subject->battleGetBattlefieldType(); } diff --git a/lib/battle/BattleProxy.h b/lib/battle/BattleProxy.h index 0489d6974..b756dadea 100644 --- a/lib/battle/BattleProxy.h +++ b/lib/battle/BattleProxy.h @@ -29,7 +29,7 @@ public: battle::Units getUnitsIf(battle::UnitFilter predicate) const override; - BFieldType getBattlefieldType() const override; + BattleField getBattlefieldType() const override; Terrain getTerrainType() const override; ObstacleCList getAllObstacles() const override; diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index 18aee3c73..a2122bcbb 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -1059,7 +1059,7 @@ AccessibilityInfo CBattleInfoCallback::getAccesibility() const //special battlefields with logically unavailable tiles std::vector impassableHexes; - if(battleGetBattlefieldType().num == BFieldType::SHIP_TO_SHIP) + if(battleGetBattlefieldType() == BattleField("ship_to_ship")) { impassableHexes = { diff --git a/lib/battle/CBattleInfoEssentials.cpp b/lib/battle/CBattleInfoEssentials.cpp index f1b1a55f0..9d0370a1f 100644 --- a/lib/battle/CBattleInfoEssentials.cpp +++ b/lib/battle/CBattleInfoEssentials.cpp @@ -20,9 +20,9 @@ Terrain CBattleInfoEssentials::battleTerrainType() const return getBattle()->getTerrainType(); } -BFieldType CBattleInfoEssentials::battleGetBattlefieldType() const +BattleField CBattleInfoEssentials::battleGetBattlefieldType() const { - RETURN_IF_NOT_BATTLE(BFieldType::NONE); + RETURN_IF_NOT_BATTLE(BattleField::NONE); return getBattle()->getBattlefieldType(); } diff --git a/lib/battle/CBattleInfoEssentials.h b/lib/battle/CBattleInfoEssentials.h index 8d489b998..bc354ab78 100644 --- a/lib/battle/CBattleInfoEssentials.h +++ b/lib/battle/CBattleInfoEssentials.h @@ -47,7 +47,7 @@ public: const IBonusBearer * getBattleNode() const; Terrain battleTerrainType() const override; - BFieldType battleGetBattlefieldType() const override; + BattleField battleGetBattlefieldType() const override; int32_t battleGetEnchanterCounter(ui8 side) const; std::vector > battleGetAllObstacles(boost::optional perspective = boost::none) const; //returns all obstacles on the battlefield diff --git a/lib/battle/IBattleInfoCallback.h b/lib/battle/IBattleInfoCallback.h index 999dbc536..ad3d0e6e4 100644 --- a/lib/battle/IBattleInfoCallback.h +++ b/lib/battle/IBattleInfoCallback.h @@ -13,7 +13,7 @@ #include "BattleHex.h" struct CObstacleInstance; -class BFieldType; +class BattleField; class Terrain; namespace battle @@ -35,7 +35,7 @@ public: virtual scripting::Pool * getContextPool() const = 0; virtual Terrain battleTerrainType() const = 0; - virtual BFieldType battleGetBattlefieldType() const = 0; + virtual BattleField battleGetBattlefieldType() const = 0; ///return none if battle is ongoing; otherwise the victorious side (0/1) or 2 if it is a draw virtual boost::optional battleIsFinished() const = 0; diff --git a/lib/battle/IBattleState.h b/lib/battle/IBattleState.h index 2d96be75f..02f21d935 100644 --- a/lib/battle/IBattleState.h +++ b/lib/battle/IBattleState.h @@ -16,6 +16,7 @@ class UnitChanges; struct Bonus; class JsonNode; class JsonSerializeFormat; +class BattleField; namespace vstd { @@ -40,7 +41,7 @@ public: virtual battle::Units getUnitsIf(battle::UnitFilter predicate) const = 0; - virtual BFieldType getBattlefieldType() const = 0; + virtual BattleField getBattlefieldType() const = 0; virtual Terrain getTerrainType() const = 0; virtual ObstacleCList getAllObstacles() const = 0; diff --git a/lib/mapping/CMapEditManager.cpp b/lib/mapping/CMapEditManager.cpp index d23b1100d..e514084c9 100644 --- a/lib/mapping/CMapEditManager.cpp +++ b/lib/mapping/CMapEditManager.cpp @@ -421,7 +421,6 @@ CTerrainViewPatternConfig::CTerrainViewPatternConfig() } // Add pattern to the patterns map - const auto & terGroup = getTerrainGroup(mappingPair.first); std::vector terrainViewPatternFlips; terrainViewPatternFlips.push_back(terGroupPattern); @@ -431,7 +430,8 @@ CTerrainViewPatternConfig::CTerrainViewPatternConfig() flipPattern(terGroupPattern, i); //FIXME: we flip in place - doesn't make much sense now, but used to work terrainViewPatternFlips.push_back(terGroupPattern); } - terrainViewPatterns[terGroup].push_back(terrainViewPatternFlips); + + terrainViewPatterns[mappingPair.first].push_back(terrainViewPatternFlips); } } else if(i == 1) @@ -453,29 +453,17 @@ CTerrainViewPatternConfig::~CTerrainViewPatternConfig() } -ETerrainGroup::ETerrainGroup CTerrainViewPatternConfig::getTerrainGroup(const std::string & terGroup) const +const std::vector & CTerrainViewPatternConfig::getTerrainViewPatterns(const Terrain & terrain) const { - static const std::map terGroups = - { - {"normal", ETerrainGroup::NORMAL}, - {"dirt", ETerrainGroup::DIRT}, - {"sand", ETerrainGroup::SAND}, - {"water", ETerrainGroup::WATER}, - {"rock", ETerrainGroup::ROCK}, - }; - auto it = terGroups.find(terGroup); - if(it == terGroups.end()) throw std::runtime_error(boost::str(boost::format("Terrain group '%s' does not exist.") % terGroup)); - return it->second; + auto iter = terrainViewPatterns.find(Terrain::Manager::getInfo(terrain).terrainViewPatterns); + if(iter == terrainViewPatterns.end()) + return terrainViewPatterns.at("normal"); + return iter->second; } -const std::vector & CTerrainViewPatternConfig::getTerrainViewPatternsForGroup(ETerrainGroup::ETerrainGroup terGroup) const +boost::optional CTerrainViewPatternConfig::getTerrainViewPatternById(const Terrain & terrain, const std::string & id) const { - return terrainViewPatterns.find(terGroup)->second; -} - -boost::optional CTerrainViewPatternConfig::getTerrainViewPatternById(ETerrainGroup::ETerrainGroup terGroup, const std::string & id) const -{ - const std::vector & groupPatterns = getTerrainViewPatternsForGroup(terGroup); + const std::vector & groupPatterns = getTerrainViewPatterns(terrain); for (const TVPVector & patternFlips : groupPatterns) { const TerrainViewPattern & pattern = patternFlips.front(); @@ -486,9 +474,10 @@ boost::optional CTerrainViewPatternConfig::getTerrai } return boost::optional(); } -boost::optional CTerrainViewPatternConfig::getTerrainViewPatternsById(ETerrainGroup::ETerrainGroup terGroup, const std::string & id) const + +boost::optional CTerrainViewPatternConfig::getTerrainViewPatternsById(const Terrain & terrain, const std::string & id) const { - const std::vector & groupPatterns = getTerrainViewPatternsForGroup(terGroup); + const std::vector & groupPatterns = getTerrainViewPatterns(terrain); for (const TVPVector & patternFlips : groupPatterns) { const TerrainViewPattern & pattern = patternFlips.front(); @@ -705,7 +694,7 @@ void CDrawTerrainOperation::updateTerrainViews() { for(const auto & pos : invalidatedTerViews) { - const auto & patterns = VLC->terviewh->getTerrainViewPatternsForGroup(getTerrainGroup(map->getTile(pos).terType)); + const auto & patterns = VLC->terviewh->getTerrainViewPatterns(map->getTile(pos).terType); // Detect a pattern which fits best int bestPattern = -1; @@ -760,19 +749,6 @@ void CDrawTerrainOperation::updateTerrainViews() } } -ETerrainGroup::ETerrainGroup CDrawTerrainOperation::getTerrainGroup(Terrain terType) const -{ - if(terType == Terrain("dirt")) - return ETerrainGroup::DIRT; - if(terType == Terrain("sand")) - return ETerrainGroup::SAND; - if(terType.isWater()) - return ETerrainGroup::WATER; - if(!terType.isPassable()) - return ETerrainGroup::ROCK; - return ETerrainGroup::NORMAL; -} - CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainView(const int3 & pos, const std::vector * pattern, int recDepth) const { for(int flip = 0; flip < 4; ++flip) @@ -790,7 +766,6 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainViewInner(const int3 & pos, const TerrainViewPattern & pattern, int recDepth) const { auto centerTerType = map->getTile(pos).terType; - auto centerTerGroup = getTerrainGroup(centerTerType); int totalPoints = 0; std::string transitionReplacement; @@ -857,8 +832,7 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi { if(terType == centerTerType) { - const auto & group = getTerrainGroup(centerTerType); - const auto & patternForRule = VLC->terviewh->getTerrainViewPatternsById(group, rule.name); + const auto & patternForRule = VLC->terviewh->getTerrainViewPatternsById(centerTerType, rule.name); if(auto p = patternForRule) { auto rslt = validateTerrainView(currentPos, &(*p), 1); @@ -884,12 +858,30 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi // Validate cell with the ruleset of the pattern bool nativeTestOk, nativeTestStrongOk; nativeTestOk = nativeTestStrongOk = (rule.isNativeStrong() || rule.isNativeRule()) && !isAlien; - if(centerTerGroup == ETerrainGroup::NORMAL) + + if(centerTerType == Terrain("dirt")) + { + nativeTestOk = rule.isNativeRule() && !terType.isTransitionRequired(); + bool sandTestOk = (rule.isSandRule() || rule.isTransition()) + && terType.isTransitionRequired(); + applyValidationRslt(rule.isAnyRule() || sandTestOk || nativeTestOk || nativeTestStrongOk); + } + else if(centerTerType == Terrain("sand")) + { + applyValidationRslt(true); + } + else if(centerTerType.isTransitionRequired()) //water, rock and some special terrains require sand transition + { + bool sandTestOk = (rule.isSandRule() || rule.isTransition()) + && isAlien; + applyValidationRslt(rule.isAnyRule() || sandTestOk || nativeTestOk); + } + else { bool dirtTestOk = (rule.isDirtRule() || rule.isTransition()) - && isAlien && !isSandType(terType); + && isAlien && !terType.isTransitionRequired(); bool sandTestOk = (rule.isSandRule() || rule.isTransition()) - && isSandType(terType); + && terType.isTransitionRequired(); if (transitionReplacement.empty() && rule.isTransition() && (dirtTestOk || sandTestOk)) @@ -906,23 +898,6 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi applyValidationRslt(rule.isAnyRule() || dirtTestOk || sandTestOk || nativeTestOk); } } - else if(centerTerGroup == ETerrainGroup::DIRT) - { - nativeTestOk = rule.isNativeRule() && !isSandType(terType); - bool sandTestOk = (rule.isSandRule() || rule.isTransition()) - && isSandType(terType); - applyValidationRslt(rule.isAnyRule() || sandTestOk || nativeTestOk || nativeTestStrongOk); - } - else if(centerTerGroup == ETerrainGroup::SAND) - { - applyValidationRslt(true); - } - else if(centerTerGroup == ETerrainGroup::WATER || centerTerGroup == ETerrainGroup::ROCK) - { - bool sandTestOk = (rule.isSandRule() || rule.isTransition()) - && isAlien; - applyValidationRslt(rule.isAnyRule() || sandTestOk || nativeTestOk); - } } if(topPoints == -1) @@ -945,13 +920,6 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi } } -bool CDrawTerrainOperation::isSandType(Terrain terType) const -{ - if(terType.isWater() || terType == Terrain("sand") || !terType.isPassable()) - return true; - return false; -} - void CDrawTerrainOperation::invalidateTerrainViews(const int3 & centerPos) { auto rect = extendTileAroundSafely(centerPos); diff --git a/lib/mapping/CMapEditManager.h b/lib/mapping/CMapEditManager.h index 5e18cf969..2bd42e26d 100644 --- a/lib/mapping/CMapEditManager.h +++ b/lib/mapping/CMapEditManager.h @@ -211,18 +211,6 @@ private: std::list > operations; }; -namespace ETerrainGroup -{ - enum ETerrainGroup - { - NORMAL, - DIRT, - SAND, - WATER, - ROCK - }; -} - /// The terrain view pattern describes a specific composition of terrain tiles /// in a 3x3 matrix and notes which terrain view frame numbers can be used. struct DLL_LINKAGE TerrainViewPattern @@ -338,15 +326,14 @@ public: CTerrainViewPatternConfig(); ~CTerrainViewPatternConfig(); - const std::vector & getTerrainViewPatternsForGroup(ETerrainGroup::ETerrainGroup terGroup) const; - boost::optional getTerrainViewPatternById(ETerrainGroup::ETerrainGroup terGroup, const std::string & id) const; - boost::optional getTerrainViewPatternsById(ETerrainGroup::ETerrainGroup terGroup, const std::string & id) const; + const std::vector & getTerrainViewPatterns(const Terrain & terrain) const; + boost::optional getTerrainViewPatternById(const Terrain & terrain, const std::string & id) const; + boost::optional getTerrainViewPatternsById(const Terrain & terrain, const std::string & id) const; const TVPVector * getTerrainTypePatternById(const std::string & id) const; - ETerrainGroup::ETerrainGroup getTerrainGroup(const std::string & terGroup) const; void flipPattern(TerrainViewPattern & pattern, int flip) const; private: - std::map > terrainViewPatterns; + std::map > terrainViewPatterns; std::map terrainTypePatterns; }; @@ -385,13 +372,10 @@ private: InvalidTiles getInvalidTiles(const int3 & centerPos) const; void updateTerrainViews(); - ETerrainGroup::ETerrainGroup getTerrainGroup(Terrain terType) const; /// Validates the terrain view of the given position and with the given pattern. The first method wraps the /// second method to validate the terrain view with the given pattern in all four flip directions(horizontal, vertical). ValidationResult validateTerrainView(const int3 & pos, const std::vector * pattern, int recDepth = 0) const; ValidationResult validateTerrainViewInner(const int3 & pos, const TerrainViewPattern & pattern, int recDepth = 0) const; - /// Tests whether the given terrain type is a sand type. Sand types are: Water, Sand and Rock - bool isSandType(Terrain terType) const; CTerrainSelection terrainSel; Terrain terType; diff --git a/lib/serializer/CSerializer.h b/lib/serializer/CSerializer.h index 15c84f5d8..e07e48d04 100644 --- a/lib/serializer/CSerializer.h +++ b/lib/serializer/CSerializer.h @@ -12,8 +12,8 @@ #include "../ConstTransitivePtr.h" #include "../GameConstants.h" -const ui32 SERIALIZATION_VERSION = 802; -const ui32 MINIMAL_SERIALIZATION_VERSION = 802; +const ui32 SERIALIZATION_VERSION = 803; +const ui32 MINIMAL_SERIALIZATION_VERSION = 803; const std::string SAVEGAME_MAGIC = "VCMISVG"; class CHero; diff --git a/lib/spells/ISpellMechanics.cpp b/lib/spells/ISpellMechanics.cpp index dedfd5dc8..1aa00f649 100644 --- a/lib/spells/ISpellMechanics.cpp +++ b/lib/spells/ISpellMechanics.cpp @@ -520,7 +520,7 @@ bool BaseMechanics::adaptProblem(ESpellCastProblem::ESpellCastProblem source, Pr caster->getCasterName(text); target.add(std::move(text), spells::Problem::NORMAL); } - else if(b && b->source == Bonus::TERRAIN_OVERLAY && b->sid == BFieldType::CURSED_GROUND) + else if(b && b->source == Bonus::TERRAIN_OVERLAY && b->sid == BattleField("cursed_ground").hash()) { text.addTxt(MetaString::GENERAL_TXT, 537); target.add(std::move(text), spells::Problem::NORMAL); diff --git a/scripting/lua/api/BattleCb.cpp b/scripting/lua/api/BattleCb.cpp index 779289014..6f4f2a81a 100644 --- a/scripting/lua/api/BattleCb.cpp +++ b/scripting/lua/api/BattleCb.cpp @@ -73,7 +73,7 @@ int BattleCbProxy::getBattlefieldType(lua_State * L) auto ret = object->battleGetBattlefieldType(); - return LuaStack::quickRetInt(L, static_cast(ret.num)); + return LuaStack::quickRetStr(L, ret); } int BattleCbProxy::getTerrainType(lua_State * L) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 62e992919..71754f0ea 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2219,9 +2219,9 @@ void CGameHandler::setupBattle(int3 tile, const CArmedInstance *armies[2], const if (gs->map->isCoastalTile(tile)) //coastal tile is always ground terrain = Terrain("sand"); - BFieldType terType = gs->battleGetBattlefieldType(tile, getRandomGenerator()); + BattleField terType = gs->battleGetBattlefieldType(tile, getRandomGenerator()); if (heroes[0] && heroes[0]->boat && heroes[1] && heroes[1]->boat) - terType = BFieldType::SHIP_TO_SHIP; + terType = BattleField("ship_to_ship"); //send info about battles BattleStart bs; diff --git a/test/erm/ERM_BU.cpp b/test/erm/ERM_BU.cpp index 51624b119..87273b2e9 100644 --- a/test/erm/ERM_BU.cpp +++ b/test/erm/ERM_BU.cpp @@ -155,7 +155,7 @@ TEST_F(ERM_BU_G, Get) source << "!?PI;" << std::endl; source << "!!BU:G?v1;" << std::endl; - EXPECT_CALL(binfoMock, battleGetBattlefieldType()).WillOnce(Return(BFieldType::SNOW_TREES)); + EXPECT_CALL(binfoMock, battleGetBattlefieldType()).WillOnce(Return(BattleField("snow_trees"))); loadScript(VLC->scriptHandler->erm, source.str()); @@ -174,7 +174,7 @@ TEST_F(ERM_BU_G, Get2) source << "!?PI;" << std::endl; source << "!!BU:G?v1;" << std::endl; - EXPECT_CALL(binfoMock, battleGetBattlefieldType()).WillOnce(Return(BFieldType::EVIL_FOG)); + EXPECT_CALL(binfoMock, battleGetBattlefieldType()).WillOnce(Return(BattleField("evil_fog"))); loadScript(VLC->scriptHandler->erm, source.str()); runServer(); diff --git a/test/game/CGameStateTest.cpp b/test/game/CGameStateTest.cpp index a31d8eb25..aebb82783 100644 --- a/test/game/CGameStateTest.cpp +++ b/test/game/CGameStateTest.cpp @@ -194,7 +194,7 @@ public: const auto t = gameCallback->getTile(tile); Terrain terrain = t->terType; - BFieldType terType = BFieldType::GRASS_HILLS; + BattleField terType = BattleField("grass_hills"); //send info about battles diff --git a/test/map/CMapEditManagerTest.cpp b/test/map/CMapEditManagerTest.cpp index 8283bccde..f33ff4d9d 100644 --- a/test/map/CMapEditManagerTest.cpp +++ b/test/map/CMapEditManagerTest.cpp @@ -130,10 +130,9 @@ TEST(MapManager, DrawTerrain_View) if(patternParts.size() != 2) throw std::runtime_error("A pattern should consist of two parts, the group and the id. Continue with next pattern."); const auto & groupStr = patternParts[0]; const auto & id = patternParts[1]; - auto terGroup = VLC->terviewh->getTerrainGroup(groupStr); // Get mapping range - const auto & pattern = VLC->terviewh->getTerrainViewPatternById(terGroup, id); + const auto & pattern = VLC->terviewh->getTerrainViewPatternById(groupStr, id); const auto & mapping = (*pattern).mapping; const auto & positionsNode = node["pos"].Vector(); diff --git a/test/mock/BattleFake.cpp b/test/mock/BattleFake.cpp index 1df5e2ece..23ce30f2f 100644 --- a/test/mock/BattleFake.cpp +++ b/test/mock/BattleFake.cpp @@ -92,7 +92,7 @@ void BattleFake::setupEmptyBattlefield() { EXPECT_CALL(*this, getDefendedTown()).WillRepeatedly(Return(nullptr)); EXPECT_CALL(*this, getAllObstacles()).WillRepeatedly(Return(IBattleInfo::ObstacleCList())); - EXPECT_CALL(*this, getBattlefieldType()).WillRepeatedly(Return(BFieldType::NONE2)); + EXPECT_CALL(*this, getBattlefieldType()).WillRepeatedly(Return(BattleField::NONE)); } diff --git a/test/mock/mock_IBattleInfoCallback.h b/test/mock/mock_IBattleInfoCallback.h index ff7904b3c..688243ebd 100644 --- a/test/mock/mock_IBattleInfoCallback.h +++ b/test/mock/mock_IBattleInfoCallback.h @@ -18,7 +18,7 @@ class IBattleInfoCallbackMock : public IBattleInfoCallback public: MOCK_CONST_METHOD0(getContextPool, scripting::Pool *()); MOCK_CONST_METHOD0(battleTerrainType, Terrain()); - MOCK_CONST_METHOD0(battleGetBattlefieldType, BFieldType()); + MOCK_CONST_METHOD0(battleGetBattlefieldType, BattleField()); MOCK_CONST_METHOD0(battleIsFinished, boost::optional()); diff --git a/test/mock/mock_battle_IBattleState.h b/test/mock/mock_battle_IBattleState.h index 3493b703d..12e3837c0 100644 --- a/test/mock/mock_battle_IBattleState.h +++ b/test/mock/mock_battle_IBattleState.h @@ -18,7 +18,7 @@ public: MOCK_CONST_METHOD0(getActiveStackID, int32_t()); MOCK_CONST_METHOD1(getStacksIf, TStacks(TStackFilter)); MOCK_CONST_METHOD1(getUnitsIf, battle::Units(battle::UnitFilter)); - MOCK_CONST_METHOD0(getBattlefieldType, BFieldType()); + MOCK_CONST_METHOD0(getBattlefieldType, BattleField()); MOCK_CONST_METHOD0(getTerrainType, Terrain()); MOCK_CONST_METHOD0(getAllObstacles, IBattleInfo::ObstacleCList()); MOCK_CONST_METHOD0(getDefendedTown, const CGTownInstance *()); From 2228a6f9bf9a8f73ca00c82c9749f4db84bf3e54 Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Sun, 26 Jun 2022 10:21:05 +0300 Subject: [PATCH 03/15] allow configurable battleground graphics --- config/objects/generic.json | 76 ++++++++++++++++++------ config/objects/moddables.json | 26 +++++--- lib/CGameState.cpp | 36 ++++------- lib/CHeroHandler.cpp | 6 +- lib/Terrain.h | 5 ++ lib/battle/BattleInfo.cpp | 6 +- lib/mapObjects/CObjectClassesHandler.cpp | 11 ++++ lib/mapObjects/CObjectClassesHandler.h | 5 ++ lib/mapObjects/CObjectHandler.cpp | 5 ++ lib/mapObjects/CObjectHandler.h | 18 +----- lib/mapObjects/MiscObjects.cpp | 2 +- lib/mapObjects/MiscObjects.h | 11 ++++ 12 files changed, 132 insertions(+), 75 deletions(-) diff --git a/config/objects/generic.json b/config/objects/generic.json index 7b5ee3c0c..1f866bcba 100644 --- a/config/objects/generic.json +++ b/config/objects/generic.json @@ -914,7 +914,7 @@ /// Passive objects, terrain overlays "cursedGround" : { "index" : 21, - "handler": "generic", + "handler": "terrain", "base" : { "sounds" : { "ambient" : ["LOOPCURS"] @@ -922,13 +922,14 @@ }, "types" : { "object" : { - "index" : 0 + "index" : 0, + "battleground": "cursed_ground" } } }, - "magicPlains" : { + "magicPlains" : { "index" : 46, - "handler" : "generic", + "handler" : "terrain", "base" : { "sounds" : { "ambient" : ["LOOPMAGI"] @@ -936,18 +937,28 @@ }, "types" : { "object" : { - "index" : 0 + "index" : 0, + "battleground": "magic_plains" } } }, - "swampFoliage" : { "index" :211, "handler": "generic", "types" : { "object" : { "index" : 0} } }, - "cloverField" : { "index" :222, "handler": "generic", "types" : { "object" : { "index" : 0} } }, + "swampFoliage" : { + "index" :211, + "handler": "terrain", + "types" : { "object" : { "index" : 0} } + }, + "cloverField" : { + "index" :222, + "handler": "terrain", + "types" : { "object" : { "index" : 0, "battleground": "clover_field" } } + }, "cursedGroundDUPLICATE" : { "index" : 223, - "handler" : "generic", + "handler" : "terrain", "types" : { "object" : { - "index" : 0 + "index" : 0, + "battleground": "cursed_ground" } }, "base" : { @@ -956,15 +967,39 @@ } } }, - "evilFog" : { "index" :224, "handler": "generic", "types" : { "object" : { "index" : 0} } }, - "favorableWinds" : { "index" :225, "handler": "generic", "types" : { "object" : { "index" : 0} } }, - "fieryFields" : { "index" :226, "handler": "generic", "types" : { "object" : { "index" : 0} } }, - "holyGround" : { "index" :227, "handler": "generic", "types" : { "object" : { "index" : 0} } }, - "lucidPools" : { "index" :228, "handler": "generic", "types" : { "object" : { "index" : 0} } }, - "magicClouds" : { "index" :229, "handler": "generic", "types" : { "object" : { "index" : 0} } }, + "evilFog" : { + "index" :224, + "handler": "terrain", + "types" : { "object" : { "index" : 0, "battleground": "evil_fog" } } + }, + "favorableWinds" : { + "index" :225, + "handler": "terrain", + "types" : { "object" : { "index" : 0, "battleground": "favorable_winds" } } + }, + "fieryFields": { + "index" :226, + "handler": "terrain", + "types" : { "object" : { "index" : 0, "battleground": "fiery_fields" } } + }, + "holyGround" : { + "index" :227, + "handler": "terrain", + "types" : { "object" : { "index" : 0, "battleground": "holy_ground" } } + }, + "lucidPools" : { + "index" :228, + "handler": "terrain", + "types" : { "object" : { "index" : 0, "battleground": "lucid_pools" } } + }, + "magicClouds" : { + "index" :229, + "handler": "terrain", + "types" : { "object" : { "index" : 0, "battleground": "magic_clouds" } } + }, "magicPlainsDUPLICATE" : { "index" : 230, - "handler": "generic", + "handler": "terrain", "base" : { "sounds" : { "ambient" : ["LOOPMAGI"] @@ -972,11 +1007,16 @@ }, "types" : { "object" : { - "index" : 0 + "index" : 0, + "battleground": "magic_plains" } } }, - "rocklands" : { "index" :231, "handler": "generic", "types" : { "object" : { "index" : 0} } }, + "rocklands" : { + "index" :231, + "handler": "terrain", + "types" : { "object" : { "index" : 0, "battleground": "rocklands" } } + }, /// Decorations "cactus" : { "index" :116, "handler": "static", "types" : { "object" : { "index" : 0} } }, diff --git a/config/objects/moddables.json b/config/objects/moddables.json index 69ccf20bf..f7e3c1034 100644 --- a/config/objects/moddables.json +++ b/config/objects/moddables.json @@ -237,7 +237,8 @@ }, "sounds" : { "ambient" : ["LOOPLUMB"] - } + }, + "battleground": "subterranean" }, "alchemistLab" : { "index" : 1, @@ -247,7 +248,8 @@ }, "sounds" : { "ambient" : ["LOOPSTAR"] - } + }, + "battleground": "subterranean" }, "orePit" : { "index" : 2, @@ -257,7 +259,8 @@ }, "sounds" : { "ambient" : ["LOOPSULF"] - } + }, + "battleground": "subterranean" }, "sulfurDune" : { "index" : 3, @@ -267,7 +270,8 @@ }, "sounds" : { "ambient" : ["LOOPSULF"] - } + }, + "battleground": "subterranean" }, "crystalCavern" : { "index" : 4, @@ -277,7 +281,8 @@ }, "sounds" : { "ambient" : ["LOOPCRYS"] - } + }, + "battleground": "subterranean" }, "gemPond" : { "index" : 5, @@ -287,7 +292,8 @@ }, "sounds" : { "ambient" : ["LOOPGEMP"] - } + }, + "battleground": "subterranean" }, "goldMine" : { "index" : 6, @@ -297,7 +303,8 @@ }, "sounds" : { "ambient" : ["LOOPMINE"] - } + }, + "battleground": "subterranean" }, "abandoned" : { "index" : 7, @@ -305,7 +312,8 @@ "sounds" : { "ambient" : ["LOOPCAVE"], "visit" : ["MYSTERY"] - } + }, + "battleground": "subterranean" } } }, @@ -319,7 +327,7 @@ } }, "types" : { - "mine" : { "index" : 7 } + "mine" : { "index" : 7, "battleground": "subterranean" } } }, diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index abbf72ce8..83196ce72 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -1902,9 +1902,12 @@ BattleField CGameState::battleGetBattlefieldType(int3 tile, CRandomGenerator & r return BattleField::NONE; const TerrainTile &t = map->getTile(tile); - //fight in mine -> subterranean - if(dynamic_cast(t.visitableObjects.front())) - return BattleField("subterranean"); + + auto topObject = t.visitableObjects.front(); + if(topObject && topObject->getBattlefield() != BattleField::NONE) + { + return topObject->getBattlefield(); + } for(auto &obj : map->objects) { @@ -1912,29 +1915,10 @@ BattleField CGameState::battleGetBattlefieldType(int3 tile, CRandomGenerator & r if( !obj || obj->pos.z != tile.z || !obj->coveringAt(tile.x, tile.y)) continue; - switch(obj->ID) - { - case Obj::CLOVER_FIELD: - return BattleField("clover_field"); - case Obj::CURSED_GROUND1: case Obj::CURSED_GROUND2: - return BattleField("cursed_ground"); - case Obj::EVIL_FOG: - return BattleField("evil_fog"); - case Obj::FAVORABLE_WINDS: - return BattleField("favorable_winds"); - case Obj::FIERY_FIELDS: - return BattleField("fiery_fields"); - case Obj::HOLY_GROUNDS: - return BattleField("holy_ground"); - case Obj::LUCID_POOLS: - return BattleField("lucid_pools"); - case Obj::MAGIC_CLOUDS: - return BattleField("magic_clouds"); - case Obj::MAGIC_PLAINS1: case Obj::MAGIC_PLAINS2: - return BattleField("magic_plains"); - case Obj::ROCKLANDS: - return BattleField("rocklands"); - } + auto customBattlefield = obj->getBattlefield(); + + if(customBattlefield != BattleField::NONE) + return customBattlefield; } if(map->isCoastalTile(tile)) //coastal tile is always ground diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index 74093e959..7fae83f7e 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -177,10 +177,10 @@ std::vector CObstacleInfo::getBlocked(BattleHex hex) const return ret; } -bool CObstacleInfo::isAppropriate(const Terrain & terrainType, const BattleField & specialBattlefield) const +bool CObstacleInfo::isAppropriate(const Terrain & terrainType, const BattleField & battlefield) const { - if(!allowedSpecialBfields.empty() && specialBattlefield != BattleField::NONE) - return vstd::contains(allowedSpecialBfields, specialBattlefield); + if(battlefield.isSpecial()) + return vstd::contains(allowedSpecialBfields, battlefield); return vstd::contains(allowedTerrains, terrainType); } diff --git a/lib/Terrain.h b/lib/Terrain.h index 5e69fa384..63dac1b6f 100644 --- a/lib/Terrain.h +++ b/lib/Terrain.h @@ -42,6 +42,11 @@ public: { h & name; } + + bool isSpecial() const + { + return name.find('_') >= 0; // hack for special battlefields, move to JSON + } protected: diff --git a/lib/battle/BattleInfo.cpp b/lib/battle/BattleInfo.cpp index 2dd2b173c..1f4587901 100644 --- a/lib/battle/BattleInfo.cpp +++ b/lib/battle/BattleInfo.cpp @@ -467,7 +467,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, const Terrain & terrain, } if(battlefieldType == BattleField("fiery_fields")) { - if(bonusSubtype == -1) bonusSubtype = 1; + if(bonusSubtype == -1) bonusSubtype = 2; } if(battlefieldType == BattleField("rocklands")) { @@ -475,13 +475,13 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, const Terrain & terrain, } if(battlefieldType == BattleField("magic_clouds")) { - if(bonusSubtype == -1) bonusSubtype = 2; + if(bonusSubtype == -1) bonusSubtype = 1; } if(battlefieldType == BattleField("lucid_pools")) { if(bonusSubtype == -1) bonusSubtype = 4; } - if(bonusSubtype == -1) + if(bonusSubtype != -1) { //common part for cases 9, 14, 15, 16, 17 curB->addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::MAGIC_SCHOOL_SKILL,Bonus::TERRAIN_OVERLAY, 3, battlefieldType.hash(), bonusSubtype)); } diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index c7944b940..638ad7cea 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -97,6 +97,7 @@ CObjectClassesHandler::CObjectClassesHandler() SET_HANDLER("oncePerHero", CGVisitableOPH); SET_HANDLER("oncePerWeek", CGVisitableOPW); SET_HANDLER("witch", CGWitchHut); + SET_HANDLER("terrain", CGTerrainPatch); #undef SET_HANDLER_CLASS #undef SET_HANDLER @@ -514,6 +515,11 @@ void AObjectTypeHandler::init(const JsonNode & input, boost::optional>(input["aiValue"].Integer()); + if(input["battleground"].isNull()) + battlefield = BattleField::NONE; + else + battlefield = BattleField(input["battleground"].String()); + initTypeData(input); } @@ -569,6 +575,11 @@ std::vector AObjectTypeHandler::getTemplates() const return templates; } +BattleField AObjectTypeHandler::getBattlefield() const +{ + return battlefield; +} + std::vector AObjectTypeHandler::getTemplates(const Terrain & terrainType) const { std::vector templates = getTemplates(); diff --git a/lib/mapObjects/CObjectClassesHandler.h b/lib/mapObjects/CObjectClassesHandler.h index cc737fc99..c46275261 100644 --- a/lib/mapObjects/CObjectClassesHandler.h +++ b/lib/mapObjects/CObjectClassesHandler.h @@ -15,6 +15,7 @@ #include "../ConstTransitivePtr.h" #include "../IHandlerBase.h" #include "../JsonNode.h" +#include "Terrain.h" class JsonNode; class CRandomGenerator; @@ -148,6 +149,9 @@ class DLL_LINKAGE AObjectTypeHandler : public boost::noncopyable SObjectSounds sounds; boost::optional aiValue; + + BattleField battlefield; + protected: void preInitObject(CGObjectInstance * obj) const; virtual bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const; @@ -179,6 +183,7 @@ public: /// returns all templates matching parameters std::vector getTemplates() const; std::vector getTemplates(const Terrain & terrainType) const; + BattleField getBattlefield() const; /// returns preferred template for this object, if present (e.g. one of 3 possible templates for town - village, fort and castle) /// note that appearance will not be changed - this must be done separately (either by assignment or via pack from server) diff --git a/lib/mapObjects/CObjectHandler.cpp b/lib/mapObjects/CObjectHandler.cpp index 7a0f84d64..9f5d7b1da 100644 --- a/lib/mapObjects/CObjectHandler.cpp +++ b/lib/mapObjects/CObjectHandler.cpp @@ -400,6 +400,11 @@ void CGObjectInstance::serializeJsonOwner(JsonSerializeFormat & handler) tempOwner = PlayerColor(temp); } +BattleField CGObjectInstance::getBattlefield() const +{ + return VLC->objtypeh->getHandlerFor(ID, subID)->getBattlefield(); +} + CGObjectInstanceBySubIdFinder::CGObjectInstanceBySubIdFinder(CGObjectInstance * obj) : obj(obj) { diff --git a/lib/mapObjects/CObjectHandler.h b/lib/mapObjects/CObjectHandler.h index 2897606cc..3db649b4e 100644 --- a/lib/mapObjects/CObjectHandler.h +++ b/lib/mapObjects/CObjectHandler.h @@ -159,21 +159,9 @@ public: std::set getBlockedOffsets() const; //returns set of relative positions blocked by this object bool isVisitable() const; //returns true if object is visitable - bool isTile2Terrain() const - { - return ID.num == Obj::CLOVER_FIELD - || ID.num == Obj::CURSED_GROUND1 - || ID.num == Obj::CURSED_GROUND2 - || ID.num == Obj::EVIL_FOG - || ID.num == Obj::FAVORABLE_WINDS - || ID.num == Obj::FIERY_FIELDS - || ID.num == Obj::HOLY_GROUNDS - || ID.num == Obj::LUCID_POOLS - || ID.num == Obj::MAGIC_CLOUDS - || ID.num == Obj::MAGIC_PLAINS1 - || ID.num == Obj::MAGIC_PLAINS2 - || ID.num == Obj::ROCKLANDS; - } + BattleField getBattlefield() const; + + virtual bool isTile2Terrain() const { return false; } boost::optional getAmbientSound() const; boost::optional getVisitSound() const; diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 9ec8d9c52..3a8f405ba 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -2199,4 +2199,4 @@ void CGLighthouse::giveBonusTo(PlayerColor player, bool onInit) const void CGLighthouse::serializeJsonOptions(JsonSerializeFormat& handler) { serializeJsonOwner(handler); -} +} \ No newline at end of file diff --git a/lib/mapObjects/MiscObjects.h b/lib/mapObjects/MiscObjects.h index a6ad8567e..e13165fe0 100644 --- a/lib/mapObjects/MiscObjects.h +++ b/lib/mapObjects/MiscObjects.h @@ -529,3 +529,14 @@ public: protected: void serializeJsonOptions(JsonSerializeFormat & handler) override; }; + +class DLL_LINKAGE CGTerrainPatch : public CGObjectInstance +{ +public: + CGTerrainPatch() = default; + + virtual bool isTile2Terrain() const override + { + return true; + } +}; From ac2bd4f79cf79ecab8acb466bbc89dd02dbabc4a Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Tue, 28 Jun 2022 11:05:30 +0300 Subject: [PATCH 04/15] battlefields in VLC and custom bonuses for terrain patches --- client/CGameInfo.cpp | 6 + client/CGameInfo.h | 3 + client/Graphics.cpp | 6 - client/Graphics.h | 1 - client/battle/CBattleInterface.cpp | 8 +- config/battlefields.json | 173 +++++++++++++++++++++++ config/battles_graphics.json | 29 ---- config/gameConfig.json | 4 + config/schemas/battlefield.json | 35 +++++ config/schemas/mod.json | 6 + include/vcmi/Services.h | 2 + lib/BattleFieldHandler.cpp | 108 ++++++++++++++ lib/BattleFieldHandler.h | 85 +++++++++++ lib/CGameState.cpp | 5 +- lib/CHeroHandler.cpp | 7 +- lib/CHeroHandler.h | 2 +- lib/CMakeLists.txt | 4 +- lib/CModHandler.cpp | 2 + lib/GameConstants.cpp | 38 +++++ lib/GameConstants.h | 17 +++ lib/IGameCallback.cpp | 1 + lib/Terrain.cpp | 26 ---- lib/Terrain.h | 42 +----- lib/VCMI_Lib.cpp | 10 ++ lib/VCMI_Lib.h | 5 + lib/battle/BattleInfo.cpp | 59 +------- lib/battle/CBattleInfoCallback.cpp | 24 ++-- lib/mapObjects/CObjectClassesHandler.cpp | 8 +- lib/mapObjects/CObjectClassesHandler.h | 3 +- lib/mapping/CMapEditManager.cpp | 7 +- lib/mapping/CMapEditManager.h | 2 +- lib/registerTypes/RegisterTypes.h | 2 + lib/rmg/CMapGenOptions.cpp | 12 +- lib/rmg/CRmgTemplateZone.cpp | 4 + lib/spells/ISpellMechanics.cpp | 4 +- scripting/lua/api/BattleCb.cpp | 4 +- scripts/lib/erm/BU.lua | 20 +-- server/CGameHandler.cpp | 2 +- test/battle/CBattleInfoCallbackTest.cpp | 43 ++++-- test/erm/ERM_BU.cpp | 8 +- test/game/CGameStateTest.cpp | 2 +- test/map/CMapEditManagerTest.cpp | 2 +- test/map/CMapFormatTest.cpp | 5 +- test/mock/BattleFake.cpp | 2 +- test/mock/ZoneOptionsFake.h | 23 +++ test/mock/mock_Services.h | 3 +- test/spells/effects/CloneTest.cpp | 2 +- 47 files changed, 645 insertions(+), 221 deletions(-) create mode 100644 config/battlefields.json create mode 100644 config/schemas/battlefield.json create mode 100644 lib/BattleFieldHandler.cpp create mode 100644 lib/BattleFieldHandler.h create mode 100644 test/mock/ZoneOptionsFake.h diff --git a/client/CGameInfo.cpp b/client/CGameInfo.cpp index 2fe9f4fd6..3c8eb86fb 100644 --- a/client/CGameInfo.cpp +++ b/client/CGameInfo.cpp @@ -37,6 +37,7 @@ void CGameInfo::setFromLib() spellh = VLC->spellh; skillh = VLC->skillh; objtypeh = VLC->objtypeh; + battleFieldHandler = VLC->battlefieldsHandler; } const ArtifactService * CGameInfo::artifacts() const @@ -44,6 +45,11 @@ const ArtifactService * CGameInfo::artifacts() const return globalServices->artifacts(); } +const BattleFieldService * CGameInfo::battlefields() const +{ + return globalServices->battlefields(); +} + const CreatureService * CGameInfo::creatures() const { return globalServices->creatures(); diff --git a/client/CGameInfo.h b/client/CGameInfo.h index d9947c1d4..b93924873 100644 --- a/client/CGameInfo.h +++ b/client/CGameInfo.h @@ -31,6 +31,7 @@ class CCursorHandler; class CGameState; class IMainVideoPlayer; class CServerHandler; +class BattleFieldHandler; class CMap; @@ -60,6 +61,7 @@ public: const scripting::Service * scripts() const override; const spells::Service * spells() const override; const SkillService * skills() const override; + const BattleFieldService * battlefields() const override; void updateEntity(Metatype metatype, int32_t index, const JsonNode & data) override; @@ -68,6 +70,7 @@ public: ConstTransitivePtr modh; //public? + ConstTransitivePtr battleFieldHandler; ConstTransitivePtr heroh; ConstTransitivePtr creh; ConstTransitivePtr spellh; diff --git a/client/Graphics.cpp b/client/Graphics.cpp index fe64be0f9..41b4a4507 100644 --- a/client/Graphics.cpp +++ b/client/Graphics.cpp @@ -109,12 +109,6 @@ void Graphics::initializeBattleGraphics() const JsonNode config(mod, ResourceID("config/battles_graphics.json")); - if(!config["backgrounds"].isNull()) - for(auto & t : config["backgrounds"].Struct()) - { - battleBacks[t.first] = t.second.String(); - } - //initialization of AC->def name mapping if(!config["ac_mapping"].isNull()) for(const JsonNode &ac : config["ac_mapping"].Vector()) diff --git a/client/Graphics.h b/client/Graphics.h index d59ae3fb2..fbf41063a 100644 --- a/client/Graphics.h +++ b/client/Graphics.h @@ -87,7 +87,6 @@ public: //towns std::map ERMUtoPicture[GameConstants::F_NUMBER]; //maps building ID to it's picture's name for each town type //for battles - std::map battleBacks; //maps BattleField to it's picture's name std::map< int, std::vector < std::string > > battleACToDef; //maps AC format to vector of appropriate def names //functions diff --git a/client/battle/CBattleInterface.cpp b/client/battle/CBattleInterface.cpp index f1de912ec..5cb46ff95 100644 --- a/client/battle/CBattleInterface.cpp +++ b/client/battle/CBattleInterface.cpp @@ -41,6 +41,7 @@ #include "../../lib/spells/ISpellMechanics.h" #include "../../lib/spells/Problem.h" #include "../../lib/CTownHandler.h" +#include "../../lib/BattleFieldHandler.h" #include "../../lib/CGameState.h" #include "../../lib/mapping/CMap.h" #include "../../lib/NetPacks.h" @@ -201,13 +202,14 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet else { auto bfieldType = curInt->cb->battleGetBattlefieldType(); - if(!vstd::contains(graphics->battleBacks, bfieldType)) + + if(bfieldType == BattleField::NONE) { - logGlobal->error("%s is not valid battlefield type!", static_cast(bfieldType)); + logGlobal->error("Invalid battlefield returned for current battle"); } else { - background = BitmapHandler::loadBitmap(graphics->battleBacks[bfieldType], false); + background = BitmapHandler::loadBitmap(bfieldType.getInfo()->graphics, false); } } diff --git a/config/battlefields.json b/config/battlefields.json new file mode 100644 index 000000000..cffc0df4a --- /dev/null +++ b/config/battlefields.json @@ -0,0 +1,173 @@ +{ + "sand_shore": { "graphics" : "CMBKBCH.BMP" }, + "sand_mesas": { "graphics" : "CMBKDES.BMP" }, + "dirt_birches": { "graphics" : "CMBKDRTR.BMP" }, + "dirt_hills": { "graphics" : "CMBKDRMT.BMP" }, + "dirt_pines": { "graphics" : "CMBKDRDD.BMP" }, + "grass_hills": { "graphics" : "CMBKGRMT.BMP" }, + "grass_pines": { "graphics" : "CMBKGRTR.BMP" }, + "lava": { "graphics" :"CMBKLAVA.BMP" }, + "magic_plains": { + "graphics": "CMBKMAG.BMP", + "isSpecial" : true, + "bonuses": [ + { + "type" : "MAGIC_SCHOOL_SKILL", + "subtype" : 0, + "val" : 3, + "valueType" : "BASE_NUMBER" + } + ] + }, + "snow_mountains": { "graphics" : "CMBKSNMT.BMP" }, + "snow_trees": { "graphics" : "CMBKSNTR.BMP" }, + "subterranean": { "graphics" : "CMBKSUB.BMP", "isSpecial" : true }, + "swamp_trees": { "graphics" : "CMBKSWMP.BMP" }, + "fiery_fields": { + "graphics": "CMBKFF.BMP", + "isSpecial" : true, + "bonuses": [ + { + "type" : "MAGIC_SCHOOL_SKILL", + "subtype" : 2, + "val" : 3, + "valueType" : "BASE_NUMBER" + } + ] + }, + "rocklands": { + "graphics": "CMBKRK.BMP", + "isSpecial" : true, + "bonuses": [ + { + "type" : "MAGIC_SCHOOL_SKILL", + "subtype" : 8, + "val" : 3, + "valueType" : "BASE_NUMBER" + } + ] + }, + "magic_clouds": { + "graphics": "CMBKMC.BMP", + "isSpecial" : true, + "bonuses": [ + { + "type" : "MAGIC_SCHOOL_SKILL", + "subtype" : 1, + "val" : 3, + "valueType" : "BASE_NUMBER" + } + ] + }, + "lucid_pools": { + "graphics": "CMBKLP.BMP", + "isSpecial" : true, + "bonuses": [ + { + "type" : "MAGIC_SCHOOL_SKILL", + "subtype" : 4, + "val" : 3, + "valueType" : "BASE_NUMBER" + } + ] + }, + "holy_ground": { + "graphics": "CMBKHG.BMP", + "isSpecial" : true, + "bonuses": [ + { + "type" : "MORALE", + "val" : 1, + "valueType" : "BASE_NUMBER", + "description" : "Creatures of good town alignment on Holly Ground", + "limiters": [{ "type" : "CREATURE_ALIGNMENT_LIMITER", "parameters" : ["good"] }] + }, + { + "type" : "MORALE", + "val" : -1, + "valueType" : "BASE_NUMBER", + "description" : "Creatures of evil town alignment on Holly Ground", + "limiters": [{ "type" : "CREATURE_ALIGNMENT_LIMITER", "parameters" : ["evil"] }] + } + ] + }, + "clover_field": { + "graphics": "CMBKCF.BMP", + "isSpecial" : true, + "bonuses": [ + { + "type" : "LUCK", + "val" : 2, + "valueType" : "BASE_NUMBER", + "description" : "Creatures of neutral town alignment on Clover Field", + "limiters": [{ "type" : "CREATURE_ALIGNMENT_LIMITER", "parameters" : ["neutral"] }] + } + ] + }, + "evil_fog": { + "graphics": "CMBKEF.BMP", + "isSpecial" : true, + "bonuses": [ + { + "type" : "MORALE", + "val" : -1, + "valueType" : "BASE_NUMBER", + "description" : "Creatures of good town alignment on Evil Fog", + "limiters": [{ "type" : "CREATURE_ALIGNMENT_LIMITER", "parameters" : ["good"] }] + }, + { + "type" : "MORALE", + "val" : 1, + "valueType" : "BASE_NUMBER", + "description" : "Creatures of evil town alignment on Evil Fog", + "limiters": [{ "type" : "CREATURE_ALIGNMENT_LIMITER", "parameters" : ["evil"] }] + } + ] + }, + "favorable_winds": { + "graphics": "CMBKFW.BMP", + "isSpecial" : true + }, + "cursed_ground": { + "graphics": "CMBKCUR.BMP", + "isSpecial" : true, + "bonuses": [ + { + "type" : "NO_MORALE", + "subtype" : 0, + "val" : 0, + "valueType" : "INDEPENDENT_MIN", + "description" : "Creatures on Cursed Ground" + }, + { + "type" : "NO_LUCK", + "subtype" : 0, + "val" : 0, + "valueType" : "INDEPENDENT_MIN", + "description" : "Creatures on Cursed Ground" + }, + { + "type" : "BLOCK_MAGIC_ABOVE", + "subtype" : 0, + "val" : 1, + "valueType" : "INDEPENDENT_MIN" + } + ] + }, + "rough": { "graphics" : "CMBKRGH.BMP" }, + "ship_to_ship": + { + "graphics" : "CMBKBOAT.BMP" + "impassableHexes" : [ + 6, 7, 8, 9, + 24, 25, 26, + 58, 59, 60, + 75, 76, 77, + 92, 93, 94, + 109, 110, 111, + 126, 127, 128, + 159, 160, 161, 162, 163, + 176, 177, 178, 179, 180] + }, + "ship": { "graphics" : "CMBKDECK.BMP" } +} diff --git a/config/battles_graphics.json b/config/battles_graphics.json index 8e37ae7eb..ee68b9693 100644 --- a/config/battles_graphics.json +++ b/config/battles_graphics.json @@ -1,33 +1,4 @@ { - // backgrounds of terrains battles can be fought on - "backgrounds": { - "sand_shore": "CMBKBCH.BMP", - "sand_mesas": "CMBKDES.BMP", - "dirt_birches": "CMBKDRTR.BMP", - "dirt_hills": "CMBKDRMT.BMP", - "dirt_pines": "CMBKDRDD.BMP", - "grass_hills": "CMBKGRMT.BMP", - "grass_pines": "CMBKGRTR.BMP", - "lava": "CMBKLAVA.BMP", - "magic_plains": "CMBKMAG.BMP", - "snow_mountains": "CMBKSNMT.BMP", - "snow_trees": "CMBKSNTR.BMP", - "subterranean": "CMBKSUB.BMP", - "swamp_trees": "CMBKSWMP.BMP", - "fiery_fields": "CMBKFF.BMP", - "rocklands": "CMBKRK.BMP", - "magic_clouds": "CMBKMC.BMP", - "lucid_pools": "CMBKLP.BMP", - "holy_ground": "CMBKHG.BMP", - "clover_field": "CMBKCF.BMP", - "evil_fog": "CMBKEF.BMP", - "favorable_winds": "CMBKFW.BMP", - "cursed_ground": "CMBKCUR.BMP", - "rough": "CMBKRGH.BMP", - "ship_to_ship": "CMBKBOAT.BMP", - "ship": "CMBKDECK.BMP" - }, - // WoG_Ac_format_to_def_names_mapping "ac_mapping": [ { "id": 0, "defnames": [ "C10SPW.DEF" ] },//merged diff --git a/config/gameConfig.json b/config/gameConfig.json index 57872e9a4..186029a94 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -84,5 +84,9 @@ "terrains": [ "config/terrains.json" + ], + "battlefields": + [ + "config/battlefields.json" ] } diff --git a/config/schemas/battlefield.json b/config/schemas/battlefield.json new file mode 100644 index 000000000..02aa0dcfd --- /dev/null +++ b/config/schemas/battlefield.json @@ -0,0 +1,35 @@ +{ + "type":"object", + "$schema": "http://json-schema.org/draft-04/schema", + "title" : "VCMI battlefield format", + "description" : "Format used to define new artifacts in VCMI", + "required" : [ "graphics" ], + + "additionalProperties" : false, + "properties":{ + "bonuses": { + "type":"array", + "description": "Bonuses provided by this battleground using bonus system", + "items": { "$ref" : "bonus.json" } + }, + "name": { + "type":"string", + "description": "Name of the battleground" + }, + "graphics": { + "type":"string", + "description": "BMP battleground resource" + }, + "isSpecial": { + "type":"boolean", + "description": "Shows if this battleground has own obstacles" + }, + "impassableHexes": { + "type":"array", + "description": "Battle hexes always impassable for this type of battlefield (ship to ship for instance)", + "items": { + "type":"number" + } + } + } +} diff --git a/config/schemas/mod.json b/config/schemas/mod.json index e6e18167a..34aed68c3 100644 --- a/config/schemas/mod.json +++ b/config/schemas/mod.json @@ -112,6 +112,12 @@ "description": "List of configuration files for RMG templates", "items": { "type":"string", "format" : "textFile" } + }, + "battlefields":{ + "type":"array", + "description": "List of configuration files for battlefields", + "items": { "type":"string", "format" : "textFile" } + }, "changelog" : { diff --git a/include/vcmi/Services.h b/include/vcmi/Services.h index 46c2e58a9..1b567a824 100644 --- a/include/vcmi/Services.h +++ b/include/vcmi/Services.h @@ -19,6 +19,7 @@ class HeroClassService; class HeroTypeService; class SkillService; class JsonNode; +class BattleFieldService; namespace spells { @@ -48,6 +49,7 @@ public: virtual const scripting::Service * scripts() const = 0; virtual const spells::Service * spells() const = 0; virtual const SkillService * skills() const = 0; + virtual const BattleFieldService * battlefields() const = 0; virtual void updateEntity(Metatype metatype, int32_t index, const JsonNode & data) = 0; diff --git a/lib/BattleFieldHandler.cpp b/lib/BattleFieldHandler.cpp new file mode 100644 index 000000000..317afcad7 --- /dev/null +++ b/lib/BattleFieldHandler.cpp @@ -0,0 +1,108 @@ +/* + * BattleFieldHandler.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 + * + */ +#include "StdInc.h" + +#include +#include "BattleFieldHandler.h" + +BattleFieldInfo * BattleFieldHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index) +{ + BattleFieldInfo * info = new BattleFieldInfo(BattleField(index), identifier); + + if(json["graphics"].getType() == JsonNode::JsonType::DATA_STRING) + { + info->graphics = json["graphics"].String(); + } + + if(json["icon"].getType() == JsonNode::JsonType::DATA_STRING) + { + info->icon = json["icon"].String(); + } + + if(json["name"].getType() == JsonNode::JsonType::DATA_STRING) + { + info->name = json["name"].String(); + } + + if(json["bonuses"].getType() == JsonNode::JsonType::DATA_VECTOR) + { + for(auto b : json["bonuses"].Vector()) + { + auto bonus = JsonUtils::parseBonus(b); + + bonus->source = Bonus::TERRAIN_OVERLAY; + bonus->sid = info->getIndex(); + bonus->duration = Bonus::ONE_BATTLE; + + info->bonuses.push_back(bonus); + } + } + + if(json["isSpecial"].getType() == JsonNode::JsonType::DATA_BOOL) + { + info->isSpecial = json["isSpecial"].Bool(); + } + + if(json["impassableHexes"].getType() == JsonNode::JsonType::DATA_VECTOR) + { + for(auto node : json["impassableHexes"].Vector()) + info->impassableHexes.push_back(BattleHex(node.Integer())); + } + + + return info; +} + +std::vector BattleFieldHandler::loadLegacyData(size_t dataSize) +{ + return std::vector(); +} + +const std::vector & BattleFieldHandler::getTypeNames() const +{ + static const std::vector types = std::vector { "battlefield" }; + + return types; +} + +std::vector BattleFieldHandler::getDefaultAllowed() const +{ + return std::vector(); +} + +int32_t BattleFieldInfo::getIndex() const +{ + return battlefield.getNum(); +} + +int32_t BattleFieldInfo::getIconIndex() const +{ + return iconIndex; +} + +const std::string & BattleFieldInfo::getName() const +{ + return name; +} + +const std::string & BattleFieldInfo::getJsonKey() const +{ + return identifier; +} + +void BattleFieldInfo::registerIcons(const IconRegistar & cb) const +{ + //cb(getIconIndex(), "BATTLEFIELD", icon); +} + +BattleField BattleFieldInfo::getId() const +{ + return battlefield; +} \ No newline at end of file diff --git a/lib/BattleFieldHandler.h b/lib/BattleFieldHandler.h new file mode 100644 index 000000000..ab5ab2742 --- /dev/null +++ b/lib/BattleFieldHandler.h @@ -0,0 +1,85 @@ +/* + * BattleFieldHandler.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 +#include "HeroBonus.h" +#include "GameConstants.h" +#include "IHandlerBase.h" +#include "Terrain.h" +#include "battle/BattleHex.h" + +class BattleFieldInfo : public EntityT +{ +public: + BattleField battlefield; + std::vector> bonuses; + bool isSpecial; + std::string graphics; + std::string name; + std::string identifier; + std::string icon; + si32 iconIndex; + std::vector impassableHexes; + + BattleFieldInfo() + : BattleFieldInfo(BattleField::NONE, "") + { + } + + BattleFieldInfo(BattleField battlefield, std::string identifier) + :bonuses(), isSpecial(false), battlefield(battlefield), identifier(identifier), graphics(), icon(), iconIndex(battlefield.getNum()), impassableHexes(), name(identifier) + { + } + + int32_t getIndex() const override; + int32_t getIconIndex() const override; + const std::string & getName() const override; + const std::string & getJsonKey() const override; + void registerIcons(const IconRegistar & cb) const override; + BattleField getId() const override; + + template void serialize(Handler & h, const int version) + { + h & name; + h & identifier; + h & isSpecial; + h & graphics; + h & icon; + h & iconIndex; + h & battlefield; + h & impassableHexes; + + } +}; + +class DLL_LINKAGE BattleFieldService : public EntityServiceT +{ +public: +}; + +class BattleFieldHandler : public CHandlerBase +{ +public: + virtual BattleFieldInfo * loadFromJson( + const std::string & scope, + const JsonNode & json, + const std::string & identifier, + size_t index) override; + + virtual const std::vector & getTypeNames() const override; + virtual std::vector loadLegacyData(size_t dataSize) override; + virtual std::vector getDefaultAllowed() const override; + + template void serialize(Handler & h, const int version) + { + h & objects; + } +}; diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 83196ce72..cb20200c3 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -1922,9 +1922,10 @@ BattleField CGameState::battleGetBattlefieldType(int3 tile, CRandomGenerator & r } if(map->isCoastalTile(tile)) //coastal tile is always ground - return BattleField("sand_shore"); + return BattleField::fromString("sand_shore"); - return *RandomGeneratorUtil::nextItem(Terrain::Manager::getInfo(t.terType).battleFields, rand); + return BattleField::fromString( + *RandomGeneratorUtil::nextItem(Terrain::Manager::getInfo(t.terType).battleFields, rand)); } UpgradeInfo CGameState::getUpgradeInfo(const CStackInstance &stack) diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index 7fae83f7e..bfb00fed3 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -25,6 +25,7 @@ #include #include "mapObjects/CObjectClassesHandler.h" +#include "BattleFieldHandler.h" CHero::CHero() = default; CHero::~CHero() = default; @@ -179,8 +180,10 @@ std::vector CObstacleInfo::getBlocked(BattleHex hex) const bool CObstacleInfo::isAppropriate(const Terrain & terrainType, const BattleField & battlefield) const { - if(battlefield.isSpecial()) - return vstd::contains(allowedSpecialBfields, battlefield); + auto bgInfo = battlefield.getInfo(); + + if(bgInfo->isSpecial) + return vstd::contains(allowedSpecialBfields, bgInfo->identifier); return vstd::contains(allowedTerrains, terrainType); } diff --git a/lib/CHeroHandler.h b/lib/CHeroHandler.h index 674dabb0a..e690fcfa9 100644 --- a/lib/CHeroHandler.h +++ b/lib/CHeroHandler.h @@ -227,7 +227,7 @@ struct DLL_LINKAGE CObstacleInfo { std::string defName; std::vector allowedTerrains; - std::vector allowedSpecialBfields; + std::vector allowedSpecialBfields; ui8 isAbsoluteObstacle; //there may only one such obstacle in battle and its position is always the same si32 width, height; //how much space to the right and up is needed to place obstacle (affects only placement algorithm) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 36c9c1bb6..1606829f4 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -129,7 +129,8 @@ set(lib_SRCS spells/effects/Sacrifice.cpp vstd/StringUtils.cpp - + + BattleFieldHandler.cpp CAndroidVMHelper.cpp CArtHandler.cpp CBonusTypeHandler.cpp @@ -338,6 +339,7 @@ set(lib_HEADERS spells/effects/Sacrifice.h AI_Base.h + BattleFieldHandler.h CAndroidVMHelper.h CArtHandler.h CBonusTypeHandler.h diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index c3d5185ae..a7b2ace73 100644 --- a/lib/CModHandler.cpp +++ b/lib/CModHandler.cpp @@ -25,6 +25,7 @@ #include "spells/CSpellHandler.h" #include "CSkillHandler.h" #include "ScriptHandler.h" +#include "BattleFieldHandler.h" #include @@ -433,6 +434,7 @@ void CContentHandler::init() handlers.insert(std::make_pair("skills", ContentTypeHandler(VLC->skillh, "skill"))); handlers.insert(std::make_pair("templates", ContentTypeHandler((IHandlerBase *)VLC->tplh, "template"))); handlers.insert(std::make_pair("scripts", ContentTypeHandler(VLC->scriptHandler, "script"))); + handlers.insert(std::make_pair("battlefields", ContentTypeHandler(VLC->battlefieldsHandler, "battlefield"))); //TODO: any other types of moddables? } diff --git a/lib/GameConstants.cpp b/lib/GameConstants.cpp index ed73ef621..f9a5d84a2 100644 --- a/lib/GameConstants.cpp +++ b/lib/GameConstants.cpp @@ -34,6 +34,7 @@ #include "StringConstants.h" #include "CGeneralTextHandler.h" #include "CModHandler.h"//todo: remove +#include "BattleFieldHandler.h" const SlotID SlotID::COMMANDER_SLOT_PLACEHOLDER = SlotID(-2); const SlotID SlotID::SUMMONED_SLOT_PLACEHOLDER = SlotID(-3); @@ -255,3 +256,40 @@ std::ostream & operator<<(std::ostream & os, const EPathfindingLayer pathfinding if (it == pathfinderLayerToString.end()) return os << ""; else return os << it->second; } + +const BattleField BattleField::NONE; + +bool operator==(const BattleField & l, const BattleField & r) +{ + return l.num == r.num; +} + +bool operator!=(const BattleField & l, const BattleField & r) +{ + return l.num != r.num; +} + +bool operator<(const BattleField & l, const BattleField & r) +{ + return l.num < r.num; +} + +BattleField::operator std::string() const +{ + return getInfo()->identifier; +} + +const BattleFieldInfo * BattleField::getInfo() const +{ + return VLC->battlefields()->getById(*this); +} + +BattleField BattleField::fromString(std::string identifier) +{ + auto rawId = VLC->modh->identifiers.getIdentifier("core", "battlefield", identifier); + + if(rawId) + return BattleField(rawId.get()); + else + return BattleField::NONE; +} \ No newline at end of file diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 636766ed9..7dfbde61a 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -1109,6 +1109,23 @@ public: ID_LIKE_OPERATORS(SpellID, SpellID::ESpellID) +class BattleFieldInfo; +class BattleField : public BaseForID +{ + INSTID_LIKE_CLASS_COMMON(BattleField, si32) + + DLL_LINKAGE static const BattleField NONE; + + DLL_LINKAGE friend bool operator==(const BattleField & l, const BattleField & r); + DLL_LINKAGE friend bool operator!=(const BattleField & l, const BattleField & r); + DLL_LINKAGE friend bool operator<(const BattleField & l, const BattleField & r); + + DLL_LINKAGE operator std::string() const; + DLL_LINKAGE const BattleFieldInfo * getInfo() const; + + DLL_LINKAGE static BattleField fromString(std::string identifier); +}; + enum class ESpellSchool: ui8 { AIR = 0, diff --git a/lib/IGameCallback.cpp b/lib/IGameCallback.cpp index cc75b45d0..b5d6f37b0 100644 --- a/lib/IGameCallback.cpp +++ b/lib/IGameCallback.cpp @@ -16,6 +16,7 @@ #include "NetPacks.h" #include "CBonusTypeHandler.h" #include "CModHandler.h" +#include "BattleFieldHandler.h" #include "serializer/CSerializer.h" // for SAVEGAME_MAGIC #include "serializer/BinaryDeserializer.h" diff --git a/lib/Terrain.cpp b/lib/Terrain.cpp index 4a669fc86..24afad471 100644 --- a/lib/Terrain.cpp +++ b/lib/Terrain.cpp @@ -18,8 +18,6 @@ const Terrain Terrain::ANY("ANY"); -const BattleField BattleField::NONE(""); - Terrain Terrain::createTerrainTypeH3M(int tId) { static std::array terrainsH3M @@ -227,27 +225,3 @@ bool Terrain::isTransitionRequired() const { return Terrain::Manager::getInfo(*this).transitionRequired; } - -bool operator==(const BattleField & l, const BattleField & r) -{ - return l.name == r.name; -} - -bool operator!=(const BattleField & l, const BattleField & r) -{ - return l.name != r.name; -} - -bool operator<(const BattleField & l, const BattleField & r) -{ - return l.name < r.name; -} -BattleField::operator std::string() const -{ - return name; -} - -int BattleField::hash() const -{ - return std::hash{}(name); -} diff --git a/lib/Terrain.h b/lib/Terrain.h index 63dac1b6f..40d2e0778 100644 --- a/lib/Terrain.h +++ b/lib/Terrain.h @@ -11,47 +11,9 @@ #pragma once #include "ConstTransitivePtr.h" +#include "GameConstants.h" #include "JsonNode.h" -class DLL_LINKAGE BattleField -{ -public: - // 1. sand/shore 2. sand/mesas 3. dirt/birches 4. dirt/hills 5. dirt/pines 6. grass/hills 7. grass/pines - //8. lava 9. magic plains 10. snow/mountains 11. snow/trees 12. subterranean 13. swamp/trees 14. fiery fields - //15. rock lands 16. magic clouds 17. lucid pools 18. holy ground 19. clover field 20. evil fog - //21. "favorable winds" text on magic plains background 22. cursed ground 23. rough 24. ship to ship 25. ship - /*enum EBFieldType {NONE = -1, NONE2, SAND_SHORE, SAND_MESAS, DIRT_BIRCHES, DIRT_HILLS, DIRT_PINES, GRASS_HILLS, - GRASS_PINES, LAVA, MAGIC_PLAINS, SNOW_MOUNTAINS, SNOW_TREES, SUBTERRANEAN, SWAMP_TREES, FIERY_FIELDS, - ROCKLANDS, MAGIC_CLOUDS, LUCID_POOLS, HOLY_GROUND, CLOVER_FIELD, EVIL_FOG, FAVORABLE_WINDS, CURSED_GROUND, - ROUGH, SHIP_TO_SHIP, SHIP - };*/ - - BattleField(const std::string & type = "") : name(type) - {} - - static const BattleField NONE; - - DLL_LINKAGE friend bool operator==(const BattleField & l, const BattleField & r); - DLL_LINKAGE friend bool operator!=(const BattleField & l, const BattleField & r); - DLL_LINKAGE friend bool operator<(const BattleField & l, const BattleField & r); - - operator std::string() const; - int hash() const; - - template void serialize(Handler &h, const int version) - { - h & name; - } - - bool isSpecial() const - { - return name.find('_') >= 0; // hack for special battlefields, move to JSON - } - -protected: - - std::string name; -}; class DLL_LINKAGE Terrain { @@ -77,7 +39,7 @@ public: std::string terrainViewPatterns; int horseSoundId; Type type; - std::vector battleFields; + std::vector battleFields; }; class DLL_LINKAGE Manager diff --git a/lib/VCMI_Lib.cpp b/lib/VCMI_Lib.cpp index 30ba67e41..ff8d35eec 100644 --- a/lib/VCMI_Lib.cpp +++ b/lib/VCMI_Lib.cpp @@ -32,6 +32,7 @@ #include "rmg/CRmgTemplateStorage.h" #include "mapping/CMapEditManager.h" #include "ScriptHandler.h" +#include "BattleFieldHandler.h" LibClasses * VLC = nullptr; @@ -110,6 +111,11 @@ spells::effects::Registry * LibClasses::spellEffects() return spells::effects::GlobalRegistry::get(); } +const BattleFieldService * LibClasses::battlefields() const +{ + return battlefieldsHandler; +} + void LibClasses::updateEntity(Metatype metatype, int32_t index, const JsonNode & data) { switch(metatype) @@ -205,6 +211,8 @@ void LibClasses::init(bool onlyEssential) createHandler(scriptHandler, "Script", pomtime); + createHandler(battlefieldsHandler, "Battlefields", pomtime); + logGlobal->info("\tInitializing handlers: %d ms", totalTime.getDiff()); modh->load(); @@ -231,6 +239,7 @@ void LibClasses::clear() delete tplh; delete terviewh; delete scriptHandler; + delete battlefieldsHandler; makeNull(); } @@ -250,6 +259,7 @@ void LibClasses::makeNull() tplh = nullptr; terviewh = nullptr; scriptHandler = nullptr; + battlefieldsHandler = nullptr; } LibClasses::LibClasses() diff --git a/lib/VCMI_Lib.h b/lib/VCMI_Lib.h index e982e3cfe..4ab7c126c 100644 --- a/lib/VCMI_Lib.h +++ b/lib/VCMI_Lib.h @@ -24,6 +24,7 @@ class CTownHandler; class CGeneralTextHandler; class CModHandler; class CContentHandler; +class BattleFieldHandler; class IBonusTypeHandler; class CBonusTypeHandler; class CTerrainViewPatternConfig; @@ -56,6 +57,7 @@ public: const scripting::Service * scripts() const override; const spells::Service * spells() const override; const SkillService * skills() const override; + const BattleFieldService * battlefields() const override; void updateEntity(Metatype metatype, int32_t index, const JsonNode & data) override; @@ -76,6 +78,7 @@ public: CModHandler * modh; CTerrainViewPatternConfig * terviewh; CRmgTemplateStorage * tplh; + BattleFieldHandler * battlefieldsHandler; scripting::ScriptHandler * scriptHandler; LibClasses(); //c-tor, loads .lods and NULLs handlers @@ -104,6 +107,8 @@ public: h & objtypeh; h & spellh; h & skillh; + h & battlefieldsHandler; + if(!h.saving) { //modh will be changed and modh->content will be empty after deserialization diff --git a/lib/battle/BattleInfo.cpp b/lib/battle/BattleInfo.cpp index 1f4587901..59925927e 100644 --- a/lib/battle/BattleInfo.cpp +++ b/lib/battle/BattleInfo.cpp @@ -16,6 +16,7 @@ #include "../mapObjects/CGTownInstance.h" #include "../CGeneralTextHandler.h" #include "../Terrain.h" +#include "../BattleFieldHandler.h" //TODO: remove #include "../IGameCallback.h" @@ -458,64 +459,12 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, const Terrain & terrain, auto good = std::make_shared(EAlignment::GOOD); auto evil = std::make_shared(EAlignment::EVIL); - //giving terrain overlay premies - int bonusSubtype = -1; + auto bgInfo = VLC->battlefields()->getById(battlefieldType); - if(battlefieldType == BattleField("magic_plains")) + for(const std::shared_ptr & bonus : bgInfo->bonuses) { - bonusSubtype = 0; + curB->addNewBonus(bonus); } - if(battlefieldType == BattleField("fiery_fields")) - { - if(bonusSubtype == -1) bonusSubtype = 2; - } - if(battlefieldType == BattleField("rocklands")) - { - if(bonusSubtype == -1) bonusSubtype = 8; - } - if(battlefieldType == BattleField("magic_clouds")) - { - if(bonusSubtype == -1) bonusSubtype = 1; - } - if(battlefieldType == BattleField("lucid_pools")) - { - if(bonusSubtype == -1) bonusSubtype = 4; - } - if(bonusSubtype != -1) - { //common part for cases 9, 14, 15, 16, 17 - curB->addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::MAGIC_SCHOOL_SKILL,Bonus::TERRAIN_OVERLAY, 3, battlefieldType.hash(), bonusSubtype)); - } - else if(battlefieldType == BattleField("holy_ground")) - { - std::string goodArmyDesc = VLC->generaltexth->arraytxt[123]; - goodArmyDesc.erase(goodArmyDesc.size() - 2, 2); //omitting hardcoded +1 in description - std::string evilArmyDesc = VLC->generaltexth->arraytxt[124]; - evilArmyDesc.erase(evilArmyDesc.size() - 2, 2); - curB->addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::TERRAIN_OVERLAY, +1, battlefieldType.hash(), goodArmyDesc, 0)->addLimiter(good)); - curB->addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::TERRAIN_OVERLAY, -1, battlefieldType.hash(), evilArmyDesc, 0)->addLimiter(evil)); - } - else if(battlefieldType == BattleField("clover_field")) - { //+2 luck bonus for neutral creatures - std::string desc = VLC->generaltexth->arraytxt[83]; - desc.erase(desc.size() - 2, 2); - curB->addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::LUCK, Bonus::TERRAIN_OVERLAY, +2, battlefieldType.hash(), desc, 0)->addLimiter(neutral)); - } - else if(battlefieldType == BattleField("evil_fog")) - { - std::string goodArmyDesc = VLC->generaltexth->arraytxt[126]; - goodArmyDesc.erase(goodArmyDesc.size() - 2, 2); - std::string evilArmyDesc = VLC->generaltexth->arraytxt[125]; - evilArmyDesc.erase(evilArmyDesc.size() - 2, 2); - curB->addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::TERRAIN_OVERLAY, -1, battlefieldType.hash(), goodArmyDesc, 0)->addLimiter(good)); - curB->addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::TERRAIN_OVERLAY, +1, battlefieldType.hash(), evilArmyDesc, 0)->addLimiter(evil)); - } - else if(battlefieldType == BattleField("cursed_ground")) - { - curB->addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::NO_MORALE, Bonus::TERRAIN_OVERLAY, 0, battlefieldType.hash(), VLC->generaltexth->arraytxt[112], 0)); - curB->addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::NO_LUCK, Bonus::TERRAIN_OVERLAY, 0, battlefieldType.hash(), VLC->generaltexth->arraytxt[81], 0)); - curB->addNewBonus(std::make_shared(Bonus::ONE_BATTLE, Bonus::BLOCK_MAGIC_ABOVE, Bonus::TERRAIN_OVERLAY, 1, battlefieldType.hash(), 0, Bonus::INDEPENDENT_MIN)); - } - //overlay premies given //native terrain bonuses static auto nativeTerrain = std::make_shared(); diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index a2122bcbb..fbeb7d20a 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -17,6 +17,7 @@ #include "../NetPacks.h" #include "../spells/CSpellHandler.h" #include "../mapObjects/CGTownInstance.h" +#include "../BattleFieldHandler.h" namespace SiegeStuffThatShouldBeMovedToHandlers // <=== TODO { @@ -1058,24 +1059,15 @@ AccessibilityInfo CBattleInfoCallback::getAccesibility() const } //special battlefields with logically unavailable tiles - std::vector impassableHexes; - if(battleGetBattlefieldType() == BattleField("ship_to_ship")) + auto bFieldType = battleGetBattlefieldType(); + + if(bFieldType != BattleField::NONE) { - impassableHexes = - { - 6, 7, 8, 9, - 24, 25, 26, - 58, 59, 60, - 75, 76, 77, - 92, 93, 94, - 109, 110, 111, - 126, 127, 128, - 159, 160, 161, 162, 163, - 176, 177, 178, 179, 180 - }; + std::vector impassableHexes = bFieldType.getInfo()->impassableHexes; + + for(auto hex : impassableHexes) + ret[hex] = EAccessibility::UNAVAILABLE; } - for(auto hex : impassableHexes) - ret[hex] = EAccessibility::UNAVAILABLE; //gate -> should be before stacks if(battleGetSiegeLevel() > 0) diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index 638ad7cea..b9525cb0a 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -515,10 +515,10 @@ void AObjectTypeHandler::init(const JsonNode & input, boost::optional>(input["aiValue"].Integer()); - if(input["battleground"].isNull()) - battlefield = BattleField::NONE; + if(input["battleground"].getType() == JsonNode::JsonType::DATA_STRING) + battlefield = input["battleground"].String(); else - battlefield = BattleField(input["battleground"].String()); + battlefield = boost::none; initTypeData(input); } @@ -577,7 +577,7 @@ std::vector AObjectTypeHandler::getTemplates() const BattleField AObjectTypeHandler::getBattlefield() const { - return battlefield; + return battlefield ? BattleField::fromString(battlefield.get()) : BattleField::NONE; } std::vector AObjectTypeHandler::getTemplates(const Terrain & terrainType) const diff --git a/lib/mapObjects/CObjectClassesHandler.h b/lib/mapObjects/CObjectClassesHandler.h index c46275261..e578f585c 100644 --- a/lib/mapObjects/CObjectClassesHandler.h +++ b/lib/mapObjects/CObjectClassesHandler.h @@ -150,7 +150,7 @@ class DLL_LINKAGE AObjectTypeHandler : public boost::noncopyable boost::optional aiValue; - BattleField battlefield; + boost::optional battlefield; protected: void preInitObject(CGObjectInstance * obj) const; @@ -219,6 +219,7 @@ public: h & subTypeName; h & sounds; h & aiValue; + h & battlefield; } }; diff --git a/lib/mapping/CMapEditManager.cpp b/lib/mapping/CMapEditManager.cpp index e514084c9..50ddd52af 100644 --- a/lib/mapping/CMapEditManager.cpp +++ b/lib/mapping/CMapEditManager.cpp @@ -461,12 +461,15 @@ const std::vector & CTerrainViewPatternCon return iter->second; } -boost::optional CTerrainViewPatternConfig::getTerrainViewPatternById(const Terrain & terrain, const std::string & id) const +boost::optional CTerrainViewPatternConfig::getTerrainViewPatternById(std::string patternId, const std::string & id) const { - const std::vector & groupPatterns = getTerrainViewPatterns(terrain); + auto iter = terrainViewPatterns.find(patternId); + const std::vector & groupPatterns = (iter == terrainViewPatterns.end()) ? terrainViewPatterns.at("normal") : iter->second; + for (const TVPVector & patternFlips : groupPatterns) { const TerrainViewPattern & pattern = patternFlips.front(); + if(id == pattern.id) { return boost::optional(pattern); diff --git a/lib/mapping/CMapEditManager.h b/lib/mapping/CMapEditManager.h index 2bd42e26d..041be905e 100644 --- a/lib/mapping/CMapEditManager.h +++ b/lib/mapping/CMapEditManager.h @@ -327,7 +327,7 @@ public: ~CTerrainViewPatternConfig(); const std::vector & getTerrainViewPatterns(const Terrain & terrain) const; - boost::optional getTerrainViewPatternById(const Terrain & terrain, const std::string & id) const; + boost::optional getTerrainViewPatternById(std::string patternId, const std::string & id) const; boost::optional getTerrainViewPatternsById(const Terrain & terrain, const std::string & id) const; const TVPVector * getTerrainTypePatternById(const std::string & id) const; void flipPattern(TerrainViewPattern & pattern, int flip) const; diff --git a/lib/registerTypes/RegisterTypes.h b/lib/registerTypes/RegisterTypes.h index a20235254..6bf06044b 100644 --- a/lib/registerTypes/RegisterTypes.h +++ b/lib/registerTypes/RegisterTypes.h @@ -55,6 +55,7 @@ void registerTypesMapObjects1(Serializer &s) s.template registerType(); s.template registerType(); s.template registerType(); s.template registerType(); + s.template registerType(); s.template registerType(); s.template registerType(); s.template registerType(); s.template registerType(); @@ -108,6 +109,7 @@ void registerTypesMapObjectTypes(Serializer &s) REGISTER_GENERIC_HANDLER(CGHeroInstance); REGISTER_GENERIC_HANDLER(CGKeymasterTent); REGISTER_GENERIC_HANDLER(CGLighthouse); + REGISTER_GENERIC_HANDLER(CGTerrainPatch); REGISTER_GENERIC_HANDLER(CGMagi); REGISTER_GENERIC_HANDLER(CGMagicSpring); REGISTER_GENERIC_HANDLER(CGMagicWell); diff --git a/lib/rmg/CMapGenOptions.cpp b/lib/rmg/CMapGenOptions.cpp index ba71ace26..55802f2a2 100644 --- a/lib/rmg/CMapGenOptions.cpp +++ b/lib/rmg/CMapGenOptions.cpp @@ -246,8 +246,18 @@ void CMapGenOptions::finalize(CRandomGenerator & rand) if(waterContent == EWaterContent::RANDOM) { - waterContent = *RandomGeneratorUtil::nextItem(mapTemplate->getWaterContentAllowed(), rand); + auto allowedContent = mapTemplate->getWaterContentAllowed(); + + if(allowedContent.size()) + { + waterContent = *RandomGeneratorUtil::nextItem(mapTemplate->getWaterContentAllowed(), rand); + } + else + { + waterContent = EWaterContent::NONE; + } } + if(monsterStrength == EMonsterStrength::RANDOM) { monsterStrength = static_cast(rand.nextInt(EMonsterStrength::GLOBAL_WEAK, EMonsterStrength::GLOBAL_STRONG)); diff --git a/lib/rmg/CRmgTemplateZone.cpp b/lib/rmg/CRmgTemplateZone.cpp index 2600fdc55..eefbd3495 100644 --- a/lib/rmg/CRmgTemplateZone.cpp +++ b/lib/rmg/CRmgTemplateZone.cpp @@ -2019,6 +2019,10 @@ int3 CRmgTemplateZone::makeBoat(TRmgTemplateZoneId land, const std::set & { std::set lakeCoast; std::set_intersection(gen->getZones()[land]->getCoastTiles().begin(), gen->getZones()[land]->getCoastTiles().end(), lake.begin(), lake.end(), std::inserter(lakeCoast, lakeCoast.begin())); + + if(lakeCoast.empty()) + return int3(-1, -1, -1); + for(int randomAttempts = 0; randomAttempts<5; ++randomAttempts) { auto coastTile = *RandomGeneratorUtil::nextItem(lakeCoast, gen->rand); diff --git a/lib/spells/ISpellMechanics.cpp b/lib/spells/ISpellMechanics.cpp index 1aa00f649..a80475041 100644 --- a/lib/spells/ISpellMechanics.cpp +++ b/lib/spells/ISpellMechanics.cpp @@ -12,6 +12,7 @@ #include "ISpellMechanics.h" #include "../CRandomGenerator.h" +#include "../VCMI_Lib.h" #include "../HeroBonus.h" #include "../battle/CBattleInfoCallback.h" @@ -39,6 +40,7 @@ #include "../CHeroHandler.h"//todo: remove #include "../IGameCallback.h"//todo: remove +#include "../BattleFieldHandler.h" namespace spells { @@ -520,7 +522,7 @@ bool BaseMechanics::adaptProblem(ESpellCastProblem::ESpellCastProblem source, Pr caster->getCasterName(text); target.add(std::move(text), spells::Problem::NORMAL); } - else if(b && b->source == Bonus::TERRAIN_OVERLAY && b->sid == BattleField("cursed_ground").hash()) + else if(b && b->source == Bonus::TERRAIN_OVERLAY && VLC->battlefields()->getByIndex(b->sid)->identifier == "cursed_ground") { text.addTxt(MetaString::GENERAL_TXT, 537); target.add(std::move(text), spells::Problem::NORMAL); diff --git a/scripting/lua/api/BattleCb.cpp b/scripting/lua/api/BattleCb.cpp index 6f4f2a81a..0f8127797 100644 --- a/scripting/lua/api/BattleCb.cpp +++ b/scripting/lua/api/BattleCb.cpp @@ -10,12 +10,14 @@ #include "StdInc.h" #include "BattleCb.h" +#include #include "../LuaStack.h" #include "../LuaCallWrapper.h" #include "../../../lib/GameConstants.h" #include "../../../lib/battle/Unit.h" +#include "../../../lib/BattleFieldHandler.h" namespace scripting { @@ -73,7 +75,7 @@ int BattleCbProxy::getBattlefieldType(lua_State * L) auto ret = object->battleGetBattlefieldType(); - return LuaStack::quickRetStr(L, ret); + return LuaStack::quickRetStr(L, ret.getInfo()->identifier); } int BattleCbProxy::getTerrainType(lua_State * L) diff --git a/scripts/lib/erm/BU.lua b/scripts/lib/erm/BU.lua index ee4ee6b9f..0aabe23aa 100644 --- a/scripts/lib/erm/BU.lua +++ b/scripts/lib/erm/BU.lua @@ -55,16 +55,16 @@ end local SPECIAL_FIELDS = {} -SPECIAL_FIELDS[0] = 0 -SPECIAL_FIELDS[22] = 1 -SPECIAL_FIELDS[9] = 2 -SPECIAL_FIELDS[18] = 3 -SPECIAL_FIELDS[20] = 4 -SPECIAL_FIELDS[19] = 5 -SPECIAL_FIELDS[17] = 6 -SPECIAL_FIELDS[14] = 7 -SPECIAL_FIELDS[15] = 8 -SPECIAL_FIELDS[16] = 9 +SPECIAL_FIELDS['sand_shore'] = 0 +SPECIAL_FIELDS['cursed_ground'] = 1 +SPECIAL_FIELDS['magic_plains'] = 2 +SPECIAL_FIELDS['holy_ground'] = 3 +SPECIAL_FIELDS['evil_fog'] = 4 +SPECIAL_FIELDS['clover_field'] = 5 +SPECIAL_FIELDS['lucid_pools'] = 6 +SPECIAL_FIELDS['fiery_fields'] = 7 +SPECIAL_FIELDS['rocklands'] = 8 +SPECIAL_FIELDS['magic_clouds'] = 9 function BU:G(x, p1) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 71754f0ea..383e7b205 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2221,7 +2221,7 @@ void CGameHandler::setupBattle(int3 tile, const CArmedInstance *armies[2], const BattleField terType = gs->battleGetBattlefieldType(tile, getRandomGenerator()); if (heroes[0] && heroes[0]->boat && heroes[1] && heroes[1]->boat) - terType = BattleField("ship_to_ship"); + terType = BattleField::fromString("ship_to_ship"); //send info about battles BattleStart bs; diff --git a/test/battle/CBattleInfoCallbackTest.cpp b/test/battle/CBattleInfoCallbackTest.cpp index b552304f4..4504180f8 100644 --- a/test/battle/CBattleInfoCallbackTest.cpp +++ b/test/battle/CBattleInfoCallbackTest.cpp @@ -10,6 +10,7 @@ #include "StdInc.h" #include "../../lib/battle/CBattleInfoCallback.h" +#include "../../lib/battle/CUnitState.h" #include @@ -25,7 +26,15 @@ using namespace testing; class UnitFake : public UnitMock { +private: + std::shared_ptr state; + public: + UnitFake() + { + state.reset(new CUnitStateDetached(this, this)); + } + void addNewBonus(const std::shared_ptr & b) { bonusFake.addNewBonus(b); @@ -58,6 +67,13 @@ public: EXPECT_CALL(*this, unitSlot()).WillRepeatedly(Return(SlotID(0))); EXPECT_CALL(*this, creatureIndex()).WillRepeatedly(Return(0)); } + + void setDefaultState() + { + EXPECT_CALL(*this, isClone()).WillRepeatedly(Return(false)); + EXPECT_CALL(*this, isCaster()).WillRepeatedly(Return(false)); + EXPECT_CALL(*this, acquireState()).WillRepeatedly(Return(state)); + } private: BonusBearerMock bonusFake; }; @@ -207,6 +223,7 @@ TEST_F(BattleFinishedTest, LastAliveUnitWins) { UnitFake & unit = unitsFake.add(1); unit.makeAlive(); + unit.setDefaultState(); setDefaultExpectations(); startBattle(); @@ -232,6 +249,7 @@ TEST_F(BattleFinishedTest, LastWarMachineNotWins) UnitFake & unit = unitsFake.add(0); unit.makeAlive(); unit.makeWarMachine(); + unit.setDefaultState(); setDefaultExpectations(); startBattle(); @@ -241,17 +259,26 @@ TEST_F(BattleFinishedTest, LastWarMachineNotWins) TEST_F(BattleFinishedTest, LastWarMachineLoose) { - UnitFake & unit1 = unitsFake.add(0); - unit1.makeAlive(); + try + { + UnitFake & unit1 = unitsFake.add(0); + unit1.makeAlive(); + unit1.setDefaultState(); - UnitFake & unit2 = unitsFake.add(1); - unit2.makeAlive(); - unit2.makeWarMachine(); + UnitFake & unit2 = unitsFake.add(1); + unit2.makeAlive(); + unit2.makeWarMachine(); + unit2.setDefaultState(); - setDefaultExpectations(); - startBattle(); + setDefaultExpectations(); + startBattle(); - expectBattleWinner(0); + expectBattleWinner(0); + } + catch(std::exception e) + { + logGlobal->error(e.what()); + } } class BattleMatchOwnerTest : public CBattleInfoCallbackTest diff --git a/test/erm/ERM_BU.cpp b/test/erm/ERM_BU.cpp index 87273b2e9..4db1c3bc7 100644 --- a/test/erm/ERM_BU.cpp +++ b/test/erm/ERM_BU.cpp @@ -155,7 +155,7 @@ TEST_F(ERM_BU_G, Get) source << "!?PI;" << std::endl; source << "!!BU:G?v1;" << std::endl; - EXPECT_CALL(binfoMock, battleGetBattlefieldType()).WillOnce(Return(BattleField("snow_trees"))); + EXPECT_CALL(binfoMock, battleGetBattlefieldType()).WillOnce(Return(BattleField::fromString("snow_trees"))); loadScript(VLC->scriptHandler->erm, source.str()); @@ -169,12 +169,14 @@ TEST_F(ERM_BU_G, Get) TEST_F(ERM_BU_G, Get2) { + const int EXPECTED_ERM_FOG_CODE = 4; + std::stringstream source; source << "VERM" << std::endl; source << "!?PI;" << std::endl; source << "!!BU:G?v1;" << std::endl; - EXPECT_CALL(binfoMock, battleGetBattlefieldType()).WillOnce(Return(BattleField("evil_fog"))); + EXPECT_CALL(binfoMock, battleGetBattlefieldType()).WillOnce(Return(BattleField::fromString("evil_fog"))); loadScript(VLC->scriptHandler->erm, source.str()); runServer(); @@ -182,7 +184,7 @@ TEST_F(ERM_BU_G, Get2) JsonNode actualState = context->saveState(); - EXPECT_EQ(actualState["ERM"]["v"]["1"], JsonUtils::floatNode(4)) << actualState.toJson(true); + EXPECT_EQ(actualState["ERM"]["v"]["1"], JsonUtils::floatNode(EXPECTED_ERM_FOG_CODE)) << actualState.toJson(true); } //TODO: ERM_BU_G Set diff --git a/test/game/CGameStateTest.cpp b/test/game/CGameStateTest.cpp index aebb82783..984cd6c02 100644 --- a/test/game/CGameStateTest.cpp +++ b/test/game/CGameStateTest.cpp @@ -194,7 +194,7 @@ public: const auto t = gameCallback->getTile(tile); Terrain terrain = t->terType; - BattleField terType = BattleField("grass_hills"); + BattleField terType = BattleField::fromString("grass_hills"); //send info about battles diff --git a/test/map/CMapEditManagerTest.cpp b/test/map/CMapEditManagerTest.cpp index f33ff4d9d..d028ac51f 100644 --- a/test/map/CMapEditManagerTest.cpp +++ b/test/map/CMapEditManagerTest.cpp @@ -132,7 +132,7 @@ TEST(MapManager, DrawTerrain_View) const auto & id = patternParts[1]; // Get mapping range - const auto & pattern = VLC->terviewh->getTerrainViewPatternById(groupStr, id); + const auto & pattern = VLC->terviewh->getTerrainViewPatternById(groupStr, id); const auto & mapping = (*pattern).mapping; const auto & positionsNode = node["pos"].Vector(); diff --git a/test/map/CMapFormatTest.cpp b/test/map/CMapFormatTest.cpp index bd497f6ac..e1d88f7a4 100644 --- a/test/map/CMapFormatTest.cpp +++ b/test/map/CMapFormatTest.cpp @@ -23,6 +23,7 @@ #include "MapComparer.h" #include "../JsonComparer.h" +#include "mock/ZoneOptionsFake.h" static const int TEST_RANDOM_SEED = 1337; @@ -43,10 +44,12 @@ TEST(MapFormat, Random) CMapGenOptions opt; CRmgTemplate tmpl; + std::shared_ptr zoneOptions = std::make_shared(); const_cast(tmpl.getCpuPlayers()).addRange(1, 4); - const_cast(tmpl.getZones())[0] = std::make_shared(); + const_cast(tmpl.getZones())[0] = zoneOptions; + zoneOptions->setOwner(1); opt.setMapTemplate(&tmpl); opt.setHeight(CMapHeader::MAP_SIZE_MIDDLE); diff --git a/test/mock/BattleFake.cpp b/test/mock/BattleFake.cpp index 23ce30f2f..f7bf0d595 100644 --- a/test/mock/BattleFake.cpp +++ b/test/mock/BattleFake.cpp @@ -92,7 +92,7 @@ void BattleFake::setupEmptyBattlefield() { EXPECT_CALL(*this, getDefendedTown()).WillRepeatedly(Return(nullptr)); EXPECT_CALL(*this, getAllObstacles()).WillRepeatedly(Return(IBattleInfo::ObstacleCList())); - EXPECT_CALL(*this, getBattlefieldType()).WillRepeatedly(Return(BattleField::NONE)); + EXPECT_CALL(*this, getBattlefieldType()).WillRepeatedly(Return(BattleField::fromString("grass_hills"))); } diff --git a/test/mock/ZoneOptionsFake.h b/test/mock/ZoneOptionsFake.h new file mode 100644 index 000000000..e371b2519 --- /dev/null +++ b/test/mock/ZoneOptionsFake.h @@ -0,0 +1,23 @@ +/* + * BattleFake.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 + * + */ +#include "../../../lib/mapping/CMap.h" +#include "../../../lib/rmg/CMapGenOptions.h" +#include "../../../lib/rmg/CMapGenerator.h" + +#pragma once + +class ZoneOptionsFake : public rmg::ZoneOptions +{ +public: + void setOwner(int ow) + { + this->owner = ow; + } +}; \ No newline at end of file diff --git a/test/mock/mock_Services.h b/test/mock/mock_Services.h index 00f6ffff0..7c6587159 100644 --- a/test/mock/mock_Services.h +++ b/test/mock/mock_Services.h @@ -23,7 +23,8 @@ public: MOCK_CONST_METHOD0(heroTypes, const HeroTypeService *()); MOCK_CONST_METHOD0(scripts, const scripting::Service *()); MOCK_CONST_METHOD0(spells, const spells::Service *()); - MOCK_CONST_METHOD0(skills, const SkillService *()); + MOCK_CONST_METHOD0(skills, const SkillService * ()); + MOCK_CONST_METHOD0(battlefields, const BattleFieldService *()); MOCK_METHOD3(updateEntity, void(Metatype, int32_t, const JsonNode &)); diff --git a/test/spells/effects/CloneTest.cpp b/test/spells/effects/CloneTest.cpp index 81391ec63..60cfa4631 100644 --- a/test/spells/effects/CloneTest.cpp +++ b/test/spells/effects/CloneTest.cpp @@ -232,7 +232,7 @@ TEST_F(CloneApplyTest, SetsLifetimeMarker) { setDefaultExpectations(); - EXPECT_CALL(*battleFake, addUnitBonus(_,_)).WillOnce(Invoke(this, &CloneApplyTest::checkCloneLifetimeMarker)); + EXPECT_CALL(*battleFake, addUnitBonus(_, _)).WillOnce(Invoke(this, &CloneApplyTest::checkCloneLifetimeMarker)); subject->apply(&serverMock, &mechanicsMock, target); } From a454edacea767a3a87c0b0ab3a9daa143dca1ea2 Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Sat, 9 Jul 2022 19:00:03 +0300 Subject: [PATCH 05/15] Fix getting battlefield crash --- lib/mapObjects/CGHeroInstance.cpp | 6 ++++++ lib/mapObjects/CGHeroInstance.h | 2 ++ lib/mapObjects/CObjectHandler.h | 2 +- lib/mapObjects/MiscObjects.cpp | 5 +++++ lib/mapObjects/MiscObjects.h | 1 + 5 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 5fe402118..2e40ea49d 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -151,6 +151,12 @@ int3 CGHeroInstance::convertPosition(int3 src, bool toh3m) //toh3m=true: manifes return src; } } + +BattleField CGHeroInstance::getBattlefield() const +{ + return BattleField::NONE; +} + int3 CGHeroInstance::getPosition(bool h3m) const //h3m=true - returns position of hero object; h3m=false - returns position of hero 'manifestation' { if (h3m) diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index 02b738c68..4263fb818 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -270,6 +270,8 @@ public: void afterAddToMap(CMap * map) override; void updateFrom(const JsonNode & data) override; + + BattleField getBattlefield() const override; protected: void setPropertyDer(ui8 what, ui32 val) override;//synchr ///common part of hero instance and hero definition diff --git a/lib/mapObjects/CObjectHandler.h b/lib/mapObjects/CObjectHandler.h index 3db649b4e..851796db8 100644 --- a/lib/mapObjects/CObjectHandler.h +++ b/lib/mapObjects/CObjectHandler.h @@ -159,7 +159,7 @@ public: std::set getBlockedOffsets() const; //returns set of relative positions blocked by this object bool isVisitable() const; //returns true if object is visitable - BattleField getBattlefield() const; + virtual BattleField getBattlefield() const; virtual bool isTile2Terrain() const { return false; } diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 3a8f405ba..acf6d28ae 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -1406,6 +1406,11 @@ void CGArtifact::pick(const CGHeroInstance * h) const cb->removeObject(this); } +BattleField CGArtifact::getBattlefield() const +{ + return BattleField::NONE; +} + void CGArtifact::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const { if(result.winner == 0) //attacker won diff --git a/lib/mapObjects/MiscObjects.h b/lib/mapObjects/MiscObjects.h index e13165fe0..e970d9c44 100644 --- a/lib/mapObjects/MiscObjects.h +++ b/lib/mapObjects/MiscObjects.h @@ -201,6 +201,7 @@ public: void initObj(CRandomGenerator & rand) override; void afterAddToMap(CMap * map) override; + BattleField getBattlefield() const override; template void serialize(Handler &h, const int version) { From 4eb398217292d2cafeada74f64ac4eaa45fb5a91 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Tue, 12 Jul 2022 15:16:30 +0300 Subject: [PATCH 06/15] Fix typo --- config/battlefields.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/battlefields.json b/config/battlefields.json index cffc0df4a..f67281311 100644 --- a/config/battlefields.json +++ b/config/battlefields.json @@ -157,7 +157,7 @@ "rough": { "graphics" : "CMBKRGH.BMP" }, "ship_to_ship": { - "graphics" : "CMBKBOAT.BMP" + "graphics" : "CMBKBOAT.BMP", "impassableHexes" : [ 6, 7, 8, 9, 24, 25, 26, From c0db4f1d96d473d54e12823891de0737c3dcb333 Mon Sep 17 00:00:00 2001 From: Nordsoft91 Date: Tue, 9 Aug 2022 09:54:32 +0400 Subject: [PATCH 07/15] Random map generator refactoring (#762) random map generator refactoring and improvements --- client/CMusicHandler.h | 2 +- client/widgets/AdventureMapClasses.h | 2 +- config/randomMap.json | 10 - config/terrains.json | 14 +- lib/CCreatureHandler.h | 2 +- lib/CHeroHandler.h | 2 +- lib/CMakeLists.txt | 40 +- lib/Terrain.cpp | 39 +- lib/Terrain.h | 5 +- lib/mapping/CDrawRoadsOperation.cpp | 139 +- lib/mapping/CDrawRoadsOperation.h | 65 +- lib/mapping/CMapEditManager.cpp | 11 +- lib/mapping/CMapEditManager.h | 5 +- lib/rmg/CMapGenOptions.cpp | 3 + lib/rmg/CMapGenerator.cpp | 795 +----- lib/rmg/CMapGenerator.h | 81 +- lib/rmg/CRmgTemplate.cpp | 30 + lib/rmg/CRmgTemplate.h | 13 +- lib/rmg/CRmgTemplateZone.cpp | 3387 -------------------------- lib/rmg/CRmgTemplateZone.h | 237 -- lib/rmg/CZonePlacer.cpp | 64 +- lib/rmg/CZonePlacer.h | 20 +- lib/rmg/ConnectionsPlacer.cpp | 280 +++ lib/rmg/ConnectionsPlacer.h | 35 + lib/rmg/Functions.cpp | 220 ++ lib/rmg/Functions.h | 48 + lib/rmg/ObjectManager.cpp | 417 ++++ lib/rmg/ObjectManager.h | 56 + lib/rmg/ObstaclePlacer.cpp | 188 ++ lib/rmg/ObstaclePlacer.h | 21 + lib/rmg/RiverPlacer.cpp | 404 +++ lib/rmg/RiverPlacer.h | 48 + lib/rmg/RmgArea.cpp | 399 +++ lib/rmg/RmgArea.h | 83 + lib/rmg/RmgMap.cpp | 343 +++ lib/rmg/RmgMap.h | 81 + lib/rmg/RmgObject.cpp | 327 +++ lib/rmg/RmgObject.h | 87 + lib/rmg/RmgPath.cpp | 193 ++ lib/rmg/RmgPath.h | 49 + lib/rmg/RoadPlacer.cpp | 105 + lib/rmg/RoadPlacer.h | 37 + lib/rmg/RockPlacer.cpp | 103 + lib/rmg/RockPlacer.h | 30 + lib/rmg/TerrainPainter.cpp | 35 + lib/rmg/TerrainPainter.h | 21 + lib/rmg/TileInfo.cpp | 78 + lib/rmg/TileInfo.h | 41 + lib/rmg/TownPlacer.cpp | 279 +++ lib/rmg/TownPlacer.h | 37 + lib/rmg/TreasurePlacer.cpp | 788 ++++++ lib/rmg/TreasurePlacer.h | 67 + lib/rmg/WaterAdopter.cpp | 261 ++ lib/rmg/WaterAdopter.h | 36 + lib/rmg/WaterProxy.cpp | 359 +++ lib/rmg/WaterProxy.h | 55 + lib/rmg/WaterRoutes.cpp | 116 + lib/rmg/WaterRoutes.h | 27 + lib/rmg/Zone.cpp | 410 ++++ lib/rmg/Zone.h | 143 ++ 60 files changed, 6721 insertions(+), 4552 deletions(-) delete mode 100644 lib/rmg/CRmgTemplateZone.cpp delete mode 100644 lib/rmg/CRmgTemplateZone.h create mode 100644 lib/rmg/ConnectionsPlacer.cpp create mode 100644 lib/rmg/ConnectionsPlacer.h create mode 100644 lib/rmg/Functions.cpp create mode 100644 lib/rmg/Functions.h create mode 100644 lib/rmg/ObjectManager.cpp create mode 100644 lib/rmg/ObjectManager.h create mode 100644 lib/rmg/ObstaclePlacer.cpp create mode 100644 lib/rmg/ObstaclePlacer.h create mode 100644 lib/rmg/RiverPlacer.cpp create mode 100644 lib/rmg/RiverPlacer.h create mode 100644 lib/rmg/RmgArea.cpp create mode 100644 lib/rmg/RmgArea.h create mode 100644 lib/rmg/RmgMap.cpp create mode 100644 lib/rmg/RmgMap.h create mode 100644 lib/rmg/RmgObject.cpp create mode 100644 lib/rmg/RmgObject.h create mode 100644 lib/rmg/RmgPath.cpp create mode 100644 lib/rmg/RmgPath.h create mode 100644 lib/rmg/RoadPlacer.cpp create mode 100644 lib/rmg/RoadPlacer.h create mode 100644 lib/rmg/RockPlacer.cpp create mode 100644 lib/rmg/RockPlacer.h create mode 100644 lib/rmg/TerrainPainter.cpp create mode 100644 lib/rmg/TerrainPainter.h create mode 100644 lib/rmg/TileInfo.cpp create mode 100644 lib/rmg/TileInfo.h create mode 100644 lib/rmg/TownPlacer.cpp create mode 100644 lib/rmg/TownPlacer.h create mode 100644 lib/rmg/TreasurePlacer.cpp create mode 100644 lib/rmg/TreasurePlacer.h create mode 100644 lib/rmg/WaterAdopter.cpp create mode 100644 lib/rmg/WaterAdopter.h create mode 100644 lib/rmg/WaterProxy.cpp create mode 100644 lib/rmg/WaterProxy.h create mode 100644 lib/rmg/WaterRoutes.cpp create mode 100644 lib/rmg/WaterRoutes.h create mode 100644 lib/rmg/Zone.cpp create mode 100644 lib/rmg/Zone.h diff --git a/client/CMusicHandler.h b/client/CMusicHandler.h index b2f3c9a81..ddcca0d17 100644 --- a/client/CMusicHandler.h +++ b/client/CMusicHandler.h @@ -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: diff --git a/client/widgets/AdventureMapClasses.h b/client/widgets/AdventureMapClasses.h index 97cb7da74..de9b98c8b 100644 --- a/client/widgets/AdventureMapClasses.h +++ b/client/widgets/AdventureMapClasses.h @@ -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 diff --git a/config/randomMap.json b/config/randomMap.json index 260acf3e0..4a9a9a50e 100644 --- a/config/randomMap.json +++ b/config/randomMap.json @@ -18,16 +18,6 @@ }, "mines" : { - "value" : - { - "wood" : 1500, - "ore" : 1500, - "gems" : 3500, - "crystal" : 3500, - "mercury" : 3500, - "sulfur" : 3500, - "gold" : 7000 - }, "extraResourcesLimit" : 3 }, "minGuardStrength" : 2000, diff --git a/config/terrains.json b/config/terrains.json index c62e2ee36..3b10c56d2 100644 --- a/config/terrains.json +++ b/config/terrains.json @@ -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" : { diff --git a/lib/CCreatureHandler.h b/lib/CCreatureHandler.h index 46939ea20..66f652a59 100644 --- a/lib/CCreatureHandler.h +++ b/lib/CCreatureHandler.h @@ -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 { diff --git a/lib/CHeroHandler.h b/lib/CHeroHandler.h index e690fcfa9..264eecfcb 100644 --- a/lib/CHeroHandler.h +++ b/lib/CHeroHandler.h @@ -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 diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 1606829f4..396e83ec9 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -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 diff --git a/lib/Terrain.cpp b/lib/Terrain.cpp index 24afad471..9c2073914 100644 --- a/lib/Terrain.cpp +++ b/lib/Terrain.cpp @@ -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::Manager::terrains() { - std::vector _terrains; + std::set _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(_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(terrain)); } std::ostream & operator<<(std::ostream & os, const Terrain terrainType) diff --git a/lib/Terrain.h b/lib/Terrain.h index 40d2e0778..2f20f12f4 100644 --- a/lib/Terrain.h +++ b/lib/Terrain.h @@ -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 battleFields; + std::vector prohibitTransitions; }; class DLL_LINKAGE Manager @@ -52,7 +55,7 @@ public: static Manager & get(); Manager(); - std::map terrainInfo; + std::unordered_map terrainInfo; }; /*enum EETerrainType diff --git a/lib/mapping/CDrawRoadsOperation.cpp b/lib/mapping/CDrawRoadsOperation.cpp index ea7398a4f..137169ae0 100644 --- a/lib/mapping/CDrawRoadsOperation.cpp +++ b/lib/mapping/CDrawRoadsOperation.cpp @@ -12,7 +12,7 @@ #include "CDrawRoadsOperation.h" #include "CMap.h" -const std::vector CDrawRoadsOperation::patterns = +const std::vector 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 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 & invalidated) +void CDrawLinesOperation::updateTiles(std::set & invalidated) { for(int3 coord : invalidated) { @@ -259,25 +252,7 @@ void CDrawRoadsOperation::updateTiles(std::set & 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 & 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 & 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 & mapping = pattern.riverMapping; + + tile.riverDir = gen->nextInt(mapping.first, mapping.second); + tile.extTileFlags = (tile.extTileFlags & 0b00111111) | (flip << 2); +} diff --git a/lib/mapping/CDrawRoadsOperation.h b/lib/mapping/CDrawRoadsOperation.h index 3b7c98ed8..b283c5dc8 100644 --- a/lib/mapping/CDrawRoadsOperation.h +++ b/lib/mapping/CDrawRoadsOperation.h @@ -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 roadMapping, riverMapping; @@ -39,20 +38,56 @@ private: int flip; }; - static const std::vector 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 patterns; + + void flipPattern(LinePattern & pattern, int flip) const; void updateTiles(std::set & 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; }; diff --git a/lib/mapping/CMapEditManager.cpp b/lib/mapping/CMapEditManager.cpp index 50ddd52af..d45c4241e 100644 --- a/lib/mapping/CMapEditManager.cpp +++ b/lib/mapping/CMapEditManager.cpp @@ -110,7 +110,7 @@ void CTerrainSelection::deselectRange(const MapRect & rect) }); } -void CTerrainSelection::setSelection(std::vector & vec) +void CTerrainSelection::setSelection(const std::vector & 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(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; } diff --git a/lib/mapping/CMapEditManager.h b/lib/mapping/CMapEditManager.h index 041be905e..cd296b0e0 100644 --- a/lib/mapping/CMapEditManager.h +++ b/lib/mapping/CMapEditManager.h @@ -93,7 +93,7 @@ public: void deselectRange(const MapRect & rect) override; void selectAll() override; void clearSelection() override; - void setSelection(std::vector & vec); + void setSelection(const std::vector & 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); diff --git a/lib/rmg/CMapGenOptions.cpp b/lib/rmg/CMapGenOptions.cpp index 55802f2a2..ed16416e6 100644 --- a/lib/rmg/CMapGenOptions.cpp +++ b/lib/rmg/CMapGenOptions.cpp @@ -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(getPlayerCount()), static_cast(getTeamCount()), static_cast(getCompOnlyPlayerCount()), static_cast(getCompOnlyTeamCount()), static_cast(getWaterContent()), static_cast(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) { diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index 5736f8855..450aeb538 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -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 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 foo) -{ - for(const int3 &dir : dirs4) - { - int3 n = pos + dir; - if(map->isInTheMap(n)) - foo(n); - } -} - -void CMapGenerator::foreachDiagonalNeighbour(const int3& pos, std::function 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(mapGenOptions); +} + +int CMapGenerator::getRandomSeed() const +{ + return randomSeed; } void CMapGenerator::loadConfig() { - static const std::map 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 CMapGenerator::generate() { - map = make_unique(); 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(mapGenOptions.getPlayerCount()) % + randomSeed % map->map().width % map->map().height % (map->map().twoLevel ? "2" : "1") % static_cast(mapGenOptions.getPlayerCount()) % static_cast(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(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(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> treasureZones; - for(auto it : zones) + for(auto it : map->getZones()) { - it.second->fill(); + it.second->initModificators(); + it.second->initFreeTiles(); + } + + std::vector> 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; jconnectRoads(); //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 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 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()) + zoneB->getModificator()->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 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 tilesA(tileSetA.begin(), tileSetA.end()), - tilesB(tileSetB.begin(), tileSetB.end()); - - std::vector 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(posA.dist2dSQ(lhs) - posB.dist2dSQ(lhs)) < std::abs((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(posA.dist2d(tile)); - float distanceFromB = static_cast(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()) + zoneA->getModificator()->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 CMapGenerator::getQuestArtsRemaning() const +const std::vector & 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; jgetZones()) + if(z.second->getType() == ETemplateZoneType::WATER) + return z.second.get(); + return nullptr; } diff --git a/lib/rmg/CMapGenerator.h b/lib/rmg/CMapGenerator.h index f0e67c81b..34ca69be0 100644 --- a/lib/rmg/CMapGenerator.h +++ b/lib/rmg/CMapGenerator.h @@ -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 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 waterTreasure; int shipyardGuard; int mineExtraResources; - std::map mineValues; int minGuardStrength; std::string defaultRoadType; int treasureValueLimit; @@ -68,84 +46,44 @@ public: std::vector questValues, questRewardValues; }; - using Zones = std::map>; - explicit CMapGenerator(CMapGenOptions& mapGenOptions, int RandomSeed = std::time(nullptr)); ~CMapGenerator(); // required due to std::unique_ptr const Config & getConfig() const; - mutable std::unique_ptr map; CRandomGenerator rand; - CMapEditManager* getEditManager() const; const CMapGenOptions& getMapGenOptions() const; std::unique_ptr generate(); - Zones & getZones(); - void createDirectConnections(); - void createConnections2(); void findZonesForQuestArts(); - void foreach_neighbour(const int3 &pos, std::function foo); - void foreachDirectNeighbour(const int3 &pos, std::function foo); - void foreachDiagonalNeighbour(const int3& pos, std::function 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 getQuestArtsRemaning() const; + const std::vector & 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 map; std::vector connectionsLeft; - Zones zones; - std::map zonesPerFaction; - ui32 zonesTotal; //zones that have their main town only - std::pair zoneWater; - - CTileInfo*** tiles; - boost::multi_array zoneColouring; //[z][x][y] + //std::pair zoneWater; int prisonsRemaining; //int questArtsRemaining; int monolithIndex; std::vector 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(); }; diff --git a/lib/rmg/CRmgTemplate.cpp b/lib/rmg/CRmgTemplate.cpp index 5dcf46732..1025ff874 100644 --- a/lib/rmg/CRmgTemplate.cpp +++ b/lib/rmg/CRmgTemplate.cpp @@ -252,6 +252,11 @@ void ZoneOptions::setMonsterTypes(const std::set & value) monsterTypes = value; } +const std::set & ZoneOptions::getMonsterTypes() const +{ + return monsterTypes; +} + void ZoneOptions::setMinesInfo(const std::map & value) { mines = value; @@ -302,6 +307,26 @@ std::vector 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 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) { diff --git a/lib/rmg/CRmgTemplate.h b/lib/rmg/CRmgTemplate.h index 24bfe6ecb..2637d8c5a 100644 --- a/lib/rmg/CRmgTemplate.h +++ b/lib/rmg/CRmgTemplate.h @@ -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 & getTerrainTypes() const; void setTerrainTypes(const std::set & value); + const CTownInfo & getPlayerTowns() const; + const CTownInfo & getNeutralTowns() const; std::set getDefaultTownTypes() const; const std::set & getTownTypes() const; + const std::set & getMonsterTypes() const; void setTownTypes(const std::set & value); void setMonsterTypes(const std::set & value); @@ -126,6 +130,11 @@ public: std::vector 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 townTypes; std::set monsterTypes; - EMonsterStrength::EMonsterStrength zoneMonsterStrength; - std::map mines; //obligatory mines to spawn in this zone std::vector treasureInfo; diff --git a/lib/rmg/CRmgTemplateZone.cpp b/lib/rmg/CRmgTemplateZone.cpp deleted file mode 100644 index eefbd3495..000000000 --- a/lib/rmg/CRmgTemplateZone.cpp +++ /dev/null @@ -1,3387 +0,0 @@ -/* - * CRmgTemplateZone.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 "CRmgTemplateZone.h" -#include "../mapping/CMapEditManager.h" -#include "../mapping/CMap.h" - -#include "../VCMI_Lib.h" -#include "../CTownHandler.h" -#include "../CCreatureHandler.h" -#include "../spells/CSpellHandler.h" //for choosing random spells - -#include "../mapObjects/CommonConstructors.h" -#include "../mapObjects/MapObjects.h" //needed to resolve templates for CommonConstructors.h -#include "../mapObjects/CGPandoraBox.h" -#include "../mapObjects/CRewardableObject.h" - -class CMap; -class CMapEditManager; -//class CGObjectInstance; - -using namespace rmg; //TODO: move all to namespace - - - -void CRmgTemplateZone::addRoadNode(const int3& node) -{ - roadNodes.insert(node); -} - -CTileInfo::CTileInfo():nearestObjectDistance(float(INT_MAX)), terrain() -{ - occupied = ETileType::POSSIBLE; //all tiles are initially possible to place objects or passages -} - -float CTileInfo::getNearestObjectDistance() const -{ - return nearestObjectDistance; -} - -void CTileInfo::setNearestObjectDistance(float value) -{ - nearestObjectDistance = std::max(0, value); //never negative (or unitialized) -} -bool CTileInfo::shouldBeBlocked() const -{ - return occupied == ETileType::BLOCKED; -} -bool CTileInfo::isBlocked() const -{ - return occupied == ETileType::BLOCKED || occupied == ETileType::USED; -} -bool CTileInfo::isPossible() const -{ - return occupied == ETileType::POSSIBLE; -} -bool CTileInfo::isFree() const -{ - return occupied == ETileType::FREE; -} - -bool CTileInfo::isRoad() const -{ - return roadType != ROAD_NAMES[0]; -} - -bool CTileInfo::isUsed() const -{ - return occupied == ETileType::USED; -} -void CTileInfo::setOccupied(ETileType::ETileType value) -{ - occupied = value; -} - -ETileType::ETileType CTileInfo::getTileType() const -{ - return occupied; -} - -Terrain CTileInfo::getTerrainType() const -{ - return terrain; -} - -void CTileInfo::setTerrainType(Terrain value) -{ - terrain = value; -} - -void CTileInfo::setRoadType(const std::string & value) -{ - roadType = value; -// setOccupied(ETileType::FREE); -} - - -CRmgTemplateZone::CRmgTemplateZone(CMapGenerator * Gen) - : ZoneOptions(), - townType(ETownType::NEUTRAL), - terrainType (Terrain("grass")), - minGuardedValue(0), - questArtZone(), - gen(Gen) -{ - -} - -bool CRmgTemplateZone::isUnderground() const -{ - return getPos().z; -} - -void CRmgTemplateZone::setOptions(const ZoneOptions& options) -{ - ZoneOptions::operator=(options); -} - -void CRmgTemplateZone::setQuestArtZone(std::shared_ptr otherZone) -{ - questArtZone = otherZone; -} - -std::set* CRmgTemplateZone::getFreePaths() -{ - return &freePaths; -} - -void CRmgTemplateZone::addFreePath(const int3 & p) -{ - gen->setOccupied(p, ETileType::FREE); - freePaths.insert(p); -} - -float3 CRmgTemplateZone::getCenter() const -{ - return center; -} -void CRmgTemplateZone::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(std::fmod(center.x, 1)); - center.y = static_cast(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); -} - - -bool CRmgTemplateZone::pointIsIn(int x, int y) -{ - return true; -} - -int3 CRmgTemplateZone::getPos() const -{ - return pos; -} -void CRmgTemplateZone::setPos(const int3 &Pos) -{ - pos = Pos; -} - -void CRmgTemplateZone::addTile (const int3 &Pos) -{ - tileinfo.insert(Pos); -} - -void CRmgTemplateZone::removeTile(const int3 & Pos) -{ - tileinfo.erase(Pos); - possibleTiles.erase(Pos); -} - -std::set CRmgTemplateZone::getTileInfo () const -{ - return tileinfo; -} -std::set CRmgTemplateZone::getPossibleTiles() const -{ - return possibleTiles; -} - -std::set CRmgTemplateZone::collectDistantTiles (float distance) const -{ - //TODO: mark tiles beyond zone as unavailable, but allow to connect with adjacent zones - - //for (auto tile : tileinfo) - //{ - // if (tile.dist2d(this->pos) > distance) - // { - // gen->setOccupied(tile, ETileType::USED); - // //gen->setOccupied(tile, ETileType::BLOCKED); //fixme: crash at rendering? - // } - //} - std::set discardedTiles; - for(auto& tile : tileinfo) - { - if(tile.dist2d(this->pos) > distance) - { - discardedTiles.insert(tile); - } - }; - return discardedTiles; -} - -void CRmgTemplateZone::clearTiles() -{ - tileinfo.clear(); -} - -void CRmgTemplateZone::initFreeTiles () -{ - vstd::copy_if(tileinfo, vstd::set_inserter(possibleTiles), [this](const int3 &tile) -> bool - { - return gen->isPossible(tile); - }); - if(freePaths.empty()) - { - addFreePath(pos); //zone must have at least one free tile where other paths go - for instance in the center - } -} - -void CRmgTemplateZone::createBorder() -{ - for(auto tile : tileinfo) - { - bool edge = false; - gen->foreach_neighbour(tile, [this, &edge](int3 &pos) - { - if (edge) - return; //optimization - do it only once - if (gen->getZoneID(pos) != id) //optimization - better than set search - { - //bugfix with missing pos - if (gen->isPossible(pos)) - gen->setOccupied(pos, ETileType::BLOCKED); - //we are edge if at least one tile does not belong to zone - //mark all nearby tiles blocked and we're done - gen->foreach_neighbour (pos, [this](int3 &nearbyPos) - { - if (gen->isPossible(nearbyPos)) - gen->setOccupied(nearbyPos, ETileType::BLOCKED); - }); - edge = true; - } - }); - } -} - -void CRmgTemplateZone::createWater(EWaterContent::EWaterContent waterContent, bool debug) -{ - if(waterContent == EWaterContent::NONE || isUnderground()) - return; //do nothing - - std::set waterTiles = collectDistantTiles((float)(getSize() + 1)); - - //add border tiles as water for ISLANDS - if(waterContent == EWaterContent::ISLANDS) - { - for(auto& tile : tileinfo) - { - if(gen->shouldBeBlocked(tile)) - { - waterTiles.insert(tile); - } - } - } - - std::list tilesQueue(waterTiles.begin(), waterTiles.end()); //tiles need to be processed - std::set tilesChecked = waterTiles; //tiles already processed - std::map> coastTilesMap; //key: distance to water; value: tiles with that distance - std::map tilesDist; //key: tile; value: distance to water - - //optimization: prefill distance for all tiles marked for water with 0 - for(auto& tile : waterTiles) - { - tilesDist[tile] = 0; - } - - //fills the distance-to-water map - while(!tilesQueue.empty()) - { - int3 src = tilesQueue.front(); - tilesQueue.pop_front(); - gen->foreachDirectNeighbour(src, [this, &src, &tilesDist, &tilesChecked, &coastTilesMap, &tilesQueue](const int3 & dst) - { - if(tilesChecked.find(dst) != tilesChecked.end()) - return; - - if(tileinfo.find(dst) != tileinfo.end()) - { - tilesDist[dst] = tilesDist[src] + 1; - coastTilesMap[tilesDist[dst]].insert(dst); - tilesChecked.insert(dst); - tilesQueue.push_back(dst); - } - }); - } - - //generating some irregularity of coast - int coastIdMax = fmin(sqrt(coastTilesMap.size()), 7.f); //size of coastTilesMap shows the most distant tile from water - assert(coastIdMax > 0); - tilesChecked.clear(); - for(int coastId = coastIdMax; coastId >= 1; --coastId) - { - //amount of iterations shall be proportion of coast perimeter - const int coastLength = coastTilesMap[coastId].size() / (coastId + 3); - for(int coastIter = 0; coastIter < coastLength; ++coastIter) - { - int3 tile = *RandomGeneratorUtil::nextItem(coastTilesMap[coastId], gen->rand); - if(tilesChecked.find(tile) != tilesChecked.end()) - continue; - if(gen->isUsed(tile) || gen->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(waterTiles.find(src) != waterTiles.end()) - continue; - - waterTiles.insert(src); - - gen->foreach_neighbour(src, [&src, &tilesDist, &tilesChecked, &tilesQueue](const int3 & dst) - { - if(tilesChecked.find(dst) != tilesChecked.end()) - return; - - if(tilesDist[dst] > 0 && tilesDist[src]-tilesDist[dst] == 1) - { - tilesQueue.push_back(dst); - tilesChecked.insert(dst); - } - }); - } - - //start filtering of narrow places and coast atrifacts - std::vector waterAdd; - for(int coastId = 1; coastId <= coastIdMax; ++coastId) - { - for(auto& tile : coastTilesMap[coastId]) - { - //collect neighbout water tiles - auto collectionLambda = [&waterTiles, &coastTilesMap](const int3 & t, std::set & outCollection) - { - if(waterTiles.find(t)!=waterTiles.end()) - { - coastTilesMap[0].insert(t); - outCollection.insert(t); - } - }; - std::set waterCoastDirect, waterCoastDiag; - gen->foreachDirectNeighbour(tile, std::bind(collectionLambda, std::placeholders::_1, std::ref(waterCoastDirect))); - gen->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.push_back(tile); - continue; - } - if(waterCoastDiagNum == 4 && waterCoastDirectNum == 2) - { - waterAdd.push_back(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.push_back(tile); - continue; - } - if(waterCoastDiagNum == 3 && diagSum != dirSum) - { - waterAdd.push_back(tile); - continue; - } - } - } - } - for(auto & i : waterAdd) - waterTiles.insert(i); - - //filtering tiny "lakes" - for(auto& tile : coastTilesMap[0]) //now it's only coast-water tiles - { - if(waterTiles.find(tile) == waterTiles.end()) //for ground tiles - continue; - - std::vector groundCoast; - gen->foreachDirectNeighbour(tile, [this, &waterTiles, &groundCoast](const int3 & t) - { - if(waterTiles.find(t) == waterTiles.end() && tileinfo.find(t) != tileinfo.end()) //for ground tiles of same zone - { - groundCoast.push_back(t); - } - }); - - if(groundCoast.size() >= 3) - { - waterTiles.erase(tile); - } - else - { - if(groundCoast.size() == 2) - { - if(groundCoast[0] + groundCoast[1] == int3()) - { - waterTiles.erase(tile); - } - } - else - { - if(!groundCoast.empty()) - { - coastTiles.insert(tile); - } - } - } - } - - //do not set water on tiles belong to other zones - vstd::erase_if(coastTiles, [&waterTiles](const int3 & tile) - { - return waterTiles.find(tile) == waterTiles.end(); - }); - - //transforming waterTiles to actual water - for(auto& tile : waterTiles) - { - gen->getZoneWater().second->addTile(tile); - gen->setZoneID(tile, gen->getZoneWater().first); - gen->setOccupied(tile, ETileType::POSSIBLE); - tileinfo.erase(tile); - possibleTiles.erase(tile); - } -} - -void CRmgTemplateZone::waterInitFreeTiles() -{ - std::set tilesAll(tileinfo.begin(), tileinfo.end()); //water tiles - std::list tilesQueue; //tiles need to be processed - std::set tilesChecked; - - //lambda for increasing distance of negihbour tiles - auto lakeSearch = [this, &tilesAll, &tilesQueue](const int3 & dst) - { - if(tilesAll.find(dst) == tilesAll.end()) - { - if(lakes.back().tiles.find(dst)==lakes.back().tiles.end()) - { - //we reach land! let's store this information - assert(gen->getZoneID(dst) != gen->getZoneWater().first); - lakes.back().connectedZones.insert(gen->getZoneID(dst)); - lakes.back().coast.insert(dst); - lakes.back().distance[dst] = 0; - return; - } - } - else - { - if(lakes.back().tiles.insert(dst).second) - { - tilesQueue.push_back(dst); - } - } - }; - - while(!tilesAll.empty()) - { - //add some random tile as initial - tilesQueue.push_back(*tilesAll.begin()); - setPos(tilesQueue.front()); - addFreePath(tilesQueue.front()); - lakes.emplace_back(); - lakes.back().tiles.insert(tilesQueue.front()); - - //find lake - while(!tilesQueue.empty()) - { - int3 tile = tilesQueue.front(); - tilesQueue.pop_front(); - gen->foreachDirectNeighbour(tile, lakeSearch); - } - - //fill distance map - tilesQueue.assign(lakes.back().coast.begin(), lakes.back().coast.end()); - while(!tilesQueue.empty()) - { - int3 src = tilesQueue.front(); - tilesQueue.pop_front(); - gen->foreachDirectNeighbour(src, [this, &src, &tilesChecked, &tilesQueue](const int3 & dst) - { - if(tilesChecked.find(dst) != tilesChecked.end()) - return; - - if(lakes.back().tiles.find(dst) != lakes.back().tiles.end()) - { - lakes.back().distance[dst] = lakes.back().distance[src] + 1; - tilesChecked.insert(dst); - tilesQueue.push_back(dst); - } - }); - } - - //cleanup - int lakeIdx = lakes.size(); - for(auto& t : lakes.back().tiles) - { - assert(lakeMap.find(t) == lakeMap.end()); - lakeMap[t] = lakeIdx; - tilesAll.erase(t); - } - } - -#ifdef _BETA - { - std::ofstream out1("lakes_id.txt"); - std::ofstream out2("lakes_map.txt"); - std::ofstream out3("lakes_dist.txt"); - int levels = gen->map->twoLevel ? 2 : 1; - int width = gen->map->width; - int height = gen->map->height; - for (int k = 0; k < levels; k++) - { - for(int j=0; j9) - out1 << '#'; - else - out1 << lakeMap[tile]; - - bool found = false; - for(auto& lake : lakes) - { - if(lake.coast.count(tile)) - { - out2 << '@'; - out3 << lake.distance[tile]; - found = true; - } - else if(lake.tiles.count(tile)) - { - out2 << '~'; - out3 << lake.distance[tile]; - found = true; - } - } - if(!found) - { - out2 << ' '; - out3 << ' '; - } - } - out1 << std::endl; - out2 << std::endl; - out3 << std::endl; - } - out1 << std::endl; - out2 << std::endl; - out3 << std::endl; - } - out1 << std::endl; - out2 << std::endl; - out3 << std::endl; - } -#endif -} - -bool CRmgTemplateZone::waterKeepConnection(TRmgTemplateZoneId zoneA, TRmgTemplateZoneId zoneB) -{ - for(auto & lake : lakes) - { - if(lake.connectedZones.count(zoneA) && lake.connectedZones.count(zoneB)) - { - lake.keepConnections.insert(zoneA); - lake.keepConnections.insert(zoneB); - return true; - } - } - return false; -} - -void CRmgTemplateZone::waterConnection(CRmgTemplateZone& dst) -{ - if(isUnderground() || dst.getCoastTiles().empty()) - return; - - //block zones are not connected by template - for(auto& lake : lakes) - { - if(lake.connectedZones.count(dst.getId())) - { - if(!lake.keepConnections.count(dst.getId())) - { - for(auto & ct : lake.coast) - { - if(gen->getZoneID(ct) == dst.getId() && gen->isPossible(ct)) - gen->setOccupied(ct, ETileType::BLOCKED); - } - continue; - } - - int3 coastTile(-1, -1, -1); - int zoneTowns = dst.playerTowns.getTownCount() + dst.playerTowns.getCastleCount() + - dst.neutralTowns.getTownCount() + dst.neutralTowns.getCastleCount(); - - if(dst.getType() == ETemplateZoneType::PLAYER_START || dst.getType() == ETemplateZoneType::CPU_START || zoneTowns) - { - coastTile = dst.createShipyard(lake.tiles, gen->getConfig().shipyardGuard); - if(!coastTile.valid()) - { - coastTile = makeBoat(dst.getId(), lake.tiles); - } - } - else - { - coastTile = makeBoat(dst.getId(), lake.tiles); - } - - if(coastTile.valid()) - { - if(connectPath(coastTile, true)) - { - addFreePath(coastTile); - } - else - logGlobal->error("Cannot build water route for zone %d", dst.getId()); - } - else - logGlobal->error("No entry from water to zone %d", dst.getId()); - - } - } -} - -const std::set& CRmgTemplateZone::getCoastTiles() const -{ - return coastTiles; -} - -bool CRmgTemplateZone::isWaterConnected(TRmgTemplateZoneId zone, const int3 & tile) const -{ - int lakeId = gen->getZoneWater().second->lakeMap.at(tile); - if(lakeId == 0) - return false; - - return gen->getZoneWater().second->lakes.at(lakeId - 1).connectedZones.count(zone) && - gen->getZoneWater().second->lakes.at(lakeId - 1).keepConnections.count(zone); -} - -void CRmgTemplateZone::fractalize() -{ - for (auto tile : tileinfo) - { - if (gen->isFree(tile)) - freePaths.insert(tile); - } - std::vector clearedTiles (freePaths.begin(), freePaths.end()); - std::set possibleTiles; - std::set 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 - - for(auto tile : tileinfo) - { - if(gen->isPossible(tile)) - possibleTiles.insert(tile); - } - assert (clearedTiles.size()); //this should come from zone connections - - std::vector nodes; //connect them with a grid - - 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 tilesToMakePath(possibleTiles.begin(), possibleTiles.end()); - RandomGeneratorUtil::randomShuffle(tilesToMakePath, gen->rand); - - int3 nodeFound(-1, -1, -1); - - for(auto tileToMakePath : tilesToMakePath) - { - //find closest free tile - float currentDistance = 1e10; - int3 closestTile(-1, -1, -1); - - for(auto clearTile : clearedTiles) - { - float distance = static_cast(tileToMakePath.dist2dSQ(clearTile)); - - if(distance < currentDistance) - { - currentDistance = distance; - closestTile = clearTile; - } - if(currentDistance <= minDistance) - { - //this tile is close enough. Forget about it and check next one - tilesToIgnore.insert(tileToMakePath); - break; - } - } - //if tiles is not close enough, make path to it - if (currentDistance > minDistance) - { - nodeFound = tileToMakePath; - nodes.push_back(nodeFound); - clearedTiles.push_back(nodeFound); //from now on nearby tiles will be considered handled - break; //next iteration - use already cleared tiles - } - } - - for(auto tileToClear : tilesToIgnore) - { - //these tiles are already connected, ignore them - vstd::erase_if_present(possibleTiles, tileToClear); - } - if(!nodeFound.valid()) //nothing else can be done (?) - break; - tilesToIgnore.clear(); - } - } - - //cut straight paths towards the center. A* is too slow for that. - for (auto node : nodes) - { - auto subnodes = nodes; - boost::sort(subnodes, [&node](const int3& ourNode, const int3& otherNode) -> bool - { - return node.dist2dSQ(ourNode) < node.dist2dSQ(otherNode); - }); - - std::vector nearbyNodes; - if (subnodes.size() >= 2) - { - nearbyNodes.push_back(subnodes[1]); //node[0] is our node we want to connect - } - if (subnodes.size() >= 3) - { - nearbyNodes.push_back(subnodes[2]); - } - - //connect with all the paths - crunchPath(node, findClosestTile(freePaths, node), true, &freePaths); - //connect with nearby nodes - for (auto nearbyNode : nearbyNodes) - { - crunchPath(node, nearbyNode, true, &freePaths); //do not allow to make another path network - } - } - for (auto node : nodes) - gen->setOccupied(node, ETileType::FREE); //make sure they are clear - - //now block most distant tiles away from passages - - float blockDistance = minDistance * 0.25f; - - for (auto tile : tileinfo) - { - if(!gen->isPossible(tile)) - continue; - - if(freePaths.count(tile)) - continue; - - bool closeTileFound = false; - - for(auto clearTile : freePaths) - { - float distance = static_cast(tile.dist2dSQ(clearTile)); - - if(distance < blockDistance) - { - closeTileFound = true; - break; - } - } - if (!closeTileFound) //this tile is far enough from passages - gen->setOccupied(tile, ETileType::BLOCKED); - } - - #define PRINT_FRACTALIZED_MAP false - if (PRINT_FRACTALIZED_MAP) //enable to debug - { - std::ofstream out(boost::to_string(boost::format("zone_%d.txt") % id)); - int levels = gen->map->twoLevel ? 2 : 1; - int width = gen->map->width; - int height = gen->map->height; - for (int k = 0; k < levels; k++) - { - for(int j=0; jgetTile(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; - } -} - -void CRmgTemplateZone::connectLater() -{ - for (const int3 & node : tilesToConnectLater) - { - if (!connectWithCenter(node, true)) - logGlobal->error("Failed to connect node %s with center of the zone", node.toString()); - } -} - -bool CRmgTemplateZone::crunchPath(const int3 &src, const int3 &dst, bool onlyStraight, std::set* clearedTiles) -{ -/* -make shortest path with free tiles, reachning dst or closest already free tile. Avoid blocks. -do not leave zone border -*/ - bool result = false; - bool end = false; - - int3 currentPos = src; - float distance = static_cast(currentPos.dist2dSQ (dst)); - - while (!end) - { - if (currentPos == dst) - { - result = true; - break; - } - - auto lastDistance = distance; - - auto processNeighbours = [this, ¤tPos, dst, &distance, &result, &end, clearedTiles](int3 &pos) - { - if (!result) //not sure if lambda is worth it... - { - if (pos == dst) - { - result = true; - end = true; - } - if (pos.dist2dSQ (dst) < distance) - { - if (!gen->isBlocked(pos)) - { - if (gen->getZoneID(pos) == id) - { - if (gen->isPossible(pos)) - { - gen->setOccupied (pos, ETileType::FREE); - if (clearedTiles) - clearedTiles->insert(pos); - currentPos = pos; - distance = static_cast(currentPos.dist2dSQ (dst)); - } - else if (gen->isFree(pos)) - { - end = true; - result = true; - } - } - } - } - } - }; - - if (onlyStraight) - gen->foreachDirectNeighbour (currentPos, processNeighbours); - else - gen->foreach_neighbour (currentPos,processNeighbours); - - int3 anotherPos(-1, -1, -1); - - if (!(result || distance < lastDistance)) //we do not advance, use more advanced pathfinding algorithm? - { - //try any nearby tiles, even if its not closer than current - float lastDistance = 2 * distance; //start with significantly larger value - - auto processNeighbours2 = [this, ¤tPos, dst, &lastDistance, &anotherPos, clearedTiles](int3 &pos) - { - if (currentPos.dist2dSQ(dst) < lastDistance) //try closest tiles from all surrounding unused tiles - { - if (gen->getZoneID(pos) == id) - { - if (gen->isPossible(pos)) - { - if (clearedTiles) - clearedTiles->insert(pos); - anotherPos = pos; - lastDistance = static_cast(currentPos.dist2dSQ(dst)); - } - } - } - }; - if (onlyStraight) - gen->foreachDirectNeighbour(currentPos, processNeighbours2); - else - gen->foreach_neighbour(currentPos, processNeighbours2); - - - if (anotherPos.valid()) - { - if (clearedTiles) - clearedTiles->insert(anotherPos); - gen->setOccupied(anotherPos, ETileType::FREE); - currentPos = anotherPos; - } - } - if (!(result || distance < lastDistance || anotherPos.valid())) - { - //FIXME: seemingly this condition is messed up, tells nothing - //logGlobal->warn("No tile closer than %s found on path from %s to %s", currentPos, src , dst); - break; - } - } - - return result; -} -boost::heap::priority_queue> CRmgTemplateZone::createPriorityQueue() -{ - return boost::heap::priority_queue>(); -} - -bool CRmgTemplateZone::createRoad(const int3& src, const int3& dst) -{ - //A* algorithm taken from Wiki http://en.wikipedia.org/wiki/A*_search_algorithm - - std::set closed; // The set of nodes already evaluated. - auto pq = createPriorityQueue(); // The set of tentative nodes to be evaluated, initially containing the start node - std::map cameFrom; // The map of navigated nodes. - std::map distances; - - gen->setRoad (src, ROAD_NAMES[0]); //just in case zone guard already has road under it. Road under nodes will be added at very end - - cameFrom[src] = int3(-1, -1, -1); //first node points to finish condition - pq.push(std::make_pair(src, 0.f)); - distances[src] = 0.f; - // Cost from start along best known path. - - while (!pq.empty()) - { - auto node = pq.top(); - pq.pop(); //remove top element - int3 currentNode = node.first; - closed.insert (currentNode); - auto currentTile = &gen->map->getTile(currentNode); - - if (currentNode == dst || gen->isRoad(currentNode)) - { - // The goal node was reached. Trace the path using - // the saved parent information and return path - int3 backTracking = currentNode; - while (cameFrom[backTracking].valid()) - { - // add node to path - roads.insert(backTracking); - gen->setRoad(backTracking, gen->getConfig().defaultRoadType); - //logGlobal->trace("Setting road at tile %s", backTracking); - // do the same for the predecessor - backTracking = cameFrom[backTracking]; - } - return true; - } - else - { - bool directNeighbourFound = false; - float movementCost = 1; - - auto foo = [this, &pq, &distances, &closed, &cameFrom, ¤tNode, ¤tTile, &node, &dst, &directNeighbourFound, &movementCost](int3& pos) -> void - { - if (vstd::contains(closed, pos)) //we already visited that node - return; - float distance = node.second + movementCost; - float bestDistanceSoFar = std::numeric_limits::max(); - auto it = distances.find(pos); - if (it != distances.end()) - bestDistanceSoFar = it->second; - - if (distance < bestDistanceSoFar) - { - auto tile = &gen->map->getTile(pos); - bool canMoveBetween = gen->map->canMoveBetween(currentNode, pos); - - if ((gen->isFree(pos) && gen->isFree(currentNode)) //empty path - || ((tile->visitable || currentTile->visitable) && canMoveBetween) //moving from or to visitable object - || pos == dst) //we already compledted the path - { - if (gen->getZoneID(pos) == id || pos == dst) //otherwise guard position may appear already connected to other zone. - { - cameFrom[pos] = currentNode; - distances[pos] = distance; - pq.push(std::make_pair(pos, distance)); - directNeighbourFound = true; - } - } - } - }; - - gen->foreachDirectNeighbour (currentNode, foo); // roads cannot be rendered correctly for diagonal directions - if (!directNeighbourFound) - { - movementCost = 2.1f; //moving diagonally is penalized over moving two tiles straight - gen->foreachDiagonalNeighbour(currentNode, foo); - } - } - - } - logGlobal->warn("Failed to create road from %s to %s", src.toString(), dst.toString()); - return false; - -} - -bool CRmgTemplateZone::connectPath(const int3& src, bool onlyStraight) -///connect current tile to any other free tile within zone -{ - //A* algorithm taken from Wiki http://en.wikipedia.org/wiki/A*_search_algorithm - - std::set 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 cameFrom; // The map of navigated nodes. - std::map distances; - - //int3 currentNode = src; - - cameFrom[src] = int3(-1, -1, -1); //first node points to finish condition - distances[src] = 0.f; - open.push(std::make_pair(src, 0.f)); - // Cost from start along best known path. - // Estimated total cost from start to goal through y. - - while (!open.empty()) - { - auto node = open.top(); - open.pop(); - int3 currentNode = node.first; - - closed.insert(currentNode); - - if (gen->isFree(currentNode)) //we reached free paths, stop - { - // Trace the path using the saved parent information and return path - int3 backTracking = currentNode; - while (cameFrom[backTracking].valid()) - { - gen->setOccupied(backTracking, ETileType::FREE); - backTracking = cameFrom[backTracking]; - } - return true; - } - else - { - auto foo = [this, &open, &closed, &cameFrom, ¤tNode, &distances](int3& pos) -> void - { - if (vstd::contains(closed, pos)) - return; - - //no paths through blocked or occupied tiles, stay within zone - if (gen->isBlocked(pos) || gen->getZoneID(pos) != id) - return; - - int distance = static_cast(distances[currentNode]) + 1; - int bestDistanceSoFar = std::numeric_limits::max(); - auto it = distances.find(pos); - if (it != distances.end()) - bestDistanceSoFar = static_cast(it->second); - - if (distance < bestDistanceSoFar) - { - cameFrom[pos] = currentNode; - open.push(std::make_pair(pos, (float)distance)); - distances[pos] = static_cast(distance); - } - }; - - if (onlyStraight) - gen->foreachDirectNeighbour(currentNode, foo); - else - gen->foreach_neighbour(currentNode, foo); - } - - } - for (auto tile : closed) //these tiles are sealed off and can't be connected anymore - { - if(gen->isPossible(tile)) - gen->setOccupied (tile, ETileType::BLOCKED); - vstd::erase_if_present(possibleTiles, tile); - } - return false; -} - -bool CRmgTemplateZone::connectWithCenter(const int3& src, bool onlyStraight, bool passThroughBlocked) -///connect current tile to any other free tile within zone -{ - //A* algorithm taken from Wiki http://en.wikipedia.org/wiki/A*_search_algorithm - - std::set 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 cameFrom; // The map of navigated nodes. - std::map 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 (currentNode == pos) //we reached center of the zone, stop - { - // Trace the path using the saved parent information and return path - int3 backTracking = currentNode; - while (cameFrom[backTracking].valid()) - { - gen->setOccupied(backTracking, ETileType::FREE); - backTracking = cameFrom[backTracking]; - } - return true; - } - else - { - auto foo = [this, &open, &closed, &cameFrom, ¤tNode, &distances, passThroughBlocked](int3& pos) -> void - { - if (vstd::contains(closed, pos)) - return; - - if (gen->getZoneID(pos) != id) - return; - - float movementCost = 0; - - if (gen->isFree(pos)) - movementCost = 1; - else if (gen->isPossible(pos)) - movementCost = 2; - else if(passThroughBlocked && gen->shouldBeBlocked(pos)) - movementCost = 3; - else - return; - - float distance = distances[currentNode] + movementCost; //we prefer to use already free paths - int bestDistanceSoFar = std::numeric_limits::max(); //FIXME: boost::limits - auto it = distances.find(pos); - if (it != distances.end()) - bestDistanceSoFar = static_cast(it->second); - - if (distance < bestDistanceSoFar) - { - cameFrom[pos] = currentNode; - open.push(std::make_pair(pos, distance)); - distances[pos] = distance; - } - }; - - if (onlyStraight) - gen->foreachDirectNeighbour(currentNode, foo); - else - gen->foreach_neighbour(currentNode, foo); - } - - } - return false; -} - - -void CRmgTemplateZone::addRequiredObject(CGObjectInstance * obj, si32 strength) -{ - requiredObjects.push_back(std::make_pair(obj, strength)); -} -void CRmgTemplateZone::addCloseObject(CGObjectInstance * obj, si32 strength) -{ - closeObjects.push_back(std::make_pair(obj, strength)); -} -void CRmgTemplateZone::addNearbyObject(CGObjectInstance * obj, CGObjectInstance * nearbyTarget) -{ - nearbyObjects.push_back(std::make_pair(obj, nearbyTarget)); -} -void CRmgTemplateZone::addObjectAtPosition(CGObjectInstance * obj, const int3 & position, si32 strength) -{ - //TODO: use strength - instantObjects.push_back(std::make_pair(obj, position)); -} - -void CRmgTemplateZone::addToConnectLater(const int3& src) -{ - tilesToConnectLater.insert(src); -} - -int CRmgTemplateZone::chooseRandomAppearance(si32 ObjID) const -{ - auto factories = VLC->objtypeh->knownSubObjects(ObjID); - vstd::erase_if(factories, [this, ObjID](si32 f) - { - return VLC->objtypeh->getHandlerFor(ObjID, f)->getTemplates(terrainType).empty(); - }); - - return *RandomGeneratorUtil::nextItem(factories, gen->rand); -} - -bool CRmgTemplateZone::addMonster(int3 &pos, si32 strength, bool clearSurroundingTiles, bool zoneGuard) -{ - //precalculate actual (randomized) monster strength based on this post - //http://forum.vcmi.eu/viewtopic.php?p=12426#12426 - - int mapMonsterStrength = gen->getMapGenOptions().getMonsterStrength(); - int monsterStrength = (zoneGuard ? 0 : zoneMonsterStrength) + mapMonsterStrength - 1; //array index from 0 to 4 - static const int value1[] = {2500, 1500, 1000, 500, 0}; - static const int value2[] = {7500, 7500, 7500, 5000, 5000}; - static const float multiplier1[] = {0.5, 0.75, 1.0, 1.5, 1.5}; - static const float multiplier2[] = {0.5, 0.75, 1.0, 1.0, 1.5}; - - int strength1 = static_cast(std::max(0.f, (strength - value1[monsterStrength]) * multiplier1[monsterStrength])); - int strength2 = static_cast(std::max(0.f, (strength - value2[monsterStrength]) * multiplier2[monsterStrength])); - - strength = strength1 + strength2; - if (strength < gen->getConfig().minGuardStrength) - return false; //no guard at all - - CreatureID creId = CreatureID::NONE; - int amount = 0; - std::vector possibleCreatures; - for (auto cre : VLC->creh->objects) - { - if (cre->special) - continue; - if (!cre->AIValue) //bug #2681 - continue; - if (!vstd::contains(monsterTypes, 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, gen->rand); - amount = strength / VLC->creh->objects[creId]->AIValue; - if (amount >= 4) - amount = static_cast(amount * gen->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); - - placeObject(guard, pos); - - if (clearSurroundingTiles) - { - //do not spawn anything near monster - gen->foreach_neighbour (pos, [this](int3 pos) - { - if (gen->isPossible(pos)) - gen->setOccupied(pos, ETileType::FREE); - }); - } - - return true; -} - -bool CRmgTemplateZone::createTreasurePile(int3 &pos, float minDistance, const CTreasureInfo& treasureInfo) -{ - CTreasurePileInfo info; - - std::map treasures; - std::set boundary; - int3 guardPos (-1,-1,-1); - info.nextTreasurePos = pos; - - int maxValue = treasureInfo.max; - int minValue = treasureInfo.min; - - ui32 desiredValue = (gen->rand.nextInt(minValue, maxValue)); - - int currentValue = 0; - CGObjectInstance * object = nullptr; - while (currentValue <= (int)desiredValue - 100) //no objects with value below 100 are available - { - treasures[info.nextTreasurePos] = nullptr; - - for (auto treasurePos : treasures) - { - gen->foreach_neighbour(treasurePos.first, [&boundary](int3 pos) - { - boundary.insert(pos); - }); - } - for (auto treasurePos : treasures) - { - //leaving only boundary around objects - vstd::erase_if_present(boundary, treasurePos.first); - } - - for (auto tile : boundary) - { - //we can't extend boundary anymore - if (!(gen->isBlocked(tile) || gen->isPossible(tile))) - break; - } - - ObjectInfo oi = getRandomObject(info, desiredValue, maxValue, currentValue); - if (!oi.value) //0 value indicates no object - { - vstd::erase_if_present(treasures, info.nextTreasurePos); - break; - } - else - { - object = oi.generateObject(); - object->appearance = oi.templ; - - //remove from possible objects - auto oiptr = std::find(possibleObjects.begin(), possibleObjects.end(), oi); - assert (oiptr != possibleObjects.end()); - oiptr->maxPerZone--; - if (!oiptr->maxPerZone) - possibleObjects.erase(oiptr); - - //update treasure pile area - int3 visitablePos = info.nextTreasurePos; - - if (oi.templ.isVisitableFromTop()) - info.visitableFromTopPositions.insert(visitablePos); //can be accessed from any direction - else - info.visitableFromBottomPositions.insert(visitablePos); //can be accessed only from bottom or side - - for (auto blockedOffset : oi.templ.getBlockedOffsets()) - { - int3 blockPos = info.nextTreasurePos + blockedOffset + oi.templ.getVisitableOffset(); //object will be moved to align vistable pos to treasure pos - info.occupiedPositions.insert(blockPos); - info.blockedPositions.insert(blockPos); - } - info.occupiedPositions.insert(visitablePos + oi.templ.getVisitableOffset()); - - currentValue += oi.value; - - treasures[info.nextTreasurePos] = object; - - //now find place for next object - int3 placeFound(-1,-1,-1); - - //randomize next position from among possible ones - std::vector boundaryCopy (boundary.begin(), boundary.end()); - //RandomGeneratorUtil::randomShuffle(boundaryCopy, gen->rand); - auto chooseTopTile = [](const int3 & lhs, const int3 & rhs) -> bool - { - return lhs.y < rhs.y; - }; - boost::sort(boundaryCopy, chooseTopTile); //start from top tiles to allow objects accessible from bottom - - for (auto tile : boundaryCopy) - { - if (gen->isPossible(tile) && gen->getZoneID(tile) == getId()) //we can place new treasure only on possible tile - { - bool here = true; - gen->foreach_neighbour (tile, [this, &here, minDistance](int3 pos) - { - if (!(gen->isBlocked(pos) || gen->isPossible(pos)) || gen->getZoneID(pos) != getId() || gen->getNearestObjectDistance(pos) < minDistance) - here = false; - }); - if (here) - { - placeFound = tile; - break; - } - } - } - if (placeFound.valid()) - info.nextTreasurePos = placeFound; - else - break; //no more place to add any objects - } - } - - if (treasures.size()) - { - //find object closest to free path, then connect it to the middle of the zone - - int3 closestTile = int3(-1,-1,-1); - float minTreasureDistance = 1e10; - - for (auto visitablePos : info.visitableFromBottomPositions) //objects that are not visitable from top must be accessible from bottom or side - { - int3 closestFreeTile = findClosestTile(freePaths, visitablePos); - if (closestFreeTile.dist2d(visitablePos) < minTreasureDistance) - { - closestTile = visitablePos + int3 (0, 1, 0); //start below object (y+1), possibly even outside the map, to not make path up through it - minTreasureDistance = static_cast(closestFreeTile.dist2d(visitablePos)); - } - } - for (auto visitablePos : info.visitableFromTopPositions) //all objects are accessible from any direction - { - int3 closestFreeTile = findClosestTile(freePaths, visitablePos); - if (closestFreeTile.dist2d(visitablePos) < minTreasureDistance) - { - closestTile = visitablePos; - minTreasureDistance = static_cast(closestFreeTile.dist2d(visitablePos)); - } - } - assert (closestTile.valid()); - - for (auto tile : info.occupiedPositions) - { - if (gen->map->isInTheMap(tile) && gen->isPossible(tile) && gen->getZoneID(tile)==id) //pile boundary may reach map border - gen->setOccupied(tile, ETileType::BLOCKED); //so that crunch path doesn't cut through objects - } - - if (!connectPath (closestTile, false)) //this place is sealed off, need to find new position - { - return false; - } - - //update boundary around our objects, including knowledge about objects visitable from bottom - boundary.clear(); - - for (auto tile : info.visitableFromBottomPositions) - { - gen->foreach_neighbour(tile, [tile, &boundary](int3 pos) - { - if (pos.y >= tile.y) //don't block these objects from above - boundary.insert(pos); - }); - } - for (auto tile : info.visitableFromTopPositions) - { - gen->foreach_neighbour(tile, [&boundary](int3 pos) - { - boundary.insert(pos); - }); - } - - bool isPileGuarded = isGuardNeededForTreasure(currentValue); - - for (auto tile : boundary) //guard must be standing there - { - if (gen->isFree(tile)) //this tile could be already blocked, don't place a monster here - { - guardPos = tile; - break; - } - } - - if (guardPos.valid()) - { - for (auto treasure : treasures) - { - int3 visitableOffset = treasure.second->getVisitableOffset(); - placeObject(treasure.second, treasure.first + visitableOffset); - } - if (isPileGuarded && addMonster(guardPos, currentValue, false)) - {//block only if the object is guarded - for (auto tile : boundary) - { - if (gen->isPossible(tile)) - gen->setOccupied(tile, ETileType::BLOCKED); - } - //do not spawn anything near monster - gen->foreach_neighbour(guardPos, [this](int3 pos) - { - if (gen->isPossible(pos)) - gen->setOccupied(pos, ETileType::FREE); - }); - } - } - else if (isPileGuarded)//we couldn't make a connection to this location, block it - { - for (auto treasure : treasures) - { - if (gen->isPossible(treasure.first)) - gen->setOccupied(treasure.first, ETileType::BLOCKED); - - delete treasure.second; - } - } - - return true; - } - else //we did not place eveyrthing successfully - { - if(gen->isPossible(pos)) - gen->setOccupied(pos, ETileType::BLOCKED); //TODO: refactor stop condition - vstd::erase_if_present(possibleTiles, pos); - return false; - } -} -void CRmgTemplateZone::initTownType () -{ - //FIXME: handle case that this player is not present -> towns should be set to neutral - int totalTowns = 0; - - //cut a ring around town to ensure crunchPath always hits it. - auto cutPathAroundTown = [this](const CGTownInstance * town) - { - auto clearPos = [this](const int3 & pos) - { - if (gen->isPossible(pos)) - gen->setOccupied(pos, ETileType::FREE); - }; - for (auto blockedTile : town->getBlockedPos()) - { - gen->foreach_neighbour(blockedTile, clearPos); - } - //clear town entry - gen->foreach_neighbour(town->visitablePos()+int3{0,1,0}, clearPos); - }; - - auto addNewTowns = [&totalTowns, this, &cutPathAroundTown](int count, bool hasFort, PlayerColor player) - { - for (int i = 0; i < count; i++) - { - si32 subType = townType; - - if(totalTowns>0) - { - if(!this->townsAreSameType) - { - if (townTypes.size()) - subType = *RandomGeneratorUtil::nextItem(townTypes, gen->rand); - else - subType = *RandomGeneratorUtil::nextItem(getDefaultTownTypes(), gen->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 - gen->registerZone(town->subID); - //first town in zone goes in the middle - placeObject(town, getPos() + town->getVisitableOffset(), true); - cutPathAroundTown(town); - setPos(town->visitablePos()); //roads lead to mian town - } - else - addRequiredObject (town); - totalTowns++; - } - }; - - - if ((type == ETemplateZoneType::CPU_START) || (type == ETemplateZoneType::PLAYER_START)) - { - //set zone types to player faction, generate main town - logGlobal->info("Preparing playing zone"); - int player_id = *owner - 1; - auto & playerInfo = gen->map->players[player_id]; - PlayerColor player(player_id); - if (playerInfo.canAnyonePlay()) - { - player = PlayerColor(player_id); - townType = gen->getMapGenOptions().getPlayersSettings().find(player)->second.getStartingTown(); - - if (townType == CMapGenOptions::CPlayerSettings::RANDOM_TOWN) - randomizeTownType(true); - } - else //no player - randomize town - { - player = PlayerColor::NEUTRAL; - randomizeTownType(); - } - - auto townFactory = VLC->objtypeh->getHandlerFor(Obj::TOWN, townType); - - 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); - } - //towns are big objects and should be centered around visitable position - placeObject(town, getPos() + town->getVisitableOffset(), true); - cutPathAroundTown(town); - setPos(town->visitablePos()); //roads lead to mian town - - totalTowns++; - //register MAIN town of zone only - gen->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(townType); - playerInfo.hasMainTown = true; - playerInfo.posOfMainTown = town->pos; - playerInfo.generateHeroAtMainTown = true; - - //now create actual towns - addNewTowns(playerTowns.getCastleCount() - 1, true, player); - addNewTowns(playerTowns.getTownCount(), false, player); - } - else - { - addNewTowns(playerTowns.getCastleCount() - 1, true, PlayerColor::NEUTRAL); - addNewTowns(playerTowns.getTownCount(), false, PlayerColor::NEUTRAL); - } - } - else //randomize town types for any other zones as well - { - randomizeTownType(); - } - - addNewTowns (neutralTowns.getCastleCount(), true, PlayerColor::NEUTRAL); - addNewTowns (neutralTowns.getTownCount(), false, PlayerColor::NEUTRAL); - - if (!totalTowns) //if there's no town present, get random faction for dwellings and pandoras - { - //25% chance for neutral - if (gen->rand.nextInt(1, 100) <= 25) - { - townType = ETownType::NEUTRAL; - } - else - { - if (townTypes.size()) - townType = *RandomGeneratorUtil::nextItem(townTypes, gen->rand); - else if (monsterTypes.size()) - townType = *RandomGeneratorUtil::nextItem(monsterTypes, gen->rand); //this happens in Clash of Dragons in treasure zones, where all towns are banned - else //just in any case - randomizeTownType(); - } - } -} - -void CRmgTemplateZone::randomizeTownType(bool matchUndergroundType) -{ - auto townTypesAllowed = (townTypes.size() ? townTypes : getDefaultTownTypes()); - if(matchUndergroundType && gen->getMapGenOptions().getHasTwoLevels()) - { - std::set townTypesVerify; - for(TFaction factionIdx : townTypesAllowed) - { - bool preferUnderground = (*VLC->townh)[factionIdx]->preferUndergroundPlacement; - if(isUnderground() ? preferUnderground : !preferUnderground) - { - townTypesVerify.insert(factionIdx); - } - } - if(!townTypesVerify.empty()) - townTypesAllowed = townTypesVerify; - } - - townType = *RandomGeneratorUtil::nextItem(townTypesAllowed, gen->rand); -} - -void CRmgTemplateZone::initTerrainType () -{ - if (type==ETemplateZoneType::WATER) - { - //collect all water terrain types - std::vector waterTerrains; - for(auto & terrain : Terrain::Manager::terrains()) - if(terrain.isWater()) - waterTerrains.push_back(terrain); - - terrainType = *RandomGeneratorUtil::nextItem(waterTerrains, gen->rand); - } - else - { - if (matchTerrainToTown && townType != ETownType::NEUTRAL) - { - terrainType = (*VLC->townh)[townType]->nativeTerrain; - } - else - { - terrainType = *RandomGeneratorUtil::nextItem(terrainTypes, gen->rand); - } - - //TODO: allow new types of terrain? - { - if(isUnderground()) - { - if(!vstd::contains(gen->getConfig().terrainUndergroundAllowed, terrainType)) - { - //collect all underground terrain types - std::vector undegroundTerrains; - for(auto & terrain : Terrain::Manager::terrains()) - if(terrain.isUnderground()) - undegroundTerrains.push_back(terrain); - - terrainType = *RandomGeneratorUtil::nextItem(undegroundTerrains, gen->rand); - } - } - else - { - if(vstd::contains(gen->getConfig().terrainGroundProhibit, terrainType) || terrainType.isUnderground()) - terrainType = Terrain("dirt"); - } - } - } - paintZoneTerrain (terrainType); -} - -void CRmgTemplateZone::paintZoneTerrain (Terrain terrainType) -{ - std::vector tiles(tileinfo.begin(), tileinfo.end()); - gen->getEditManager()->getTerrainSelection().setSelection(tiles); - gen->getEditManager()->drawTerrain(terrainType, &gen->rand); -} - -bool CRmgTemplateZone::placeMines () -{ - using namespace Res; - std::vector createdMines; - - for(const auto & mineInfo : mines) - { - ERes res = (ERes)mineInfo.first; - for(int i = 0; i < mineInfo.second; ++i) - { - auto mine = (CGMine*) VLC->objtypeh->getHandlerFor(Obj::MINE, res)->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)) - addCloseObject(mine, gen->getConfig().mineValues.at(res)); //only first wood&ore mines are close - else - addRequiredObject(mine, gen->getConfig().mineValues.at(res)); - } - } - - //create extra resources - if(int extraRes = gen->getConfig().mineExtraResources) - { - for(auto * mine : createdMines) - { - for(int rc = gen->rand.nextInt(1, extraRes); rc > 0; --rc) - { - auto resourse = (CGResource*) VLC->objtypeh->getHandlerFor(Obj::RESOURCE, mine->producedResource)->create(ObjectTemplate()); - resourse->amount = CGResource::RANDOM_AMOUNT; - addNearbyObject(resourse, mine); - } - } - } - - - return true; -} - -EObjectPlacingResult::EObjectPlacingResult CRmgTemplateZone::tryToPlaceObjectAndConnectToPath(CGObjectInstance * obj, const int3 & pos) -{ - //check if we can find a path around this object. Tiles will be set to "USED" after object is successfully placed. - obj->pos = pos; - gen->setOccupied(obj->visitablePos(), ETileType::BLOCKED); - for (auto tile : obj->getBlockedPos()) - { - if (gen->map->isInTheMap(tile)) - gen->setOccupied(tile, ETileType::BLOCKED); - } - int3 accessibleOffset = getAccessibleOffset(obj->appearance, pos); - if (!accessibleOffset.valid()) - { - logGlobal->warn("Cannot access required object at position %s, retrying", pos.toString()); - return EObjectPlacingResult::CANNOT_FIT; - } - if (!connectPath(accessibleOffset, true)) - { - logGlobal->trace("Failed to create path to required object at position %s, retrying", pos.toString()); - return EObjectPlacingResult::SEALED_OFF; - } - else - return EObjectPlacingResult::SUCCESS; -} - -bool CRmgTemplateZone::createRequiredObjects() -{ - logGlobal->trace("Creating required objects"); - - for(const auto &object : requiredObjects) - { - auto obj = object.first; - if (!obj->appearance.canBePlacedAt(terrainType)) - continue; - - int3 pos; - while (true) - { - if (!findPlaceForObject(obj, 3, pos)) - { - logGlobal->error("Failed to fill zone %d due to lack of space", id); - return false; - } - - if (tryToPlaceObjectAndConnectToPath(obj, pos) == EObjectPlacingResult::SUCCESS) - { - //paths to required objects constitute main paths of zone. otherwise they just may lead to middle and create dead zones - placeObject(obj, pos); - guardObject(obj, object.second, (obj->ID == Obj::MONOLITH_TWO_WAY), true); - break; - } - } - } - - for (const auto &obj : closeObjects) - { - setTemplateForObject(obj.first); - if (!obj.first->appearance.canBePlacedAt(terrainType)) - continue; - - auto tilesBlockedByObject = obj.first->getBlockedOffsets(); - - bool finished = false; - bool attempt = true; - while (!finished && attempt) - { - attempt = false; - - std::vector tiles(possibleTiles.begin(), possibleTiles.end()); - //new tiles vector after each object has been placed, OR misplaced area has been sealed off - - boost::remove_if(tiles, [obj, this](int3 &tile)-> bool - { - //object must be accessible from at least one surounding tile - return !this->isAccessibleFromSomewhere(obj.first->appearance, tile); - }); - - auto targetPosition = requestedPositions.find(obj.first)!=requestedPositions.end() ? requestedPositions[obj.first] : pos; - // smallest distance to zone center, greatest distance to nearest object - auto isCloser = [this, &targetPosition, &tilesBlockedByObject](const int3 & lhs, const int3 & rhs) -> bool - { - float lDist = std::numeric_limits::max(); - float rDist = std::numeric_limits::max(); - for(int3 t : tilesBlockedByObject) - { - t += targetPosition; - lDist = fmin(lDist, static_cast(t.dist2d(lhs))); - rDist = fmin(rDist, static_cast(t.dist2d(rhs))); - } - lDist *= (lDist > 12) ? 10 : 1; //objects within 12 tile radius are preferred (smaller distance rating) - rDist *= (rDist > 12) ? 10 : 1; - - return (lDist * 0.5f - std::sqrt(gen->getNearestObjectDistance(lhs))) < (rDist * 0.5f - std::sqrt(gen->getNearestObjectDistance(rhs))); - }; - - boost::sort(tiles, isCloser); - - if (tiles.empty()) - { - logGlobal->error("Failed to fill zone %d due to lack of space", id); - return false; - } - for (auto tile : tiles) - { - //code partially adapted from findPlaceForObject() - if(!areAllTilesAvailable(obj.first, tile, tilesBlockedByObject)) - continue; - - attempt = true; - - EObjectPlacingResult::EObjectPlacingResult result = tryToPlaceObjectAndConnectToPath(obj.first, tile); - if (result == EObjectPlacingResult::SUCCESS) - { - placeObject(obj.first, tile); - guardObject(obj.first, obj.second, (obj.first->ID == Obj::MONOLITH_TWO_WAY), true); - finished = true; - break; - } - else if (result == EObjectPlacingResult::CANNOT_FIT) - continue; // next tile - else if (result == EObjectPlacingResult::SEALED_OFF) - { - break; //tiles expired, pick new ones - } - else - throw (rmgException("Wrong result of tryToPlaceObjectAndConnectToPath()")); - } - } - } - - //create nearby objects (e.g. extra resources close to mines) - for(const auto & object : nearbyObjects) - { - auto obj = object.first; - std::set possiblePositions; - for (auto blockedTile : object.second->getBlockedPos()) - { - gen->foreachDirectNeighbour(blockedTile, [this, &possiblePositions](int3 pos) - { - if (!gen->isBlocked(pos) && tileinfo.count(pos)) - { - //some resources still could be unaccessible, at least one free cell shall be - gen->foreach_neighbour(pos, [this, &possiblePositions, &pos](int3 p) - { - if(gen->isFree(p)) - possiblePositions.insert(pos); - }); - } - }); - } - - if(possiblePositions.empty()) - { - delete obj; //is it correct way to prevent leak? - } - else - { - auto pos = *RandomGeneratorUtil::nextItem(possiblePositions, gen->rand); - placeObject(obj, pos); - } - } - - //create object on specific positions - //TODO: implement guards - for (const auto &obj : instantObjects) - { - if(tryToPlaceObjectAndConnectToPath(obj.first, obj.second)==EObjectPlacingResult::SUCCESS) - { - placeObject(obj.first, obj.second); - //TODO: guardObject(...) - } - } - - requiredObjects.clear(); - closeObjects.clear(); - nearbyObjects.clear(); - instantObjects.clear(); - - return true; -} - -int3 CRmgTemplateZone::makeBoat(TRmgTemplateZoneId land, const std::set & lake) -{ - std::set lakeCoast; - std::set_intersection(gen->getZones()[land]->getCoastTiles().begin(), gen->getZones()[land]->getCoastTiles().end(), lake.begin(), lake.end(), std::inserter(lakeCoast, lakeCoast.begin())); - - if(lakeCoast.empty()) - return int3(-1, -1, -1); - - for(int randomAttempts = 0; randomAttempts<5; ++randomAttempts) - { - auto coastTile = *RandomGeneratorUtil::nextItem(lakeCoast, gen->rand); - if(gen->getZoneID(coastTile) == gen->getZoneWater().first && isWaterConnected(land, coastTile) && makeBoat(land, coastTile)) - return coastTile; - } - //if no success on random selection, use brute force - for(const auto& coastTile : lakeCoast) - { - if(gen->getZoneID(coastTile) == gen->getZoneWater().first && isWaterConnected(land, coastTile) && makeBoat(land, coastTile)) - return coastTile; - } - return int3(-1,-1,-1); -} - -bool CRmgTemplateZone::makeBoat(TRmgTemplateZoneId land, const int3 & coast) -{ - //verify coast - if(gen->getZoneWater().first != id) - throw rmgException("Cannot make a ship: not a water zone"); - if(gen->getZoneID(coast) != id) - throw rmgException("Cannot make a ship: coast tile doesn't belong to water"); - - //find zone for ship boarding - std::vector landTiles; - gen->foreach_neighbour(coast, [this, &landTiles, land](const int3 & t) - { - if(land == gen->getZoneID(t) && gen->isPossible(t)) - { - landTiles.push_back(t); - } - }); - - if(landTiles.empty()) - return false; - - int3 landTile = {-1, -1, -1}; - for(auto& lt : landTiles) - { - if(gen->getZones()[land]->connectPath(lt, false)) - { - landTile = lt; - gen->setOccupied(landTile, ETileType::FREE); - break; - } - } - - if(!landTile.valid()) - return false; - - auto subObjects = VLC->objtypeh->knownSubObjects(Obj::BOAT); - auto* boat = (CGBoat*)VLC->objtypeh->getHandlerFor(Obj::BOAT, *RandomGeneratorUtil::nextItem(subObjects, gen->rand))->create(ObjectTemplate()); - - auto targetPos = boat->getVisitableOffset() + coast + int3{1, 0, 0}; //+1 offset for boat - bug? - if (gen->map->isInTheMap(targetPos) && gen->isPossible(targetPos) && gen->getZoneID(targetPos) == getId()) - { - //don't connect to path because it's not initialized - addObjectAtPosition(boat, targetPos); - gen->setOccupied(targetPos, ETileType::USED); - return true; - } - - return false; -} - -int3 CRmgTemplateZone::createShipyard(const std::set & lake, si32 guardStrength) -{ - std::set lakeCoast; - std::set_intersection(getCoastTiles().begin(), getCoastTiles().end(), lake.begin(), lake.end(), std::inserter(lakeCoast, lakeCoast.begin())); - for(int randomAttempts = 0; randomAttempts < 5; ++randomAttempts) - { - auto coastTile = *RandomGeneratorUtil::nextItem(lakeCoast, gen->rand); - if(gen->getZoneID(coastTile) == gen->getZoneWater().first && isWaterConnected(id, coastTile) && createShipyard(coastTile, guardStrength)) - return coastTile; - } - //if no success on random selection, use brute force - for(const auto& coastTile : lakeCoast) - { - if(gen->getZoneID(coastTile) == gen->getZoneWater().first && isWaterConnected(id, coastTile) && createShipyard(coastTile, guardStrength)) - return coastTile; - } - return int3(-1,-1,-1); -} - -bool CRmgTemplateZone::createShipyard(const int3 & position, si32 guardStrength) -{ - int subtype = chooseRandomAppearance(Obj::SHIPYARD); - auto shipyard = (CGShipyard*) VLC->objtypeh->getHandlerFor(Obj::SHIPYARD, subtype)->create(ObjectTemplate()); - shipyard->tempOwner = PlayerColor::NEUTRAL; - - setTemplateForObject(shipyard); - std::vector outOffsets; - auto tilesBlockedByObject = shipyard->getBlockedOffsets(); - tilesBlockedByObject.insert(shipyard->getVisitableOffset()); - shipyard->getOutOffsets(outOffsets); - - int3 targetTile(-1, -1, -1); - std::set shipAccessCandidates; - - for(const auto & outOffset : outOffsets) - { - auto candidateTile = position - outOffset; - std::set tilesBlockedAbsolute; - - //check space under object - bool allClear = true; - for(const auto & objectTileOffset : tilesBlockedByObject) - { - auto objectTile = candidateTile + objectTileOffset; - tilesBlockedAbsolute.insert(objectTile); - if(!gen->map->isInTheMap(objectTile) || !gen->isPossible(objectTile) || gen->getZoneID(objectTile)!=id) - { - allClear = false; - break; - } - } - if(!allClear) //cannot place shipyard anyway - continue; - - //prepare temporary map - for(auto& blockedPos : tilesBlockedAbsolute) - gen->setOccupied(blockedPos, ETileType::USED); - - - //check if boarding position is accessible - gen->foreach_neighbour(position, [this, &shipAccessCandidates](const int3 & v) - { - if(!gen->isBlocked(v) && gen->getZoneID(v)==id) - { - //make sure that it's possible to create path to boarding position - if(connectWithCenter(v, false, false)) - shipAccessCandidates.insert(v); - } - }); - - //check if we can connect shipyard entrance with path - if(!connectWithCenter(candidateTile + shipyard->getVisitableOffset(), false)) - shipAccessCandidates.clear(); - - //rollback temporary map - for(auto& blockedPos : tilesBlockedAbsolute) - gen->setOccupied(blockedPos, ETileType::POSSIBLE); - - if(!shipAccessCandidates.empty() && isAccessibleFromSomewhere(shipyard->appearance, candidateTile)) - { - targetTile = candidateTile; - break; //no need to check other offsets as we already found position - } - - shipAccessCandidates.clear(); //invalidate positions - } - - if(!targetTile.valid()) - { - delete shipyard; - return false; - } - - if(tryToPlaceObjectAndConnectToPath(shipyard, targetTile)==EObjectPlacingResult::SUCCESS) - { - placeObject(shipyard, targetTile); - guardObject(shipyard, guardStrength, false, true); - - for(auto& accessPosition : shipAccessCandidates) - { - if(connectPath(accessPosition, false)) - { - gen->setOccupied(accessPosition, ETileType::FREE); - return true; - } - } - } - - logGlobal->warn("Cannot find path to shipyard boarding position"); - delete shipyard; - return false; -} - -void CRmgTemplateZone::createTreasures() -{ - int mapMonsterStrength = gen->getMapGenOptions().getMonsterStrength(); - int monsterStrength = 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; - }; - - //place biggest treasures first at large distance, place smaller ones inbetween - 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((125.f / totalDensity), 2.0f); - //distance lower than 2 causes objects to overlap and crash - - bool stop = false; - do { - //optimization - don't check tiles which are not allowed - vstd::erase_if(possibleTiles, [this](const int3 &tile) -> bool - { - //for water area we sholdn't place treasures close to coast - for(auto & lake : lakes) - if(vstd::contains(lake.distance, tile) && lake.distance[tile] < 2) - return true; - - return !gen->isPossible(tile) || gen->getZoneID(tile)!=getId(); - }); - - - int3 treasureTilePos; - //If we are able to place at least one object with value lower than minGuardedValue, it's ok - do - { - if (!findPlaceForTreasurePile(minDistance, treasureTilePos, t.min)) - { - stop = true; - break; - } - } - while (!createTreasurePile(treasureTilePos, minDistance, t)); //failed creation - position was wrong, cannot connect it - - } while (!stop); - } -} - -void CRmgTemplateZone::createObstacles1() -{ - if (pos.z) //underground - { - //now make sure all accessible tiles have no additional rock on them - - std::vector accessibleTiles; - for (auto tile : tileinfo) - { - if (gen->isFree(tile) || gen->isUsed(tile)) - { - accessibleTiles.push_back(tile); - } - } - gen->getEditManager()->getTerrainSelection().setSelection(accessibleTiles); - gen->getEditManager()->drawTerrain(terrainType, &gen->rand); - } -} - -void CRmgTemplateZone::createObstacles2() -{ - - typedef std::vector obstacleVector; - //obstacleVector possibleObstacles; - - std::map obstaclesBySize; - typedef std::pair obstaclePair; - std::vector 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(terrainType) && temp.getBlockMapOffset().valid()) - obstaclesBySize[(ui8)temp.getBlockedOffsets().size()].push_back(temp); - } - } - } - } - for (auto o : obstaclesBySize) - { - possibleObstacles.push_back (std::make_pair(o.first, o.second)); - } - boost::sort (possibleObstacles, [](const obstaclePair &p1, const obstaclePair &p2) -> bool - { - return p1.first > p2.first; //bigger obstacles first - }); - - auto sel = gen->getEditManager()->getTerrainSelection(); - sel.clearSelection(); - - auto tryToPlaceObstacleHere = [this, &possibleObstacles](int3& tile, int index)-> bool - { - auto temp = *RandomGeneratorUtil::nextItem(possibleObstacles[index].second, gen->rand); - int3 obstaclePos = tile + temp.getBlockMapOffset(); - if (canObstacleBePlacedHere(temp, obstaclePos)) //can be placed here - { - auto obj = VLC->objtypeh->getHandlerFor(temp.id, temp.subid)->create(temp); - placeObject(obj, obstaclePos, false); - return true; - } - return false; - }; - - //reverse order, since obstacles begin in bottom-right corner, while the map coordinates begin in top-left - for (auto tile : boost::adaptors::reverse(tileinfo)) - { - //fill tiles that should be blocked with obstacles - if (gen->shouldBeBlocked(tile)) - { - //start from biggets obstacles - for (int i = 0; i < possibleObstacles.size(); i++) - { - if (tryToPlaceObstacleHere(tile, i)) - break; - } - } - } - //cleanup - remove unused possible tiles to make space for roads - for (auto tile : tileinfo) - { - if (gen->isPossible(tile)) - { - gen->setOccupied (tile, ETileType::FREE); - } - } -} - -void CRmgTemplateZone::connectRoads() -{ - logGlobal->debug("Started building roads"); - - std::set roadNodesCopy(roadNodes); - std::set processed; - - while(!roadNodesCopy.empty()) - { - int3 node = *roadNodesCopy.begin(); - roadNodesCopy.erase(node); - int3 cross(-1, -1, -1); - - auto comparator = [=](int3 lhs, int3 rhs) { return node.dist2dSQ(lhs) < node.dist2dSQ(rhs); }; - - if (processed.size()) //connect with already existing network - { - cross = *boost::range::min_element(processed, comparator); //find another remaining node - } - else if (roadNodesCopy.size()) //connect with any other unconnected node - { - cross = *boost::range::min_element(roadNodesCopy, comparator); //find another remaining node - } - else //no other nodes left, for example single road node in this zone - break; - - logGlobal->debug("Building road from %s to %s", node.toString(), cross.toString()); - if (createRoad(node, cross)) - { - processed.insert(cross); //don't draw road starting at end point which is already connected - vstd::erase_if_present(roadNodesCopy, cross); - } - - processed.insert(node); - } - - drawRoads(); - - logGlobal->debug("Finished building roads"); -} - -void CRmgTemplateZone::drawRoads() -{ - std::vector tiles; - for (auto tile : roads) - { - if(gen->map->isInTheMap(tile)) - tiles.push_back (tile); - } - for (auto tile : roadNodes) - { - if (gen->getZoneID(tile) == id) //mark roads for our nodes, but not for zone guards in other zones - tiles.push_back(tile); - } - - gen->getEditManager()->getTerrainSelection().setSelection(tiles); - gen->getEditManager()->drawRoad(gen->getConfig().defaultRoadType, &gen->rand); -} - - -bool CRmgTemplateZone::fill() -{ - initTerrainType(); - - addAllPossibleObjects(); - - //zone center should be always clear to allow other tiles to connect - initFreeTiles(); - connectLater(); //ideally this should work after fractalize, but fails - fractalize(); - placeMines(); - createRequiredObjects(); - createTreasures(); - - logGlobal->info("Zone %d filled successfully", id); - return true; -} - -bool CRmgTemplateZone::findPlaceForTreasurePile(float min_dist, int3 &pos, int value) -{ - float best_distance = 0; - bool result = false; - - bool needsGuard = isGuardNeededForTreasure(value); - - //logGlobal->info("Min dist for density %f is %d", density, min_dist); - for(auto tile : possibleTiles) - { - auto dist = gen->getNearestObjectDistance(tile); - - if ((dist >= min_dist) && (dist > best_distance)) - { - bool allTilesAvailable = true; - gen->foreach_neighbour (tile, [this, &allTilesAvailable, needsGuard](int3 neighbour) - { - if (!(gen->isPossible(neighbour) || gen->shouldBeBlocked(neighbour) || gen->getZoneID(neighbour)==getId() || (!needsGuard && gen->isFree(neighbour)))) - { - allTilesAvailable = false; //all present tiles must be already blocked or ready for new objects - } - }); - if (allTilesAvailable) - { - best_distance = dist; - pos = tile; - result = true; - } - } - } - if (result) - { - gen->setOccupied(pos, ETileType::BLOCKED); //block that tile //FIXME: why? - } - return result; -} - -bool CRmgTemplateZone::canObstacleBePlacedHere(ObjectTemplate &temp, int3 &pos) -{ - if (!gen->map->isInTheMap(pos)) //blockmap may fit in the map, but botom-right corner does not - return false; - - auto tilesBlockedByObject = temp.getBlockedOffsets(); - - for (auto blockingTile : tilesBlockedByObject) - { - int3 t = pos + blockingTile; - if (!gen->map->isInTheMap(t) || !(gen->isPossible(t) || gen->shouldBeBlocked(t)) || !temp.canBePlacedAt(gen->map->getTile(t).terType)) - { - return false; //if at least one tile is not possible, object can't be placed here - } - } - return true; -} - -bool CRmgTemplateZone::isAccessibleFromSomewhere(ObjectTemplate & appearance, const int3 & tile) const -{ - return getAccessibleOffset(appearance, tile).valid(); -} - -int3 CRmgTemplateZone::getAccessibleOffset(ObjectTemplate & appearance, const int3 & tile) const -{ - auto tilesBlockedByObject = appearance.getBlockedOffsets(); - - int3 ret(-1, -1, -1); - for (int x = -1; x < 2; x++) - { - for (int y = -1; y <2; y++) - { - if (x && y) //check only if object is visitable from another tile - { - int3 offset = int3(x, y, 0) - appearance.getVisitableOffset(); - if (!vstd::contains(tilesBlockedByObject, offset)) - { - int3 nearbyPos = tile + offset; - if (gen->map->isInTheMap(nearbyPos)) - { - if (appearance.isVisitableFrom(x, y) && !gen->isBlocked(nearbyPos) && tileinfo.find(nearbyPos) != tileinfo.end()) - ret = nearbyPos; - } - } - } - } - } - return ret; -} - -void CRmgTemplateZone::setTemplateForObject(CGObjectInstance* obj) -{ - if (obj->appearance.id == Obj::NO_OBJ) - { - auto templates = VLC->objtypeh->getHandlerFor(obj->ID, obj->subID)->getTemplates(gen->map->getTile(getPos()).terType); - if (templates.empty()) - throw rmgException(boost::to_string(boost::format("Did not find graphics for object (%d,%d) at %s") % obj->ID % obj->subID % pos.toString())); - - obj->appearance = templates.front(); - } -} - -bool CRmgTemplateZone::areAllTilesAvailable(CGObjectInstance* obj, int3& tile, const std::set& tilesBlockedByObject) const -{ - for (auto blockingTile : tilesBlockedByObject) - { - int3 t = tile + blockingTile; - if (!gen->map->isInTheMap(t) || !gen->isPossible(t) || gen->getZoneID(t)!=getId()) - { - //if at least one tile is not possible, object can't be placed here - return false; - } - } - return true; -} - -bool CRmgTemplateZone::findPlaceForObject(CGObjectInstance* obj, si32 min_dist, int3 &pos) -{ - //we need object apperance to deduce free tile - setTemplateForObject(obj); - - int best_distance = 0; - bool result = false; - - auto tilesBlockedByObject = obj->getBlockedOffsets(); - - for (auto tile : tileinfo) - { - //object must be accessible from at least one surounding tile - if (!isAccessibleFromSomewhere(obj->appearance, tile)) - continue; - - auto ti = gen->getTile(tile); - auto dist = ti.getNearestObjectDistance(); - //avoid borders - if (gen->isPossible(tile) && (dist >= min_dist) && (dist > best_distance)) - { - if (areAllTilesAvailable(obj, tile, tilesBlockedByObject)) - { - best_distance = static_cast(dist); - pos = tile; - result = true; - } - } - } - if (result) - { - gen->setOccupied(pos, ETileType::BLOCKED); //block that tile - } - return result; -} - -void CRmgTemplateZone::checkAndPlaceObject(CGObjectInstance* object, const int3 &pos) -{ - if (!gen->map->isInTheMap(pos)) - throw rmgException(boost::to_string(boost::format("Position of object %d at %s is outside the map") % object->id % pos.toString())); - object->pos = pos; - - if (object->isVisitable() && !gen->map->isInTheMap(object->visitablePos())) - throw rmgException(boost::to_string(boost::format("Visitable tile %s of object %d at %s is outside the map") % object->visitablePos().toString() % object->id % object->pos.toString())); - for (auto tile : object->getBlockedPos()) - { - if (!gen->map->isInTheMap(tile)) - throw rmgException(boost::to_string(boost::format("Tile %s of object %d at %s is outside the map") % tile.toString() % object->id % object->pos.toString())); - } - - if (object->appearance.id == Obj::NO_OBJ) - { - auto terrainType = gen->map->getTile(pos).terType; - auto h = VLC->objtypeh->getHandlerFor(object->ID, object->subID); - auto templates = h->getTemplates(terrainType); - if (templates.empty()) - throw rmgException(boost::to_string(boost::format("Did not find graphics for object (%d,%d) at %s (terrain %d)") % object->ID % object->subID % pos.toString() % terrainType)); - - object->appearance = templates.front(); - } - - gen->getEditManager()->insertObject(object); -} - -void CRmgTemplateZone::placeObject(CGObjectInstance* object, const int3 &pos, bool updateDistance) -{ - checkAndPlaceObject (object, pos); - - auto points = object->getBlockedPos(); - if (object->isVisitable()) - points.insert(pos + object->getVisitableOffset()); - points.insert(pos); - for(auto p : points) - { - if (gen->map->isInTheMap(p)) - { - gen->setOccupied(p, ETileType::USED); - } - } - if (updateDistance) - updateDistances(pos); - - switch (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: - { - addRoadNode(object->visitablePos()); - } - break; - - default: - break; - } -} - -void CRmgTemplateZone::updateDistances(const int3 & pos) -{ - for (auto tile : possibleTiles) //don't need to mark distance for not possible tiles - { - ui32 d = pos.dist2dSQ(tile); //optimization, only relative distance is interesting - gen->setNearestObjectDistance(tile, std::min((float)d, gen->getNearestObjectDistance(tile))); - } -} - -void CRmgTemplateZone::placeAndGuardObject(CGObjectInstance* object, const int3 &pos, si32 str, bool zoneGuard) -{ - placeObject(object, pos); - guardObject(object, str, zoneGuard); -} - -void CRmgTemplateZone::placeSubterraneanGate(int3 pos, si32 guardStrength) -{ - auto factory = VLC->objtypeh->getHandlerFor(Obj::SUBTERRANEAN_GATE, 0); - auto gate = factory->create(ObjectTemplate()); - placeObject (gate, pos, true); - addToConnectLater (getAccessibleOffset (gate->appearance, pos)); //guard will be placed on accessibleOffset - guardObject (gate, guardStrength, true); -} - -std::vector CRmgTemplateZone::getAccessibleOffsets (const CGObjectInstance* object) -{ - //get all tiles from which this object can be accessed - int3 visitable = object->visitablePos(); - std::vector tiles; - - auto tilesBlockedByObject = object->getBlockedPos(); //absolue value, as object is already placed - - gen->foreach_neighbour(visitable, [&](int3& pos) - { - if (gen->isPossible(pos) || gen->isFree(pos)) - { - if (!vstd::contains(tilesBlockedByObject, pos)) - { - if (object->appearance.isVisitableFrom(pos.x - visitable.x, pos.y - visitable.y) && !gen->isBlocked(pos)) //TODO: refactor - info about visitability from absolute coordinates - { - tiles.push_back(pos); - } - } - - }; - }); - - return tiles; -} - -bool CRmgTemplateZone::isGuardNeededForTreasure(int value) -{ - return getType() != ETemplateZoneType::WATER && value > minGuardedValue; -} - -bool CRmgTemplateZone::guardObject(CGObjectInstance* object, si32 str, bool zoneGuard, bool addToFreePaths) -{ - std::vector tiles = getAccessibleOffsets(object); - - int3 guardTile(-1, -1, -1); - - if (tiles.size()) - { - //guardTile = tiles.front(); - guardTile = getAccessibleOffset(object->appearance, object->pos); - logGlobal->trace("Guard object at %s", object->pos.toString()); - } - else - { - logGlobal->error("Failed to guard object at %s", object->pos.toString()); - return false; - } - - if (addMonster (guardTile, str, false, zoneGuard)) //do not place obstacles around unguarded object - { - for (auto pos : tiles) - { - if (gen->isPossible(pos) && gen->getZoneID(pos) == id) - gen->setOccupied(pos, ETileType::BLOCKED); - } - gen->foreach_neighbour (guardTile, [&](int3& pos) - { - if (gen->isPossible(pos) && gen->getZoneID(pos) == id) - gen->setOccupied(pos, ETileType::FREE); - }); - - gen->setOccupied (guardTile, ETileType::USED); - } - else //allow no guard or other object in front of this object - { - for (auto tile : tiles) - if (gen->isPossible(tile)) - gen->setOccupied(tile, ETileType::FREE); - } - - return true; -} - -ObjectInfo CRmgTemplateZone::getRandomObject(CTreasurePileInfo &info, ui32 desiredValue, ui32 maxValue, ui32 currentValue) -{ - //int objectsVisitableFromBottom = 0; //for debug - - std::vector> thresholds; //handle complex object via pointer - ui32 total = 0; - - //calculate actual treasure value range based on remaining value - ui32 maxVal = desiredValue - currentValue; - ui32 minValue = static_cast(0.25f * (desiredValue - currentValue)); - - //roulette wheel - 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.value >= minValue && oi.maxPerZone > 0) - { - int3 newVisitableOffset = oi.templ.getVisitableOffset(); //visitablePos assumes object will be shifter by visitableOffset - int3 newVisitablePos = info.nextTreasurePos; - - if (!oi.templ.isVisitableFromTop()) - { - //objectsVisitableFromBottom++; - //there must be free tiles under object - auto blockedOffsets = oi.templ.getBlockedOffsets(); - if (!isAccessibleFromSomewhere(oi.templ, newVisitablePos)) - continue; - } - - //NOTE: y coordinate grows downwards - if (info.visitableFromBottomPositions.size() + info.visitableFromTopPositions.size()) //do not try to match first object in zone - { - bool fitsHere = false; - - if (oi.templ.isVisitableFromTop()) //new can be accessed from any direction - { - for (auto tile : info.visitableFromTopPositions) - { - int3 actualTile = tile + newVisitableOffset; - if (newVisitablePos.areNeighbours(actualTile)) //we access other removable object from any position - { - fitsHere = true; - break; - } - } - for (auto tile : info.visitableFromBottomPositions) - { - int3 actualTile = tile + newVisitableOffset; - if (newVisitablePos.areNeighbours(actualTile) && newVisitablePos.y >= actualTile.y) //we access existing static object from side or bottom only - { - fitsHere = true; - break; - } - } - } - else //if new object is not visitable from top, it must be accessible from below or side - { - for (auto tile : info.visitableFromTopPositions) - { - int3 actualTile = tile + newVisitableOffset; - if (newVisitablePos.areNeighbours(actualTile) && newVisitablePos.y <= actualTile.y) //we access existing removable object from top or side only - { - fitsHere = true; - break; - } - } - for (auto tile : info.visitableFromBottomPositions) - { - int3 actualTile = tile + newVisitableOffset; - if (newVisitablePos.areNeighbours(actualTile) && newVisitablePos.y == actualTile.y) //we access other static object from side only - { - fitsHere = true; - break; - } - } - } - if (!fitsHere) - continue; - } - - //now check blockmap, including our already reserved pile area - - bool fitsBlockmap = true; - - std::set blockedOffsets = oi.templ.getBlockedOffsets(); - blockedOffsets.insert (newVisitableOffset); - for (auto blockingTile : blockedOffsets) - { - int3 t = info.nextTreasurePos + newVisitableOffset + blockingTile; - if (!gen->map->isInTheMap(t) || vstd::contains(info.occupiedPositions, t)) - { - fitsBlockmap = false; //if at least one tile is not possible, object can't be placed here - break; - } - if (!(gen->isPossible(t) || gen->isBlocked(t))) //blocked tiles of object may cover blocked tiles, but not used or free tiles - { - fitsBlockmap = false; - break; - } - } - if (!fitsBlockmap) - continue; - - total += oi.probability; - - thresholds.push_back (std::make_pair (total, &oi)); - } - } - - if(thresholds.empty()) - { - ObjectInfo oi; - //Generate pandora Box with gold if the value is extremely high - if(minValue > gen->getConfig().treasureValueLimit) //we don't have object valuable enough - { - oi.generateObject = [minValue]() -> CGObjectInstance * - { - auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0); - auto obj = (CGPandoraBox *) factory->create(ObjectTemplate()); - obj->resources[Res::GOLD] = minValue; - return obj; - }; - oi.setTemplate(Obj::PANDORAS_BOX, 0, terrainType); - oi.value = minValue; - oi.probability = 0; - } - else //generate empty object with 0 value if the value if we can't spawn anything - { - oi.generateObject = []() -> CGObjectInstance * - { - return nullptr; - }; - oi.setTemplate(Obj::PANDORAS_BOX, 0, terrainType); //TODO: null template or something? should be never used, but hell knows - oi.value = 0; // this field is checked to determine no object - oi.probability = 0; - } - return oi; - } - else - { - int r = gen->rand.nextInt (1, total); - - //binary search = fastest - auto it = std::lower_bound(thresholds.begin(), thresholds.end(), r, - [](const std::pair &rhs, const int lhs)->bool - { - return (int)rhs.first < lhs; - }); - return *(it->second); - } -} - -void CRmgTemplateZone::addAllPossibleObjects() -{ - ObjectInfo oi; - - int numZones = static_cast(gen->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(terrainType)) - { - 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(type == ETemplateZoneType::WATER) - return; - - //prisons - //levels 1, 5, 10, 20, 30 - static int prisonsLevels = std::min(gen->getConfig().prisonExperience.size(), gen->getConfig().prisonValues.size()); - for(int i = 0; i < prisonsLevels; i++) - { - oi.generateObject = [i, this]() -> CGObjectInstance * - { - std::vector possibleHeroes; - for(int j = 0; j < gen->map->allowedHeroes.size(); j++) - { - if(gen->map->allowedHeroes[j]) - possibleHeroes.push_back(j); - } - - auto hid = *RandomGeneratorUtil::nextItem(possibleHeroes, gen->rand); - auto factory = VLC->objtypeh->getHandlerFor(Obj::PRISON, 0); - auto obj = (CGHeroInstance *) factory->create(ObjectTemplate()); - - - obj->subID = hid; //will be initialized later - obj->exp = gen->getConfig().prisonExperience[i]; - obj->setOwner(PlayerColor::NEUTRAL); - gen->map->allowedHeroes[hid] = false; //ban this hero - gen->decreasePrisonsRemaining(); - obj->appearance = VLC->objtypeh->getHandlerFor(Obj::PRISON, 0)->getTemplates(terrainType).front(); //can't init template with hero subID - - return obj; - }; - oi.setTemplate(Obj::PRISON, 0, terrainType); - oi.value = gen->getConfig().prisonValues[i]; - oi.probability = 30; - oi.maxPerZone = gen->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().max(); - - std::vector creatures; //native creatures for this zone - for (auto cre : VLC->creh->objects) - { - if (!cre->special && cre->faction == townType) - { - 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(VLC->objtypeh->getHandlerFor(dwellingType, secondaryID).get()); - auto creatures = dwellingHandler->getProducedCreatures(); - if(creatures.empty()) - continue; - - auto cre = creatures.front(); - if(cre->faction == townType) - { - float nativeZonesCount = static_cast(gen->getZoneCount(cre->faction)); - oi.value = static_cast(cre->AIValue * cre->growth * (1 + (nativeZonesCount / gen->getTotalZoneCount()) + (nativeZonesCount / 2))); - oi.probability = 40; - - for(auto tmplate : dwellingHandler->getTemplates()) - { - if(tmplate.canBePlacedAt(terrainType)) - { - 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 < gen->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 out; - - for (auto spell : VLC->spellh->objects) //spellh size appears to be greater (?) - { - if (gen->isAllowedSpell(spell->id) && spell->level == i + 1) - { - out.push_back(spell->id); - } - } - auto a = CArtifactInstance::createScroll(*RandomGeneratorUtil::nextItem(out, gen->rand)); - obj->storedArtifact = a; - return obj; - }; - oi.setTemplate(Obj::SPELL_SCROLL, 0, terrainType); - oi.value = gen->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, terrainType); - oi.value = i * gen->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, terrainType); - oi.value = i * gen->getConfig().pandoraMultiplierExperience; - oi.probability = 20; - possibleObjects.push_back(oi); - } - - //pandora box with creatures - const std::vector & tierValues = gen->getConfig().pandoraCreatureValues; - - auto creatureToCount = [&tierValues](CCreature * creature) -> int - { - if (!creature->AIValue) //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(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, terrainType); - oi.value = static_cast((2 * (creature->AIValue) * creaturesAmount * (1 + (float)(gen->getZoneCount(creature->faction)) / gen->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 spells; - for (auto spell : VLC->spellh->objects) - { - if (gen->isAllowedSpell(spell->id) && spell->level == i) - spells.push_back(spell); - } - - RandomGeneratorUtil::randomShuffle(spells, gen->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, terrainType); - oi.value = (i + 1) * gen->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 spells; - for (auto spell : VLC->spellh->objects) - { - if (gen->isAllowedSpell(spell->id) && spell->school[(ESpellSchool)i]) - spells.push_back(spell); - } - - RandomGeneratorUtil::randomShuffle(spells, gen->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, terrainType); - oi.value = gen->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 spells; - for (auto spell : VLC->spellh->objects) - { - if (gen->isAllowedSpell(spell->id)) - spells.push_back(spell); - } - - RandomGeneratorUtil::randomShuffle(spells, gen->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, terrainType); - oi.value = gen->getConfig().pandoraSpell60; - oi.probability = 2; - possibleObjects.push_back(oi); - - //seer huts with creatures or generic rewards - - if(questArtZone.lock()) //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(gen->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, gen->rand); - - auto generateArtInfo = [this](ArtifactID id) -> ObjectInfo - { - ObjectInfo artInfo; - artInfo.probability = std::numeric_limits::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->terrainType); - 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(Obj::SEER_HUT); - - 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(gen->getQuestArtsRemaning(), gen->rand); - obj->quest->m5arts.push_back(artid); - obj->quest->lastDay = -1; - obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false; - - gen->banQuestArt(artid); - - this->questArtZone.lock()->possibleObjects.push_back (generateArtInfo(artid)); - - return obj; - }; - oi.setTemplate(Obj::SEER_HUT, randomAppearance, terrainType); - oi.value = static_cast(((2 * (creature->AIValue) * creaturesAmount * (1 + (float)(gen->getZoneCount(creature->faction)) / gen->getTotalZoneCount())) - 4000) / 3); - oi.probability = 3; - possibleObjects.push_back(oi); - } - - static int seerLevels = std::min(gen->getConfig().questValues.size(), gen->getConfig().questRewardValues.size()); - for(int i = 0; i < seerLevels; i++) //seems that code for exp and gold reward is similiar - { - int randomAppearance = chooseRandomAppearance(Obj::SEER_HUT); - - oi.setTemplate(Obj::SEER_HUT, randomAppearance, terrainType); - oi.value = gen->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 = gen->getConfig().questRewardValues[i]; - - obj->quest->missionType = CQuest::MISSION_ART; - ArtifactID artid = *RandomGeneratorUtil::nextItem(gen->getQuestArtsRemaning(), gen->rand); - obj->quest->m5arts.push_back(artid); - obj->quest->lastDay = -1; - obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false; - - gen->banQuestArt(artid); - - this->questArtZone.lock()->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 = gen->getConfig().questRewardValues[i]; - - obj->quest->missionType = CQuest::MISSION_ART; - ArtifactID artid = *RandomGeneratorUtil::nextItem(gen->getQuestArtsRemaning(), gen->rand); - obj->quest->m5arts.push_back(artid); - obj->quest->lastDay = -1; - obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false; - - gen->banQuestArt(artid); - - this->questArtZone.lock()->possibleObjects.push_back(generateArtInfo(artid)); - - return obj; - }; - - possibleObjects.push_back(oi); - } - } -} - -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(); -} diff --git a/lib/rmg/CRmgTemplateZone.h b/lib/rmg/CRmgTemplateZone.h deleted file mode 100644 index 24f006c9a..000000000 --- a/lib/rmg/CRmgTemplateZone.h +++ /dev/null @@ -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 //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 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 visitableFromBottomPositions; //can be visited only from bottom or side - std::set visitableFromTopPositions; //they can be visited from any direction - std::set blockedPositions; - std::set 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 getTileInfo() const; - std::set getPossibleTiles() const; - std::set 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 & lake, si32 guardStrength=0); - bool makeBoat(TRmgTemplateZoneId land, const int3 & coast); - int3 makeBoat(TRmgTemplateZoneId land, const std::set & 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& 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* 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 getAccessibleOffsets (const CGObjectInstance* object); - bool areAllTilesAvailable(CGObjectInstance* obj, int3& tile, const std::set& tilesBlockedByObject) const; - - void setQuestArtZone(std::shared_ptr otherZone); - std::set* 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 TDistance; - struct NodeComparer - { - bool operator()(const TDistance & lhs, const TDistance & rhs) const - { - return (rhs.second < lhs.second); - } - }; - boost::heap::priority_queue> createPriorityQueue(); - -private: - - //subclass to store disconnected parts of water zone - struct Lake - { - std::set tiles; - std::set coast; - std::map distance; - std::set connectedZones; - std::set keepConnections; - }; - - CMapGenerator * gen; - - //template info - si32 townType; - Terrain terrainType; - std::weak_ptr questArtZone; //artifacts required for Seer Huts will be placed here - or not if null - - std::vector possibleObjects; - int minGuardedValue; - - //content info - std::vector> requiredObjects; - std::vector> closeObjects; - std::vector> instantObjects; - std::vector> nearbyObjects; - std::vector objects; - std::map requestedPositions; - - //placement info - int3 pos; - float3 center; - std::set tileinfo; //irregular area assined to zone - std::set possibleTiles; //optimization purposes for treasure generation - std::set freePaths; //core paths of free tiles that all other objects will be linked to - std::set coastTiles; //tiles bordered to water - - std::set roadNodes; //tiles to be connected with roads - std::set roads; //all tiles with roads - std::set tilesToConnectLater; //will be connected after paths are fractalized - std::vector lakes; //disconnected parts of zone. Used to work with water zones - std::map 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); -}; diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index bae33194f..c95b4b359 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -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> & 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, float3> bestSolution; + std::map, 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 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(zones.size() * zones.size()); //experimental - the more zones, the greater total distance expected - std::shared_ptr misplacedZone; + std::shared_ptr 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 targetZone; + std::shared_ptr 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> & pr) + { + return pr.second->getType() == ETemplateZoneType::WATER; + }); - typedef std::pair, float> Dpair; + typedef std::pair, float> Dpair; std::vector 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 zone) -> void + auto moveZoneToCenterOfMass = [](std::shared_ptr 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::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::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"); diff --git a/lib/rmg/CZonePlacer.h b/lib/rmg/CZonePlacer.h index 86799a700..6558435bb 100644 --- a/lib/rmg/CZonePlacer.h +++ b/lib/rmg/CZonePlacer.h @@ -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>> TZoneVector; -typedef std::map > TZoneMap; -typedef std::map , float3> TForceVector; -typedef std::map , float> TDistanceVector; +typedef std::vector>> TZoneVector; +typedef std::map> TZoneMap; +typedef std::map, float3> TForceVector; +typedef std::map, 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 graph; - CMapGenerator * gen; + RmgMap & map; }; diff --git a/lib/rmg/ConnectionsPlacer.cpp b/lib/rmg/ConnectionsPlacer.cpp new file mode 100644 index 000000000..5d1071f65 --- /dev/null +++ b/lib/rmg/ConnectionsPlacer.cpp @@ -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()); + auto & manager = *zone.getModificator(); + 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()); + zone.getModificator()->addRoadNode(guardPos); + + assert(otherZone->getModificator()); + otherZone->getModificator()->addRoadNode(borderPos); + + assert(otherZone->getModificator()); + otherZone->getModificator()->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()) + { + if(generator.getZoneWater()->getModificator()->waterKeepConnection(connection.getZoneA(), connection.getZoneB())) + { + assert(otherZone->getModificator()); + otherZone->getModificator()->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()); + auto & manager = *zone.getModificator(); + + assert(otherZone->getModificator()); + auto & managerOther = *otherZone->getModificator(); + + 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()); + otherZone->getModificator()->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()->addRequiredObject(teleport1, connection.getGuardStrength()); + otherZone->getModificator()->addRequiredObject(teleport2, connection.getGuardStrength()); + + assert(otherZone->getModificator()); + otherZone->getModificator()->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); + } +} diff --git a/lib/rmg/ConnectionsPlacer.h b/lib/rmg/ConnectionsPlacer.h new file mode 100644 index 000000000..bdbd86740 --- /dev/null +++ b/lib/rmg/ConnectionsPlacer.h @@ -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 dConnections, dCompleted; + std::map dNeighbourZones; +}; diff --git a/lib/rmg/Functions.cpp b/lib/rmg/Functions.cpp new file mode 100644 index 000000000..9afd30c22 --- /dev/null +++ b/lib/rmg/Functions.cpp @@ -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(); + zone.addModificator(); + zone.addModificator(); + zone.addModificator(); + break; + + default: + zone.addModificator(); + zone.addModificator(); + zone.addModificator(); + zone.addModificator(); + zone.addModificator(); + 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 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 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); + }*/ +} diff --git a/lib/rmg/Functions.h b/lib/rmg/Functions.h new file mode 100644 index 000000000..0be072e84 --- /dev/null +++ b/lib/rmg/Functions.h @@ -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 //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); + diff --git a/lib/rmg/ObjectManager.cpp b/lib/rmg/ObjectManager.cpp new file mode 100644 index 000000000..ac90cf4fa --- /dev/null +++ b/lib/rmg/ObjectManager.cpp @@ -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 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 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()) + { + 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()) + m->addRoadNode(object.instances().front()->getVisitablePosition()); + break; + + case Obj::WATER_WHEEL: + if(auto * m = zone.getModificator()) + 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 value1{2500, 1500, 1000, 500, 0}; + static const std::array value2{7500, 7500, 7500, 5000, 5000}; + static const std::array multiplier1{0.5, 0.75, 1.0, 1.5, 1.5}; + static const std::array multiplier2{0.5, 0.75, 1.0, 1.0, 1.5}; + + int strength1 = static_cast(std::max(0.f, (strength - value1.at(monsterStrength)) * multiplier1.at(monsterStrength))); + int strength2 = static_cast(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 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(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; +} diff --git a/lib/rmg/ObjectManager.h b/lib/rmg/ObjectManager.h new file mode 100644 index 000000000..c24087765 --- /dev/null +++ b/lib/rmg/ObjectManager.h @@ -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 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 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> requiredObjects; + std::vector> closeObjects; + std::vector> instantObjects; + std::vector> nearbyObjects; + std::vector objects; + rmg::Area objectsVisitableArea; +}; diff --git a/lib/rmg/ObstaclePlacer.cpp b/lib/rmg/ObstaclePlacer.cpp new file mode 100644 index 000000000..74e158889 --- /dev/null +++ b/lib/rmg/ObstaclePlacer.cpp @@ -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(); + if(!manager) + return; + + auto * riverManager = zone.getModificator(); + + typedef std::vector ObstacleVector; + //obstacleVector possibleObstacles; + + std::map obstaclesBySize; + typedef std::pair ObstaclePair; + std::vector 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 allObjects; + std::vector> weightedObjects; //obj + position + int maxWeight = std::numeric_limits::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); +} diff --git a/lib/rmg/ObstaclePlacer.h b/lib/rmg/ObstaclePlacer.h new file mode 100644 index 000000000..cb1242048 --- /dev/null +++ b/lib/rmg/ObstaclePlacer.h @@ -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; +}; diff --git a/lib/rmg/RiverPlacer.cpp b/lib/rmg/RiverPlacer.cpp new file mode 100644 index 000000000..a9086f056 --- /dev/null +++ b/lib/rmg/RiverPlacer.cpp @@ -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 RIVER_DELTA_TEMPLATE_NAME +{ + {RIVER_NAMES[1], "clrdelt"}, + {RIVER_NAMES[2], "icedelt"}, + {RIVER_NAMES[3], "muddelt"}, + {RIVER_NAMES[4], "lavdelt"} +}; + +const std::array, 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{ + 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{ + 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{ + 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 { + 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()) + { + 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 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()) + { + 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 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()); +} diff --git a/lib/rmg/RiverPlacer.h b/lib/rmg/RiverPlacer.h new file mode 100644 index 000000000..46c6e1e23 --- /dev/null +++ b/lib/rmg/RiverPlacer.h @@ -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 deltaPositions; + std::map deltaOrientations; + + std::map heightMap; +}; diff --git a/lib/rmg/RmgArea.cpp b/lib/rmg/RmgArea.cpp new file mode 100644 index 000000000..0c7d4fbf7 --- /dev/null +++ b/lib/rmg/RmgArea.cpp @@ -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 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 connectedAreas(const Area & area) +{ + std::list result; + Tileset connected = area.getTiles(); + while(!connected.empty()) + { + result.emplace_back(); + std::list queue({*connected.begin()}); + std::set 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 & 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 & 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 & 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 & 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::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::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 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(); +} + +} diff --git a/lib/rmg/RmgArea.h b/lib/rmg/RmgArea.h new file mode 100644 index 000000000..dcd03e691 --- /dev/null +++ b/lib/rmg/RmgArea.h @@ -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; + using DistanceMap = std::map; + 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 & getTilesVector() const; + const Tileset & getBorder() const; //lazy cache invalidation + const Tileset & getBorderOutside() const; //lazy cache invalidation + + DistanceMap computeDistanceMap(std::map & reverseDistanceMap) const; + + Area getSubarea(std::function filter) const; + + bool connected() const; //is connected + bool empty() const; + bool contains(const int3 & tile) const; + bool contains(const std::vector & tiles) const; + bool contains(const Area & area) const; + bool overlap(const Area & area) const; + bool overlap(const std::vector & 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 connectedAreas(const Area & area); + + private: + + void invalidate(); + void computeBorderCache(); + void computeBorderOutsideCache(); + + mutable Tileset dTiles; + mutable std::vector dTilesVectorCache; + mutable Tileset dBorderCache; + mutable Tileset dBorderOutsideCache; + mutable int3 dTotalShiftCache; + }; +} diff --git a/lib/rmg/RmgMap.cpp b/lib/rmg/RmgMap.cpp new file mode 100644 index 000000000..d6074106b --- /dev/null +++ b/lib/rmg/RmgMap.cpp @@ -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(); + getEditManager()->getUndoManager().setUndoRedoLimit(0); +} + +void RmgMap::foreach_neighbour(const int3 &pos, std::function 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 foo) +{ + for(const int3 &dir : dirs4) + { + int3 n = pos + dir; + if(mapInstance->isInTheMap(n)) + foo(n); + } +} + +void RmgMap::foreachDiagonalNeighbour(const int3& pos, std::function 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(*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(*this, generator); + zone->setOptions(options); + zones[zone->getId()] = zone; + break; + } +} + +void RmgMap::addModificators() +{ + for(auto & z : getZones()) + { + auto zone = z.second; + + zone->addModificator(); + zone->addModificator(); + zone->addModificator(); + zone->addModificator(); + + if(zone->getType() == ETemplateZoneType::WATER) + { + for(auto & z1 : getZones()) + { + z1.second->addModificator(); + z1.second->getModificator()->setWaterZone(zone->getId()); + } + zone->addModificator(); + zone->addModificator(); + } + else + { + zone->addModificator(); + zone->addModificator(); + zone->addModificator(); + zone->addModificator(); + } + + if(zone->isUnderground()) + { + zone->addModificator(); + } + + } +} + +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; +} diff --git a/lib/rmg/RmgMap.h b/lib/rmg/RmgMap.h new file mode 100644 index 000000000..ab5be9b78 --- /dev/null +++ b/lib/rmg/RmgMap.h @@ -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 mapInstance; + CMap & map() const; + + RmgMap(const CMapGenOptions& mapGenOptions); + ~RmgMap(); + + CMapEditManager* getEditManager() const; + const CMapGenOptions& getMapGenOptions() const; + + void foreach_neighbour(const int3 &pos, std::function foo); + void foreachDirectNeighbour(const int3 &pos, std::function foo); + void foreachDiagonalNeighbour(const int3& pos, std::function 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>; + + 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 zonesPerFaction; + ui32 zonesTotal; //zones that have their main town only + const CMapGenOptions& mapGenOptions; + boost::multi_array tiles; //[x][y][z] + boost::multi_array zoneColouring; //[x][y][z] +}; diff --git a/lib/rmg/RmgObject.cpp b/lib/rmg/RmgObject.cpp new file mode 100644 index 000000000..08d3bf7ac --- /dev/null +++ b/lib/rmg/RmgObject.cpp @@ -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(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(i.object()), i.getPosition()); + setPosition(object.getPosition()); +} + +std::list Object::instances() +{ + std::list result; + for(auto & i : dInstances) + result.push_back(&i); + return result; +} + +std::list Object::instances() const +{ + std::list 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(); +} + diff --git a/lib/rmg/RmgObject.h b/lib/rmg/RmgObject.h new file mode 100644 index 000000000..e418631c3 --- /dev/null +++ b/lib/rmg/RmgObject.h @@ -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 instances(); + std::list 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 dInstances; + mutable Area dFullAreaCache; + mutable Area dAccessibleAreaCache, dAccessibleAreaFullCache; + int3 dPosition; + ui32 dStrenght; +}; +} diff --git a/lib/rmg/RmgPath.cpp b/lib/rmg/RmgPath.cpp new file mode 100644 index 000000000..5fdd9a887 --- /dev/null +++ b/lib/rmg/RmgPath.cpp @@ -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 //A* + +using namespace rmg; + +const std::function Path::DEFAULT_MOVEMENT_FUNCTION = +[](const int3 & src, const int3 & dst) +{ + return 1.f; +}; + +//A* priority queue +typedef std::pair TDistance; +struct NodeComparer +{ + bool operator()(const TDistance & lhs, const TDistance & rhs) const + { + return (rhs.second < lhs.second); + } +}; +boost::heap::priority_queue> createPriorityQueue() +{ + return boost::heap::priority_queue>(); +} + +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 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 cameFrom; // The map of navigated nodes. + std::map 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, ¤tNode, &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::max(); + auto it = distances.find(pos); + if(it != distances.end()) + bestDistanceSoFar = static_cast(it->second); + + if(distance < bestDistanceSoFar) + { + cameFrom[pos] = currentNode; + open.push(std::make_pair(pos, distance)); + distances[pos] = distance; + } + }; + + auto dirs = int3::getDirs(); + std::vector 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 moveCostFunction) const +{ + return search(Tileset{dst}, straight, moveCostFunction); +} + +Path Path::search(const Area & dst, bool straight, std::function moveCostFunction) const +{ + return search(dst.getTiles(), straight, moveCostFunction); +} + +Path Path::search(const Path & dst, bool straight, std::function 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; +} diff --git a/lib/rmg/RmgPath.h b/lib/rmg/RmgPath.h new file mode 100644 index 000000000..0866f856c --- /dev/null +++ b/lib/rmg/RmgPath.h @@ -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 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 moveCostFunction = DEFAULT_MOVEMENT_FUNCTION) const; + Path search(const int3 & dst, bool straight, std::function moveCostFunction = DEFAULT_MOVEMENT_FUNCTION) const; + Path search(const Area & dst, bool straight, std::function moveCostFunction = DEFAULT_MOVEMENT_FUNCTION) const; + Path search(const Path & dst, bool straight, std::function 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; +}; +} diff --git a/lib/rmg/RoadPlacer.cpp b/lib/rmg/RoadPlacer.cpp new file mode 100644 index 000000000..03860fbae --- /dev/null +++ b/lib/rmg/RoadPlacer.cpp @@ -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); +} diff --git a/lib/rmg/RoadPlacer.h b/lib/rmg/RoadPlacer.h new file mode 100644 index 000000000..5c7f44337 --- /dev/null +++ b/lib/rmg/RoadPlacer.h @@ -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; +}; diff --git a/lib/rmg/RockPlacer.cpp b/lib/rmg/RockPlacer.cpp new file mode 100644 index 000000000..7688c690c --- /dev/null +++ b/lib/rmg/RockPlacer.cpp @@ -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()) + 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()) + { + if(m != this && !m->isFinished()) + return; + } + } + + processMap(); +} + +void RockPlacer::processMap() +{ + //merge all areas + for(auto & z : map.getZones()) + { + if(auto * m = z.second->getModificator()) + { + 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()) + { + //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()) + m->riverProhibit().unite(rockArea); + if(auto * m = zone.getModificator()) + 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); +} diff --git a/lib/rmg/RockPlacer.h b/lib/rmg/RockPlacer.h new file mode 100644 index 000000000..c7217674f --- /dev/null +++ b/lib/rmg/RockPlacer.h @@ -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; +}; diff --git a/lib/rmg/TerrainPainter.cpp b/lib/rmg/TerrainPainter.cpp new file mode 100644 index 000000000..fba82c55c --- /dev/null +++ b/lib/rmg/TerrainPainter.cpp @@ -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); +} diff --git a/lib/rmg/TerrainPainter.h b/lib/rmg/TerrainPainter.h new file mode 100644 index 000000000..7052d883f --- /dev/null +++ b/lib/rmg/TerrainPainter.h @@ -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; +}; diff --git a/lib/rmg/TileInfo.cpp b/lib/rmg/TileInfo.cpp new file mode 100644 index 000000000..9070333d3 --- /dev/null +++ b/lib/rmg/TileInfo.cpp @@ -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(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); +} diff --git a/lib/rmg/TileInfo.h b/lib/rmg/TileInfo.h new file mode 100644 index 000000000..30ed5d90f --- /dev/null +++ b/lib/rmg/TileInfo.h @@ -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; +}; diff --git a/lib/rmg/TownPlacer.cpp b/lib/rmg/TownPlacer.cpp new file mode 100644 index 000000000..78dfe5fdf --- /dev/null +++ b/lib/rmg/TownPlacer.cpp @@ -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(); + 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 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 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; +} diff --git a/lib/rmg/TownPlacer.h b/lib/rmg/TownPlacer.h new file mode 100644 index 000000000..3c7fcadbe --- /dev/null +++ b/lib/rmg/TownPlacer.h @@ -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; +}; diff --git a/lib/rmg/TreasurePlacer.cpp b/lib/rmg/TreasurePlacer.cpp new file mode 100644 index 000000000..22b8fe0ee --- /dev/null +++ b/lib/rmg/TreasurePlacer.cpp @@ -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(); + 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(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 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().max(); + + std::vector 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(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(map.getZoneCount(cre->faction)); + oi.value = static_cast(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 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 & 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(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((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 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 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 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(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::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()->possibleObjects.push_back(generateArtInfo(artid)); + + return obj; + }; + oi.setTemplate(Obj::SEER_HUT, randomAppearance, zone.getTerrainType()); + oi.value = static_cast(((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()->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()->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 TreasurePlacer::prepareTreasurePile(const CTreasureInfo& treasureInfo) +{ + std::vector 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 & 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> 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(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 &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 & 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((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(); +} diff --git a/lib/rmg/TreasurePlacer.h b/lib/rmg/TreasurePlacer.h new file mode 100644 index 000000000..8c03b1cb7 --- /dev/null +++ b/lib/rmg/TreasurePlacer.h @@ -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 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 prepareTreasurePile(const CTreasureInfo & treasureInfo); + rmg::Object constuctTreasurePile(const std::vector & treasureInfos); + + +protected: + std::vector 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 +}; diff --git a/lib/rmg/WaterAdopter.cpp b/lib/rmg/WaterAdopter.cpp new file mode 100644 index 000000000..df06e682d --- /dev/null +++ b/lib/rmg/WaterAdopter.cpp @@ -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 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 & outCollection) + { + if(waterArea.contains(t)) + { + reverseDistanceMap[0].insert(t); + outCollection.insert(t); + } + }; + std::set 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 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); +} diff --git a/lib/rmg/WaterAdopter.h b/lib/rmg/WaterAdopter.h new file mode 100644 index 000000000..8b97b8d74 --- /dev/null +++ b/lib/rmg/WaterAdopter.h @@ -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 distanceMap; + std::map reverseDistanceMap; +}; diff --git a/lib/rmg/WaterProxy.cpp b/lib/rmg/WaterProxy.cpp new file mode 100644 index 000000000..b058a98d0 --- /dev/null +++ b/lib/rmg/WaterProxy.cpp @@ -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()); + dependency(z.second->getModificator()); + postfunction(z.second->getModificator()); + postfunction(z.second->getModificator()); + } + POSTFUNCTION(TreasurePlacer); +} + +const std::vector & 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(); + 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()) + 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(); + 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(); + 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 '~'; +} diff --git a/lib/rmg/WaterProxy.h b/lib/rmg/WaterProxy.h new file mode 100644 index 000000000..769d70208 --- /dev/null +++ b/lib/rmg/WaterProxy.h @@ -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 distanceMap; //distance map for lake + std::map reverseDistanceMap; + std::map neighbourZones; //zones boardered. Area - part of land + std::set 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 & 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 lakes; //disconnected parts of zone. Used to work with water zones + std::map lakeMap; //map tile on lakeId which is position of lake in lakes array +1 +}; + diff --git a/lib/rmg/WaterRoutes.cpp b/lib/rmg/WaterRoutes.cpp new file mode 100644 index 000000000..1125c2c77 --- /dev/null +++ b/lib/rmg/WaterRoutes.cpp @@ -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(); + 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 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()); + postfunction(z.second->getModificator()); + postfunction(z.second->getModificator()); + } + dependency(zone.getModificator()); + postfunction(zone.getModificator()); +} + +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 ' '; +} + diff --git a/lib/rmg/WaterRoutes.h b/lib/rmg/WaterRoutes.h new file mode 100644 index 000000000..996318362 --- /dev/null +++ b/lib/rmg/WaterRoutes.h @@ -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 result; +}; diff --git a/lib/rmg/Zone.cpp b/lib/rmg/Zone.cpp new file mode 100644 index 000000000..cd83aa97c --- /dev/null +++ b/lib/rmg/Zone.cpp @@ -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 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(std::fmod(center.x, 1)); + center.y = static_cast(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 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 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 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(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()); +#define POSTFUNCTION(x) postfunction(zone.getModificator()); +#define DEPENDENCY_ALL(x) for(auto & z : map.getZones()) \ + { \ + dependency(z.second->getModificator()); \ + } +#define POSTFUNCTION_ALL(x) for(auto & z : map.getZones()) \ + { \ + postfunction(z.second->getModificator()); \ + } + +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 preceeders; //must be ordered container + void dump(); +}; + +extern std::function 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 areafilter = AREA_NO_FILTER) const; + rmg::Path searchPath(const int3 & src, bool onlyStraight, std::function areafilter = AREA_NO_FILTER) const; + + template + T* getModificator() + { + for(auto & m : modificators) + if(auto * mm = dynamic_cast(m.get())) + return mm; + return nullptr; + } + + template + void addModificator() + { + modificators.emplace_back(new T(*this, map, generator)); + } + + void initModificators(); + void processModificators(); + +protected: + CMapGenerator & generator; + RmgMap & map; + std::list> 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; + +}; From 9ec0ca332c2da5d7c1a19bb6268f776d99dbdb04 Mon Sep 17 00:00:00 2001 From: Nordsoft91 Date: Tue, 9 Aug 2022 16:12:58 +0400 Subject: [PATCH 08/15] Fix for sealed-off treasures (#771) Fix for sealer-off treasures --- lib/rmg/Zone.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/rmg/Zone.cpp b/lib/rmg/Zone.cpp index cd83aa97c..627a397d1 100644 --- a/lib/rmg/Zone.cpp +++ b/lib/rmg/Zone.cpp @@ -155,7 +155,7 @@ rmg::Path Zone::searchPath(const rmg::Area & src, bool onlyStraight, std::functi }; auto area = (dAreaPossible + dAreaFree).getSubarea(areafilter); - rmg::Path freePath(area); + rmg::Path freePath(area), resultPath(area); freePath.connect(dAreaFree); //connect to all pieces @@ -167,9 +167,10 @@ rmg::Path Zone::searchPath(const rmg::Area & src, bool onlyStraight, std::functi return rmg::Path::invalid(); freePath.connect(path.getPathArea()); + resultPath.connect(path.getPathArea()); } - return freePath; + return resultPath; } rmg::Path Zone::searchPath(const int3 & src, bool onlyStraight, std::function areafilter) const From ae63e0c30e0a8088e793b970845f2cefea7390a1 Mon Sep 17 00:00:00 2001 From: Nordsoft91 Date: Thu, 11 Aug 2022 15:29:39 +0400 Subject: [PATCH 09/15] Fix problem with empty areas (#772) * Fixes for obstacles and treasures --- lib/rmg/ObjectManager.cpp | 5 +++++ lib/rmg/ObstaclePlacer.cpp | 5 ++++- lib/rmg/TreasurePlacer.cpp | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/rmg/ObjectManager.cpp b/lib/rmg/ObjectManager.cpp index ac90cf4fa..6a1badedd 100644 --- a/lib/rmg/ObjectManager.cpp +++ b/lib/rmg/ObjectManager.cpp @@ -135,6 +135,7 @@ rmg::Path ObjectManager::placeAndConnectObject(const rmg::Area & searchArea, rmg { auto & guardedArea = obj.instances().back()->getAccessibleArea(); accessibleArea.intersect(guardedArea); + accessibleArea.add(obj.instances().back()->getPosition(true)); } auto path = zone.searchPath(accessibleArea, onlyStraight, [&obj, isGuarded](const int3 & t) @@ -145,6 +146,10 @@ rmg::Path ObjectManager::placeAndConnectObject(const rmg::Area & searchArea, rmg auto & unguardedArea = obj.getAccessibleArea(isGuarded); if(unguardedArea.contains(t) && !guardedArea.contains(t)) return false; + + //guard position is always target + if(obj.instances().back()->getPosition(true) == t) + return true; } return !obj.getArea().contains(t); }); diff --git a/lib/rmg/ObstaclePlacer.cpp b/lib/rmg/ObstaclePlacer.cpp index 74e158889..9baa60917 100644 --- a/lib/rmg/ObstaclePlacer.cpp +++ b/lib/rmg/ObstaclePlacer.cpp @@ -88,7 +88,10 @@ void ObstaclePlacer::process() if(!possibleObstacles[i].first) continue; - for(auto & temp : possibleObstacles[i].second) + auto shuffledObstacles = possibleObstacles[i].second; + RandomGeneratorUtil::randomShuffle(shuffledObstacles, generator.rand); + + for(auto & temp : shuffledObstacles) { auto handler = VLC->objtypeh->getHandlerFor(temp.id, temp.subid); auto obj = handler->create(temp); diff --git a/lib/rmg/TreasurePlacer.cpp b/lib/rmg/TreasurePlacer.cpp index 22b8fe0ee..8a6d1df3e 100644 --- a/lib/rmg/TreasurePlacer.cpp +++ b/lib/rmg/TreasurePlacer.cpp @@ -722,7 +722,7 @@ void TreasurePlacer::createTreasures(ObjectManager & manager) if(areaToBlock.overlap(zone.freePaths()) || areaToBlock.overlap(manager.getVisitableArea())) return -1.f; - return 1.f; + return ti.getNearestObjectDistance(); }, guarded, false, false); } else From f3f1fb80336bd12e8c9e3712803be8efe259c88a Mon Sep 17 00:00:00 2001 From: Nordsoft91 Date: Sat, 20 Aug 2022 14:17:27 +0400 Subject: [PATCH 10/15] Improve treasure placement logic (#775) Improve treasure placement logic (#775) --- lib/rmg/CMapGenerator.cpp | 2 +- lib/rmg/ConnectionsPlacer.cpp | 4 +- lib/rmg/ObjectManager.cpp | 95 +++++++++++++++++++++++++++-------- lib/rmg/ObjectManager.h | 30 +++++++++-- lib/rmg/TownPlacer.cpp | 2 +- lib/rmg/TreasurePlacer.cpp | 48 +++++++++++++++--- lib/rmg/TreasurePlacer.h | 2 +- lib/rmg/WaterProxy.cpp | 4 +- 8 files changed, 149 insertions(+), 38 deletions(-) diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index 450aeb538..d4e6a98dc 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -286,8 +286,8 @@ void CMapGenerator::fillZones() for(auto it : map->getZones()) { - it.second->initModificators(); it.second->initFreeTiles(); + it.second->initModificators(); } std::vector> treasureZones; diff --git a/lib/rmg/ConnectionsPlacer.cpp b/lib/rmg/ConnectionsPlacer.cpp index 5d1071f65..78aa17984 100644 --- a/lib/rmg/ConnectionsPlacer.cpp +++ b/lib/rmg/ConnectionsPlacer.cpp @@ -224,10 +224,10 @@ void ConnectionsPlacer::selfSideIndirectConnection(const rmg::ZoneConnection & c rmg::Area toPlace(rmgGate1.getArea() + rmgGate1.getAccessibleArea()); toPlace.translate(-zShift); - path2 = managerOther.placeAndConnectObject(toPlace, rmgGate2, minDist, guarded2, true, false); + path2 = managerOther.placeAndConnectObject(toPlace, rmgGate2, minDist, guarded2, true, ObjectManager::OptimizeType::NONE); return path2.valid() ? 1.f : -1.f; - }, guarded1, true, false); + }, guarded1, true, ObjectManager::OptimizeType::NONE); if(path1.valid() && path2.valid()) { diff --git a/lib/rmg/ObjectManager.cpp b/lib/rmg/ObjectManager.cpp index 6a1badedd..5846c8bf5 100644 --- a/lib/rmg/ObjectManager.cpp +++ b/lib/rmg/ObjectManager.cpp @@ -34,6 +34,16 @@ void ObjectManager::init() { DEPENDENCY(WaterAdopter); POSTFUNCTION(RoadPlacer); + createDistancesPriorityQueue(); +} + +void ObjectManager::createDistancesPriorityQueue() +{ + tilesByDistance.clear(); + for (auto & tile : zone.areaPossible().getTilesVector()) + { + tilesByDistance.push(std::make_pair(tile, map.getNearestObjectDistance(tile))); + } } void ObjectManager::addRequiredObject(CGObjectInstance * obj, si32 strength) @@ -53,10 +63,12 @@ void ObjectManager::addNearbyObject(CGObjectInstance * obj, CGObjectInstance * n void ObjectManager::updateDistances(const rmg::Object & obj) { + tilesByDistance.clear(); for (auto tile : zone.areaPossible().getTiles()) //don't need to mark distance for not possible tiles { ui32 d = obj.getArea().distanceSqr(tile); //optimization, only relative distance is interesting map.setNearestObjectDistance(tile, std::min((float)d, map.getNearestObjectDistance(tile))); + tilesByDistance.push(std::make_pair(tile, map.getNearestObjectDistance(tile))); } } @@ -65,59 +77,102 @@ const rmg::Area & ObjectManager::getVisitableArea() const return objectsVisitableArea; } -int3 ObjectManager::findPlaceForObject(const rmg::Area & searchArea, rmg::Object & obj, std::function weightFunction, bool optimizer) const +int3 ObjectManager::findPlaceForObject(const rmg::Area & searchArea, rmg::Object & obj, std::function weightFunction, OptimizeType optimizer) const { float bestWeight = 0.f; int3 result(-1, -1, -1); - for(const auto & tile : searchArea.getTiles()) + if(optimizer & OptimizeType::DISTANCE) { - obj.setPosition(tile); - - if(!searchArea.contains(obj.getArea()) || !searchArea.overlap(obj.getAccessibleArea())) - continue; - - float weight = weightFunction(tile); - if(weight > bestWeight) + auto open = tilesByDistance; + while(!open.empty()) { - bestWeight = weight; - result = tile; - if(!optimizer) - break; + auto node = open.top(); + open.pop(); + int3 tile = node.first; + + if(!searchArea.contains(tile)) + continue; + + 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 & OptimizeType::WEIGHT)) + break; + } } } + else + { + 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 & OptimizeType::WEIGHT)) + 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 +int3 ObjectManager::findPlaceForObject(const rmg::Area & searchArea, rmg::Object & obj, si32 min_dist, OptimizeType optimizer) const { - return findPlaceForObject(searchArea, obj, [this, min_dist](const int3 & tile) + return findPlaceForObject(searchArea, obj, [this, min_dist, &obj](const int3 & tile) { auto ti = map.getTile(tile); float dist = ti.getNearestObjectDistance(); if(dist < min_dist) return -1.f; + for(auto & t : obj.getArea().getTilesVector()) + { + if(map.getTile(t).getNearestObjectDistance() < min_dist) + return -1.f; + } + return dist; }, optimizer); } -rmg::Path ObjectManager::placeAndConnectObject(const rmg::Area & searchArea, rmg::Object & obj, si32 min_dist, bool isGuarded, bool onlyStraight, bool optimizer) const +rmg::Path ObjectManager::placeAndConnectObject(const rmg::Area & searchArea, rmg::Object & obj, si32 min_dist, bool isGuarded, bool onlyStraight, OptimizeType optimizer) const { - return placeAndConnectObject(searchArea, obj, [this, min_dist](const int3 & tile) + return placeAndConnectObject(searchArea, obj, [this, min_dist, &obj](const int3 & tile) { auto ti = map.getTile(tile); float dist = ti.getNearestObjectDistance(); if(dist < min_dist) return -1.f; + for(auto & t : obj.getArea().getTilesVector()) + { + if(map.getTile(t).getNearestObjectDistance() < min_dist) + return -1.f; + } + return dist; }, isGuarded, onlyStraight, optimizer); } -rmg::Path ObjectManager::placeAndConnectObject(const rmg::Area & searchArea, rmg::Object & obj, std::function weightFunction, bool isGuarded, bool onlyStraight, bool optimizer) const +rmg::Path ObjectManager::placeAndConnectObject(const rmg::Area & searchArea, rmg::Object & obj, std::function weightFunction, bool isGuarded, bool onlyStraight, OptimizeType optimizer) const { int3 pos; auto possibleArea = searchArea; @@ -172,7 +227,7 @@ bool ObjectManager::createRequiredObjects() 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); + auto path = placeAndConnectObject(zone.areaPossible(), rmgObject, 3, guarded, false, OptimizeType::DISTANCE); if(!path.valid()) { @@ -217,7 +272,7 @@ bool ObjectManager::createRequiredObjects() 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); + }, guarded, false, OptimizeType::WEIGHT); if(!path.valid()) { diff --git a/lib/rmg/ObjectManager.h b/lib/rmg/ObjectManager.h index c24087765..3349d293d 100644 --- a/lib/rmg/ObjectManager.h +++ b/lib/rmg/ObjectManager.h @@ -12,13 +12,31 @@ #include "Zone.h" #include "RmgObject.h" +#include //A* class CGObjectInstance; class ObjectTemplate; class CGCreature; +typedef std::pair TDistance; +struct DistanceMaximizeFunctor +{ + bool operator()(const TDistance & lhs, const TDistance & rhs) const + { + return (rhs.second > lhs.second); + } +}; + class ObjectManager: public Modificator { +public: + enum OptimizeType + { + NONE = 0x00000000, + WEIGHT = 0x00000001, + DISTANCE = 0x00000010 + }; + public: MODIFICATOR(ObjectManager); @@ -31,17 +49,18 @@ public: 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 weightFunction, bool optimizer) const; + int3 findPlaceForObject(const rmg::Area & searchArea, rmg::Object & obj, si32 min_dist, OptimizeType optimizer) const; + int3 findPlaceForObject(const rmg::Area & searchArea, rmg::Object & obj, std::function weightFunction, OptimizeType 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 weightFunction, bool isGuarded, bool onlyStraight, bool optimizer) const; + rmg::Path placeAndConnectObject(const rmg::Area & searchArea, rmg::Object & obj, si32 min_dist, bool isGuarded, bool onlyStraight, OptimizeType optimizer) const; + rmg::Path placeAndConnectObject(const rmg::Area & searchArea, rmg::Object & obj, std::function weightFunction, bool isGuarded, bool onlyStraight, OptimizeType optimizer) const; CGCreature * chooseGuard(si32 strength, bool zoneGuard = false); bool addGuard(rmg::Object & object, si32 strength, bool zoneGuard = false); void placeObject(rmg::Object & object, bool guarded, bool updateDistance); void updateDistances(const rmg::Object & obj); + void createDistancesPriorityQueue(); const rmg::Area & getVisitableArea() const; @@ -53,4 +72,7 @@ protected: std::vector> nearbyObjects; std::vector objects; rmg::Area objectsVisitableArea; + + boost::heap::priority_queue> tilesByDistance; + }; diff --git a/lib/rmg/TownPlacer.cpp b/lib/rmg/TownPlacer.cpp index 78dfe5fdf..30212c872 100644 --- a/lib/rmg/TownPlacer.cpp +++ b/lib/rmg/TownPlacer.cpp @@ -142,7 +142,7 @@ int3 TownPlacer::placeMainTown(ObjectManager & manager, CGTownInstance & town) { float distance = zone.getPos().dist2dSQ(t); return 100000.f - distance; //some big number - }, true); + }, ObjectManager::OptimizeType::WEIGHT); rmgObject.setPosition(position); manager.placeObject(rmgObject, false, true); cleanupBoundaries(rmgObject); diff --git a/lib/rmg/TreasurePlacer.cpp b/lib/rmg/TreasurePlacer.cpp index 8a6d1df3e..6cb161349 100644 --- a/lib/rmg/TreasurePlacer.cpp +++ b/lib/rmg/TreasurePlacer.cpp @@ -545,7 +545,7 @@ std::vector TreasurePlacer::prepareTreasurePile(const CTreasureInfo return objectInfos; } -rmg::Object TreasurePlacer::constuctTreasurePile(const std::vector & treasureInfos) +rmg::Object TreasurePlacer::constructTreasurePile(const std::vector & treasureInfos, bool densePlacement) { rmg::Object rmgObject; for(auto & oi : treasureInfos) @@ -568,7 +568,32 @@ rmg::Object TreasurePlacer::constuctTreasurePile(const std::vector return rmgObject; } - int3 nextPos = *RandomGeneratorUtil::nextItem(accessibleArea.getTiles(), generator.rand); + std::vector bestPositions; + if(densePlacement) + { + int bestPositionsWeight = std::numeric_limits::max(); + for(auto & t : accessibleArea.getTilesVector()) + { + instance.setPosition(t); + int w = rmgObject.getAccessibleArea().getTilesVector().size(); + if(w < bestPositionsWeight) + { + bestPositions.clear(); + bestPositions.push_back(t); + bestPositionsWeight = w; + } + else if(w == bestPositionsWeight) + { + bestPositions.push_back(t); + } + } + } + else + { + bestPositions = accessibleArea.getTilesVector(); + } + + int3 nextPos = *RandomGeneratorUtil::nextItem(bestPositions, generator.rand); instance.setPosition(nextPos - rmgObject.getPosition()); auto instanceAccessibleArea = instance.getAccessibleArea(); @@ -637,6 +662,8 @@ ObjectInfo * TreasurePlacer::getRandomObject(ui32 desiredValue, ui32 currentValu void TreasurePlacer::createTreasures(ObjectManager & manager) { + const int maxAttempts = 2; + int mapMonsterStrength = map.getMapGenOptions().getMonsterStrength(); int monsterStrength = zone.zoneMonsterStrength + mapMonsterStrength - 1; //array index from 0 to 4 @@ -684,7 +711,7 @@ void TreasurePlacer::createTreasures(ObjectManager & manager) const float minDistance = std::max((125.f / totalDensity), 2.0f); //distance lower than 2 causes objects to overlap and crash - while(true) + for(int attempt = 0; attempt <= maxAttempts;) { auto treasurePileInfos = prepareTreasurePile(t); if(treasurePileInfos.empty()) @@ -692,7 +719,7 @@ void TreasurePlacer::createTreasures(ObjectManager & manager) int value = std::accumulate(treasurePileInfos.begin(), treasurePileInfos.end(), 0, [](int v, const ObjectInfo * oi){return v + oi->value;}); - auto rmgObject = constuctTreasurePile(treasurePileInfos); + auto rmgObject = constructTreasurePile(treasurePileInfos, attempt == maxAttempts); if(rmgObject.instances().empty()) //handle incorrect placement { restoreZoneLimits(treasurePileInfos); @@ -716,6 +743,12 @@ void TreasurePlacer::createTreasures(ObjectManager & manager) if(ti.getNearestObjectDistance() < minDistance) return -1.f; + for(auto & t : rmgObject.getArea().getTilesVector()) + { + if(map.getTile(t).getNearestObjectDistance() < minDistance) + return -1.f; + } + auto guardedArea = rmgObject.instances().back()->getAccessibleArea(); auto areaToBlock = rmgObject.getAccessibleArea(true); areaToBlock.subtract(guardedArea); @@ -723,11 +756,11 @@ void TreasurePlacer::createTreasures(ObjectManager & manager) return -1.f; return ti.getNearestObjectDistance(); - }, guarded, false, false); + }, guarded, false, ObjectManager::OptimizeType::DISTANCE); } else { - path = manager.placeAndConnectObject(possibleArea, rmgObject, minDistance, guarded, false, false); + path = manager.placeAndConnectObject(possibleArea, rmgObject, minDistance, guarded, false, ObjectManager::OptimizeType::DISTANCE); } if(path.valid()) @@ -744,12 +777,13 @@ void TreasurePlacer::createTreasures(ObjectManager & manager) } zone.connectPath(path); manager.placeObject(rmgObject, guarded, true); + attempt = 0; } else { restoreZoneLimits(treasurePileInfos); rmgObject.clear(); - break; + ++attempt; } } } diff --git a/lib/rmg/TreasurePlacer.h b/lib/rmg/TreasurePlacer.h index 8c03b1cb7..b3ecf39f7 100644 --- a/lib/rmg/TreasurePlacer.h +++ b/lib/rmg/TreasurePlacer.h @@ -52,7 +52,7 @@ protected: ObjectInfo * getRandomObject(ui32 desiredValue, ui32 currentValue, ui32 maxValue, bool allowLargeObjects); std::vector prepareTreasurePile(const CTreasureInfo & treasureInfo); - rmg::Object constuctTreasurePile(const std::vector & treasureInfos); + rmg::Object constructTreasurePile(const std::vector & treasureInfos, bool densePlacement = false); protected: diff --git a/lib/rmg/WaterProxy.cpp b/lib/rmg/WaterProxy.cpp index b058a98d0..38ba3b6b4 100644 --- a/lib/rmg/WaterProxy.cpp +++ b/lib/rmg/WaterProxy.cpp @@ -230,7 +230,7 @@ bool WaterProxy::placeBoat(Zone & land, const Lake & lake, RouteInfo & info) } //try to place boat at water, create paths on water and land - auto path = manager->placeAndConnectObject(shipPositions, rmgObject, 2, false, true, false); + auto path = manager->placeAndConnectObject(shipPositions, rmgObject, 2, false, true, ObjectManager::OptimizeType::NONE); auto landPath = land.searchPath(boardingPosition, false); if(!path.valid() || !landPath.valid()) { @@ -298,7 +298,7 @@ bool WaterProxy::placeShipyard(Zone & land, const Lake & lake, si32 guard, Route return -1.f; return 1.0f; - }, guarded, true, false); + }, guarded, true, ObjectManager::OptimizeType::NONE); //search path to boarding position auto searchArea = land.areaPossible() - rmgObject.getArea(); From f668c65eb2b481c8385dfdad8b61109f46b70e17 Mon Sep 17 00:00:00 2001 From: Nordsoft91 Date: Mon, 22 Aug 2022 15:32:54 +0400 Subject: [PATCH 11/15] Fix RMG bug with incorrect town placement (#779) * Fix RMG bug with incorrect town placement * Readability tweak --- lib/registerTypes/RegisterTypes.h | 2 +- lib/rmg/TownPlacer.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/registerTypes/RegisterTypes.h b/lib/registerTypes/RegisterTypes.h index 6bf06044b..17e46c54c 100644 --- a/lib/registerTypes/RegisterTypes.h +++ b/lib/registerTypes/RegisterTypes.h @@ -142,11 +142,11 @@ void registerTypesMapObjectTypes(Serializer &s) s.template registerType(); s.template registerType(); s.template registerType(); + s.template registerType(); s.template registerType(); s.template registerType(); s.template registerType(); - s.template registerType(); //new types (other than netpacks) must register here //order of type registration is critical for loading old savegames } diff --git a/lib/rmg/TownPlacer.cpp b/lib/rmg/TownPlacer.cpp index 30212c872..6aff7a02d 100644 --- a/lib/rmg/TownPlacer.cpp +++ b/lib/rmg/TownPlacer.cpp @@ -138,6 +138,7 @@ int3 TownPlacer::placeMainTown(ObjectManager & manager, CGTownInstance & town) { //towns are big objects and should be centered around visitable position rmg::Object rmgObject(town); + rmgObject.setTemplate(zone.getTerrainType()); auto position = manager.findPlaceForObject(zone.areaPossible(), rmgObject, [this](const int3 & t) { float distance = zone.getPos().dist2dSQ(t); From 47d3539f41c59f03d4193b25594a0ef96eee411c Mon Sep 17 00:00:00 2001 From: Nordsoft91 Date: Sun, 28 Aug 2022 12:54:06 +0400 Subject: [PATCH 12/15] Fix problems with water zone (#784) --- lib/rmg/RmgArea.cpp | 9 +++++++-- lib/rmg/RmgArea.h | 5 ++++- lib/rmg/RmgMap.cpp | 7 ++----- lib/rmg/RmgPath.cpp | 2 +- lib/rmg/TreasurePlacer.cpp | 5 ++++- lib/rmg/WaterProxy.cpp | 3 ++- lib/rmg/WaterRoutes.cpp | 3 +++ lib/rmg/Zone.cpp | 4 ++-- 8 files changed, 25 insertions(+), 13 deletions(-) diff --git a/lib/rmg/RmgArea.cpp b/lib/rmg/RmgArea.cpp index 0c7d4fbf7..a2fb40045 100644 --- a/lib/rmg/RmgArea.cpp +++ b/lib/rmg/RmgArea.cpp @@ -85,8 +85,13 @@ bool Area::connected() const return connected.empty(); } -std::list connectedAreas(const Area & area) +std::list connectedAreas(const Area & area, bool disableDiagonalConnections) { + auto allDirs = int3::getDirs(); + std::vector dirs(allDirs.begin(), allDirs.end()); + if(disableDiagonalConnections) + dirs.assign(rmg::dirs4.begin(), rmg::dirs4.end()); + std::list result; Tileset connected = area.getTiles(); while(!connected.empty()) @@ -101,7 +106,7 @@ std::list connectedAreas(const Area & area) result.back().add(t); queue.pop_front(); - for(auto & i : int3::getDirs()) + for(auto & i : dirs) { auto tile = t + i; if(!queueSet.count(tile) && connected.count(tile) && !result.back().contains(tile)) diff --git a/lib/rmg/RmgArea.h b/lib/rmg/RmgArea.h index dcd03e691..bc821dc02 100644 --- a/lib/rmg/RmgArea.h +++ b/lib/rmg/RmgArea.h @@ -15,6 +15,9 @@ namespace rmg { + static const std::array dirs4 = { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0) }; + static const std::array dirsDiagonal= { int3(1,1,0),int3(1,-1,0),int3(-1,1,0),int3(-1,-1,0) }; + using Tileset = std::set; using DistanceMap = std::map; void toAbsolute(Tileset & tiles, const int3 & position); @@ -66,7 +69,7 @@ namespace rmg 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 connectedAreas(const Area & area); + friend std::list connectedAreas(const Area & area, bool disableDiagonalConnections); private: diff --git a/lib/rmg/RmgMap.cpp b/lib/rmg/RmgMap.cpp index d6074106b..732bb59af 100644 --- a/lib/rmg/RmgMap.cpp +++ b/lib/rmg/RmgMap.cpp @@ -30,9 +30,6 @@ #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) { @@ -54,7 +51,7 @@ void RmgMap::foreach_neighbour(const int3 &pos, std::function f void RmgMap::foreachDirectNeighbour(const int3& pos, std::function foo) { - for(const int3 &dir : dirs4) + for(const int3 &dir : rmg::dirs4) { int3 n = pos + dir; if(mapInstance->isInTheMap(n)) @@ -64,7 +61,7 @@ void RmgMap::foreachDirectNeighbour(const int3& pos, std::function foo) { - for (const int3 &dir : dirsDiagonal) + for (const int3 &dir : rmg::dirsDiagonal) { int3 n = pos + dir; if (mapInstance->isInTheMap(n)) diff --git a/lib/rmg/RmgPath.cpp b/lib/rmg/RmgPath.cpp index 5fdd9a887..7e38ae04c 100644 --- a/lib/rmg/RmgPath.cpp +++ b/lib/rmg/RmgPath.cpp @@ -137,7 +137,7 @@ Path Path::search(const Tileset & dst, bool straight, std::function neighbors(dirs.begin(), dirs.end()); if(straight) - neighbors = { { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0) } }; + neighbors.assign(rmg::dirs4.begin(), rmg::dirs4.end()); for(auto & i : neighbors) { computeTileScore(currentNode + i); diff --git a/lib/rmg/TreasurePlacer.cpp b/lib/rmg/TreasurePlacer.cpp index 6cb161349..7e24f2d28 100644 --- a/lib/rmg/TreasurePlacer.cpp +++ b/lib/rmg/TreasurePlacer.cpp @@ -715,7 +715,10 @@ void TreasurePlacer::createTreasures(ObjectManager & manager) { auto treasurePileInfos = prepareTreasurePile(t); if(treasurePileInfos.empty()) - break; + { + ++attempt; + continue; + } int value = std::accumulate(treasurePileInfos.begin(), treasurePileInfos.end(), 0, [](int v, const ObjectInfo * oi){return v + oi->value;}); diff --git a/lib/rmg/WaterProxy.cpp b/lib/rmg/WaterProxy.cpp index 38ba3b6b4..5b5354c45 100644 --- a/lib/rmg/WaterProxy.cpp +++ b/lib/rmg/WaterProxy.cpp @@ -93,7 +93,7 @@ const std::vector & WaterProxy::getLakes() const void WaterProxy::collectLakes() { int lakeId = 0; - for(auto lake : connectedAreas(zone.getArea())) + for(auto lake : connectedAreas(zone.getArea(), true)) { lakes.push_back(Lake{}); lakes.back().area = lake; @@ -267,6 +267,7 @@ bool WaterProxy::placeShipyard(Zone & land, const Lake & lake, si32 guard, Route bool guarded = manager->addGuard(rmgObject, guard); auto waterAvailable = zone.areaPossible() + zone.freePaths(); + waterAvailable.intersect(lake.area); rmg::Area coast = lake.neighbourZones.at(land.getId()); //having land tiles coast.intersect(land.areaPossible() + land.freePaths()); //having only available land tiles auto boardingPositions = coast.getSubarea([&waterAvailable](const int3 & tile) //tiles where boarding is possible diff --git a/lib/rmg/WaterRoutes.cpp b/lib/rmg/WaterRoutes.cpp index 1125c2c77..40569d4a2 100644 --- a/lib/rmg/WaterRoutes.cpp +++ b/lib/rmg/WaterRoutes.cpp @@ -34,6 +34,9 @@ void WaterRoutes::process() if(!wproxy) return; + if(auto * manager = zone.getModificator()) + manager->createDistancesPriorityQueue(); + for(auto & z : map.getZones()) { if(z.first != zone.getId()) diff --git a/lib/rmg/Zone.cpp b/lib/rmg/Zone.cpp index 627a397d1..d859cf449 100644 --- a/lib/rmg/Zone.cpp +++ b/lib/rmg/Zone.cpp @@ -159,7 +159,7 @@ rmg::Path Zone::searchPath(const rmg::Area & src, bool onlyStraight, std::functi freePath.connect(dAreaFree); //connect to all pieces - auto goals = connectedAreas(src); + auto goals = connectedAreas(src, onlyStraight); for(auto & goal : goals) { auto path = freePath.search(goal, onlyStraight, movementCost); @@ -235,7 +235,7 @@ void Zone::fractalize() } //cut straight paths towards the center. A* is too slow for that. - auto areas = connectedAreas(clearedTiles); + auto areas = connectedAreas(clearedTiles, false); for(auto & area : areas) { if(dAreaFree.overlap(area)) From be6a7d18366b9d232957e11a53b12ccf2524be49 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Wed, 31 Aug 2022 01:03:35 +0400 Subject: [PATCH 13/15] Randomly shuffle required objects to avoid patterns --- lib/rmg/ObjectManager.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/rmg/ObjectManager.cpp b/lib/rmg/ObjectManager.cpp index 5846c8bf5..06f037e00 100644 --- a/lib/rmg/ObjectManager.cpp +++ b/lib/rmg/ObjectManager.cpp @@ -27,7 +27,7 @@ void ObjectManager::process() { zone.fractalize(); - createRequiredObjects(); + createRequiredObjects(); } void ObjectManager::init() @@ -220,6 +220,7 @@ bool ObjectManager::createRequiredObjects() { logGlobal->trace("Creating required objects"); + RandomGeneratorUtil::randomShuffle(requiredObjects, generator.rand); for(const auto & object : requiredObjects) { auto * obj = object.first; From c6c5c07e2df0d69648bba62f4ef004d285c678c2 Mon Sep 17 00:00:00 2001 From: Nordsoft91 Date: Sat, 3 Sep 2022 20:02:24 +0400 Subject: [PATCH 14/15] Final preparations for merge into develop (#780) * version updated, change log updated from release draft * horse sounds for terrains * Update and freeze essential files --- AUTHORS | 2 +- CMakeLists.txt | 4 +- ChangeLog | 55 +++++++++++++++--- .../CreaturePurchaseCard.png | Bin 0 -> 5666 bytes Mods/vcmi/Data/s/std.verm | 40 +++++++++++++ Mods/vcmi/Data/s/testy.erm | 14 +++++ Mods/vcmi/Maps/VCMI_Tests_2011b.h3m | Bin 0 -> 18793 bytes config/terrains.json | 30 ++++++---- lib/GameConstants.cpp | 4 +- 9 files changed, 126 insertions(+), 23 deletions(-) create mode 100755 Mods/vcmi/Data/QuickRecruitmentWindow/CreaturePurchaseCard.png create mode 100755 Mods/vcmi/Data/s/std.verm create mode 100755 Mods/vcmi/Data/s/testy.erm create mode 100755 Mods/vcmi/Maps/VCMI_Tests_2011b.h3m diff --git a/AUTHORS b/AUTHORS index c8a5bb872..c58bbfd79 100644 --- a/AUTHORS +++ b/AUTHORS @@ -80,4 +80,4 @@ Dmitry Orlov, * special buildings support in fan towns, new features and bug fixes Andrey Cherkas aka nordsoft, - * random map generator features and bug fixes \ No newline at end of file + * new terrain support, random map generator features and various bug fixes \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f09fc070..6baab705a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,8 +40,8 @@ if(NOT CMAKE_BUILD_TYPE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release RelWithDebInfo) endif() -set(VCMI_VERSION_MAJOR 0) -set(VCMI_VERSION_MINOR 99) +set(VCMI_VERSION_MAJOR 1) +set(VCMI_VERSION_MINOR 0) set(VCMI_VERSION_PATCH 0) option(ENABLE_ERM "Enable compilation of ERM scripting module" ON) diff --git a/ChangeLog b/ChangeLog index b0c3d0597..57c7f4e37 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,9 +1,12 @@ -0.99 -> 1.00 +0.99 -> 1.0 GENERAL: * Spectator mode was implemented through command-line options * Some main menu settings get saved after returning to main menu - last selected map, save etc. * Restart scenario button should work correctly now +* Skyship Grail works now immediately after capturing without battle +* Lodestar Grail implemented +* Fixed Gargoyles immunity * New bonuses: - SOUL_STEAL - "WoG ghost" ability, should work somewhat same as in H3 - TRANSMUTATION - "WoG werewolf"-like ability @@ -35,17 +38,53 @@ MODS: * Added bonus updaters for hero specialties * Added allOf, anyOf and noneOf qualifiers for bonus limiters * Added bonus limiters: alignment, faction and terrain +* Supported new terrains, new battlefields, custom water and rock terrains +* Following special buildings becomes available in the fan towns: +- attackVisitingBonus +- defenceVisitingBonus +- spellPowerVisitingBonus +- knowledgeVisitingBonus +- experienceVisitingBonus +- lighthouse +- treasury SOUND: * Fixed many mising or wrong pickup and visit sounds for map objects -* All map objects now have ambient sounds identical to OH3 - -RANDOM MAP GENERATOR: -* Random map generator supports water modes (normal, islands) -* Added config randomMap.json with settings for map generator -* Added parameter for template allowedWaterContent -* Extra resource packs appear nearby mines +* All map objects now have ambient sounds identical to OH3 + +RANDOM MAP GENERATOR: +* Random map generator supports water modes (normal, islands) +* Added config randomMap.json with settings for map generator +* Added parameter for template allowedWaterContent +* Extra resource packs appear nearby mines * Underground can be player starting place for factions allowed to be placed underground +* Improved obstacles placement aesthetics +* Rivers are generated on the random maps +* RMG works more stable, various crashes have been fixed +* Treasures requiring guards are guaranteed to be protected + +VCAI: +* Reworked goal decomposition engine, fixing many loopholes. AI will now pick correct goals faster. +* AI will now use universal pathfinding globally +* AI can use Summon Boat and Town Portal +* AI can gather and save resources on purpose +* AI will only buy army on demand instead of every turn +* AI can distinguish the value of all map objects +* General speed optimizations + +BATTLES: +* Towers should block ranged retaliation +* AI can bypass broken wall with moat instead of standing and waiting until gate is destroyed +* Towers do not attack war machines automatically +* Draw is possible now as battle outcome in case the battle ends with only summoned creatures (both sides loose) + +ADVENTURE MAP: +* Added buttons and keyboard shortcuts to quickly exchange army and artifacts between heroes +* Fix: Captured town should not be duplicated on the UI + +LAUNCHER: +* Implemented notifications about updates +* Supported redirection links for downloading mods 0.98 -> 0.99 diff --git a/Mods/vcmi/Data/QuickRecruitmentWindow/CreaturePurchaseCard.png b/Mods/vcmi/Data/QuickRecruitmentWindow/CreaturePurchaseCard.png new file mode 100755 index 0000000000000000000000000000000000000000..9920a1ea2a1e77f736ae22715be30f1ebda470e0 GIT binary patch literal 5666 zcmai22T)U6yA8dlbc9?wQl*7}bfiUkD3LBDfPiR#(4-T32c<|2O{54SARP=vs#1ak zDS~tj9h7Qlk9*&nJMX=@|NqaLUC!R?%sF#rt^KVz@kRz(H>m%h1^@szbhII`s}TVJ z5UEm{3bk)!>GJ53UkwX$jvKmz?MVry#n%2OvQJ7LoP!>Nig`iF1g9r;`vlj3+FZHX82Vr?+t` zN!@_U!i-i+UP*vab`t*A$&59S<)fUkIhw!O@MCt9G;|cCJIZSr0X9qb{w4Y#_$bGT zR(>BGr34)E9$j|YTb&bJNtS2mNJ@nRS@4u6t>6v;$4DQY7}b7{)jD9Xfeb(>ZBG1$ z_x*9^aD>=O0ANx3qut)PgvBtHafr5Wbl6Iqfv-FcC}4d*COMrkaS4ls;uHIZ zK{T?-x0WTRbAXoYOI`qH?3pD~fE(}~ui~_vyd20)%&kLc0%dcSmv;O#(>4mV1B7s$ z^cva>H;O7X6FCn|!DgM=c1rg~51qEBDNbSehGf zPnBdtm2HiLv5T~+i@pUwyGTN;CKN~N371@qV0umj;bvh9Yb;{Th^U4OR*?G^(Mw0l zAX$CF9T%nLD8$2u;G}azFc3{anD8F;uqs_t^dKEGEY^oxW03t_w8e9_RT44{dL#A> zN`4J>ZDMBC%_3L@du7ZuD zlqi<&k9%M_F7b%wt`|s>V7i?s!=9`~dJAfm@N)VvI(mhdi6Hu$A2iid{rRZqd8(c> zydrj0{ra5q6<2G5c53E&Vvb&ZnOSUBEK^cxOktE?ad*OWLg+E| zAw5*{$;*+?qiX;kQ3YUrqPV7cQQjEGTKyXRfgFLEJhIp^Niu~MrOz(FTu)pNtY^ce z$VUVFlkCgtEZ!(gvTr8b=<}*|Ku9)G2hjyF1d%DGghbw2_CYqu*;y+Y#EJBpE?OZ4 zk>*Io7T|k^dAi*s+P?k0p`T7Cn!&W8lqxBOJVJ~~RBxlS)o+B438J#8~JkL6i*H`8)WHwVnB7gv0rG+<3MH^LfAVVz|>kYCfd(QnJ*aS9cdE z>7(=e~72{&({pLAGPDFdyTD1 zO-g)B)?>ySz@{^r@+R^c0rg&qzsSC8F^TrYEo&?pCkUMixd=I$L{%C(-EW=nozUE{ z+>pDWAyq~;26HWK@OTv2Ak3VQkgwaITcDf#$&*=_qnPb8$=dn>m|#!vB`BJTnHrjQ zYzTaj`65zT^W{fHK*i55x)Wc&Xim((mYN8ez)dv2_Itg6Q+3UDje4^Bc>duJtEot*dJ2>_+^yK*DFWjf`%5jB~LzBF>+r{~n7>(1YqgOPh z15mA^dJX)mQO6kSp7hkwiiZc1Wj=R&&wcsZ4%;HzqyqZ`jRT_tw*!w4s0gCxeCHel z4B0og+5Y)PMBV`j_=W zYZd+}HpJbP(GOSfn9HHFyJPAiGO{Fcuz19N{BxJ&=eo(CF8oP{7d*NRl?zFRD} zMBrVy7mz3}I%$FKI5}bH<45yrF?^1fj2DaFl`lq_KJA^V-S;a1%?pC1%GsBMD#l;S@TOvfrqs{+k8Ys9UTJNi9F z!W*L+U7w(Y8pK5|8U4khwTkf@_xZ9mbo& zQR`z$mSWb{dn0>uX`0hwxCt;MlBqkvwJ4F7JQ$|zv2sJ`zYt<=v-LT#hx)iz|9!C-~k}@(P zmMi8|jjVd*`wRY|duYKQh5OczsPZUp&f+AF<%{J+<(+IF_Nc`MJ*!P=2yr>FaieG~ zdBwP4Jum;|#W??V$|mRCKl~e-2TIFVf`)dFxp2y6-qzp!XQ)2Jzho>l_)&vd6YbDBA1*3uJ?r6HZ^?%rP|u?KhFz7q%c#8s^1cGQMJ0f0aO03a+103ci~g{=Vqz90Z#0}cQvWCH*!NOYUA($xmJgPs=T zDw%2ngmy5dbyq7?NNr1R0N@(qpN$BRi)OuAr0~&!-lJH$ev?s9VpM^7>}pR`2cl-; z|8;u~8u!rBD)uK{89()bS(>d?1G#x?tmN{=(co+Ba^B>$_8s!21X1QD`0|6og|9%R|0m$OS0KKB1;%WK zD|BUCfwausjtU9wY3p+Sv(v}biq~r5iex$dnM_(nDgfdK9meai)SONB*q!C91o^2~ zfOa)?t6ycrNv6dTIq*HDYE~@(`{r_!61FqJ(A96M`kM9Y748}JsrqG49;na4%JlP{ z-z4`o*&MAKY^5MQ1~tk?rC#fd@e64JwT96?6ATW#g(<6P@seq6QB&##f{&4~DYcG0 zW__M8{$QC;(rIlFg^dagXHLN`N_uLSS9jGu(($%1va4?m%B|`raP+a)yvP+v!MQ1T z#eh>LV6IIZ&{>!t4meILOKsQ{P;y?DbsKgUBNdH{+zTds{Q^GWyI$FmrShQMBX3noJy3^ ziH(=tlyYPr=o#cqgO9@V4;IS%dlg#px%a(M3%{nwLH$HEJ91K3EP*Sv+ z-A1|dYnHg)dS+-igHYh!9avy52=f~Cd3`G@DUVUT>zi=tbC`oDa9_K{K0rSSPTpv? zalzN(zvZl{8&CIPX_|e_p?9t}oeRUcg%2f2HIRCRJsv`?$bY)%k9PWEP&>r7A06JR zA$*!&{{7wQyUtoJ{Z{?{-;WgsgqaBEYWWMzW&@Zp-*|MlGS2sgvlxyPX99}sFwE|j zyE&I%=BX|e%P4`@pVjO-0v&Eh)2VY?bnRcr5qz?(kE^PPrBdVUjgAL6B@B0dqh~I> z+TPWf?bQx1fcu>ui>dpbWL>hqr8pg_!JJ|PU$lhS5D3c9*m?fj|4EepqqF~`-+!s# zes>Rp~n2 ztvbCHvR@gmailzB9?co9yu0KM;9RJdZwWg2A=qY?;cF(?X+zr4jjw6@ouMDBvm3)h z3&iG3@V)UUVs&DtA{sZ=)4#Eg3%3SKYQnPitb_t$9`-i%>GDLWQWD9M=$#;jdr~I& zbD`L?&x1+^Es2g&Ic%G5$7&OnoyWY<(HIBIj#ie@5NM^-P@a4Y!zTJ7& zzS(_oR%M5=^+i>|sGT(_u?EBFiWR=gF z+@u$LvYN5&8EZMi!UnQR=M=%9Gdmkj)8D=27}|rY%Sua+jD)C&PGp4cMK!; zNAXgn5XvPag2qSE!Cil5eFqb$JdM3vb@@nCDb|yWPjBnAC%R7gG{CU~1o9Jd>Rf5C zerk8baug}d5?W@A3|28JH=1su+VHYtf9&flfoP6*4O`AxS&5;uD!VV2PwkAnys-Jo zs%;yfprOm`-+Myjf6WjJ78Il}Om=;1a(n3m6BBo|YJ=*?5cZjtD@vctuakhh#UOco z<6?T{d%SX?l;XnWi{!?LSf`6jia~JW&0@m4bGrq$EBhSQgskK@stk0Z0tt(HouqbW z8ZD^cv(d!8w!B_*<1zYP{E`Mk&cHbVV zmGAP)o%_yGZCS~V063>(v$m}DDKwU-c-zxM$gy}-%56$LtM-oy9E5VrJxCPtNeg#% zQ}en|K-ntr+1P=yR&_d|eNJ(HkHsTdzvl#gyrjxW0n3UD(c=K)K;nfFT7wt#b%_%J zAH{_a|luLVp)AYQKe*1MAdDZqy){wz5eUKtLWz4P1xq^(qxW<0>({rts`f%1{Yjo3{ zuO+>w&zyHK43Z+Jo?=fF9Fk)1aI`+03$|RW|8a*S)1Ex{ybGC4eGujcf(~_-p4;38 zZ6D*69^mS>W^;t!H$eT#xH8!$H_*`Npks#ia!}?lCg=vwoP99*H#|3vdFOJ^MjbhN z*Z;%Yra6ov+|2)g(!gNxbIGts4dgwmp@E1Nv`@5~^v(yPqH9D?h@HY(RlW13e1gAk z?gmgU&1g7vR`efbd0afh4j?)MXZ6ZwUZ9%U-2L1Z+P%&@Y11Dcp-$NrhuTlG)f8H6 za`N!Ow|qR$Y(-pL_E%)@o@z+gTs!KsJGZQ2-C@ss6A%p#r`!H*e8E*!3QKV8rj(>J zJIFh=%7<-SxPk%EyJu7BS@Y+yv^6R{p?DEH{DaG%*{nTF^o?ly&_kZ5L8u|m3rZ&r z+S*+%m2CM^xe&JBXNKSK0}g)5)9nlX1ez$lO>zkpvNy#nOFgyUO?Q3pXJ2L)LVjko zVg~=s(S6rA>u%Tk%Ues`r_VIiYrm5?*MAoYo0GH~*ckpUl86A-)FAG?4wpbJHIB=q z7M}dv9)Wiri4=DCT+oPCJ)O%kg;iOwEe&N4{jzw_kKwJ^FxmhgF#&3VciTyxbq0`3 z?_PfPQzETTtO|Z>Lw87JMx=~aQrRWhb3FDAh2?yE-}EswEV8AAdPw5pIU8+PCu-uT zb7A;s>O6RAXSnYq+7J`0Jl%*mgLQMmz%bDD)B88VzfH>6 zASl4)J<$>^!w=_!&l@Rn3ubMISqGJZPsm34soyovpP-L10uEy5w9?a5v7oM}SO0MV O0G)dVkhkjai2njF1*NC} literal 0 HcmV?d00001 diff --git a/Mods/vcmi/Data/s/std.verm b/Mods/vcmi/Data/s/std.verm new file mode 100755 index 000000000..865997ce7 --- /dev/null +++ b/Mods/vcmi/Data/s/std.verm @@ -0,0 +1,40 @@ +VERM +; standard verm file, global engine things should be put here + +!?PI; +; example 1 --- Hello World +![print ^Hello world!^] + +; example 2 --- simple arithmetics +![defun add [x y] [+ x y]] +![print [add 2 3]] + +; example 3 --- semantic macros +![defmacro do-n-times [times body] + `[progn + [setq do-counter 0] + [setq do-max ,times] + [do [< do-counter do-max] + [progn + [setq do-counter [+ do-counter 1]] + ,body + ] + ] + ] +] +![do-n-times 4 [print ^tekst\n^]] + + +; example 4 --- conditional expression +![if [> 2 1] [print ^Wieksze^] [print ^Mniejsze^]] + +; example 5 --- lambda expressions +![[lambda [x y] [if [> x y] [print ^wieksze^] [print ^mniejsze^]]] 2 3] + +; example 6 --- resursion +![defun factorial [n] + [if [= n 0] 1 + [* n [factorial [- n 1]]] + ] +] +![print [factorial 8]] \ No newline at end of file diff --git a/Mods/vcmi/Data/s/testy.erm b/Mods/vcmi/Data/s/testy.erm new file mode 100755 index 000000000..082c1891e --- /dev/null +++ b/Mods/vcmi/Data/s/testy.erm @@ -0,0 +1,14 @@ +ZVSE +!?PI; + !!VRv2777:S4; + !!DO1/0/5/1&v2777<>1:P0; + +!?FU1; + !!VRv2778:Sx16%2; + !!IF&x16>3:M^Hello world number %X16! To duza liczba^; + !!IF&v2778==0&x16<=3:M^Hello world number %X16! To mala parzysta liczba^; + !!IF&v2778==1&x16<=3:M^Hello world number %X16! To mala nieparzysta liczba^; + +!?PI; + !!VRz10:S^Composed hello ^; + !!IF:M^%Z10%%world%%, v2777=%V2777, v2778=%V2778!^; \ No newline at end of file diff --git a/Mods/vcmi/Maps/VCMI_Tests_2011b.h3m b/Mods/vcmi/Maps/VCMI_Tests_2011b.h3m new file mode 100755 index 0000000000000000000000000000000000000000..6039eace4ab4718fd7640170f8dabaf2a21b0da5 GIT binary patch literal 18793 zcmV)kK%l=LiwFP!000003(b8Cd>lzyU-gVM56PD0hb+mGHDh_T>pkCkHF{gN?2+Ub zu*>f4F1vdh$RW$}gOw%UNVeAyxCIV4Zh<`>Nk||$5|WS$Ngz)W61WR_9fX7cfrJEd z7xE$``?F~K$azoX&*s!u)j z^6ImnQ+;V;XLqMMHCd}&f9k2~czCC&x%yP~=60jHb8lmFbA4@R_s-U>zyyIYd*Pos zxo7OJ82**)#en_aN&7!LlldI`zi+Vr`-uIYfQ8_X3z!|@ak!!Ff!qr&OFb9<4cm*$ z8J(lKue#2KsO}lr6x|B!;?3TkIq-X*`rRMc+uM8J@9f#%Z|{A^bo6^O?EIO%y-$5$ zPk)up;Ql}Pm5xhMg<@2S0V$3_&k?8%GjMGjl%m`IKM<_cWxtn8U2^ZJ6tZWfG0!4! z%rU4Pg`NX2<}j>!l)()`&mm|Wge}0@01S;Q8)H3>!6HW4;)eiS?;(zkLovo``0NAF zWe5fvgf6%RN1?F_jkupj;rVnT#CwGBGr@L_1g zRY#Z(gwYdz?}naL7;F^4aS>HmE-qpOdLr>5Zz7`)z?cetNQy|8!%?ttpDltI54d$GIFYinKt|V{H~?FKkAlm^m1&W;MLx#6@_PUP^x1K* zX%_@9dG66JzKQyyRp%dzR-Lb~VpNLbP>ix-4B!v|#~g#gjgs$@qMQ^VKt6arKOp;A z1dO5RLWCo8>Aj;+Xk#RkgWQp55jj~)PF9vX4n+m-RRG|$Emq7SDaz6~X^Yhu0LL6- zV~#?X$@E}i=haOQ)OtM=;n3ljLEjhKp84EL-WKK6{Nqy0ghk2ZSEQ)hMw6eD;<7Z( zNbQ^y)6lpi#eyrsds9+el0pZ&Dz%y$MFILvepZSrQdsc(WPSwjJ`Cuu05*{djzJex z3}b-zA=#eks4;g*ia9Axq_DCgSXq&6U5OhfQ+QYrtZ3tM3@`?8T01FSW?{awQmE-5 z&kz?Pz0GL4$Q1!j2b)R84En0M!{J6AFu8ua*C+Gc76H`=0bz08HcAoJrlfXG5HRg} zmXl&Z8t0@oBehwnDY)KK@M(ZsaubEcB{xdH&_r%Be<50Yepv9Xb~5txaX_)&L$iV! z$O=#typITOb+Z)xQ&K3^`PwOnHK_! z?`d%XYO;g*OVC({8s4)s(Hrr*tqBL@BW_IWCA3k{4?w)5C5KX98)ZXs5f#*9Oe@}% z3XZ|-=tiIwi$>H@woYA=iWwz0*=vjNd{DI*%kPi4IKMBjC=P2D{UKY}Xo9VsviF1- z_D4t!@7cwNjdsa_Pr6nt7x@Equ$)+yDu=8WE^Qi672u6*L=on=PP)REf7S~YApgD>DEzXCQ zIJy{JFs)E|OlNf|L=qW+Gcsl@?&aBY{Zd3|%*V?;EQMHd(j^~JNM6V$-=7!386#ZL zco}Nvh5r-}%72PR<WvtaANL!dNjjC30Tzpr}$tSAYemsYpeiTPcgL5LwCOhx#FEmlintVb=2v{wOmsJ!mE9MoA#zc+FK~&U(J}IP@m#|M9my(D}mxNQgBpM^N zm2PS5mY&@b??7$S*62?};JGN^wkqK_jlSlk$i?XIm&Ggkh4f>) z4$Uo#s)7{zBZkcv`qLj5Nl%+LGA$bN>enR z(R!j5AfAsY-o=kA%HoR{GSQtEl^n<-;zEWNM>BbLks)2urAr>AOB%bSv0HjdEhq>c z3bF+SRqU$DBW6xa_a~!u#t2sqSA^qXhN8b;22=El@XyO4QXaJRq%=n6EGvr@mAi@# z^{!EDt4KgWZOJAlks4<~tN{45(AlienI*SiGB*9DQ`?$oSb7YILHjBcLks}?xMIYx zD@6&v6;)G8+$~m^5o@mok%q0uMMQP=d{nGR#RKH{4vY0TDzPynig$; z74B){ax$1zAuEFZX_#S7X27rkx|V2B;}IeHPH4hmEDulalzR5dzF;&>qi`iw^s+8k zE3NhW6;c>S7 zU5|~+gR&Efe&O4uBn}DRD2Iz;SEaG4(Vx6*WD_2o_%*`eDKAUAsCSJhR;53cz@4xgR-P5iT4aBOLwrES0Q9%8yfSRQAPCykheL+(Qo6E-Jm1 zUR1v$5uwgeymztfK~_Vr4n2LWp;E-eB|Pgv`H9R$6MJav9>olX`w(0iR`C}-bUn82 zHEb7kY36B`|6p~HhXMSx;j&3e_$DV5xt9HCj>>}y6b;#NnQgHg1GA|Vy8bCDqz zE;5dKXoMV7zmBp-L;$L5wDM@?kuRNv<^|E7zI+>cPbDdfOLCs7>I!&pnvMw4VLGwrVQ<-ZB z3f)iLYY471?L216Xyap2;IXQXqPQJqaF}Q_2tDDDERp<}#bS_7V;;30X4+xS?# zdoo6<%JO*tpK69n)n;Kntm%m#dVJ_wVw@JuEwu7bYhzJ`E^rRp8sI~Fz z1*35Ecm{K7bXpgavv_ys{l+v?f#MxcLQzknfrXwF9?PM5fZ@!8uytDWLQ#TyWd=}U z_b6O11L$Q1ogrFo5}(KDj}}o}F_0as$6Jnunc@h=y<$Z%rn0~tQhzCFOhpKk6j1~rztdL;@OT?fVlY;M zE)?4O^jHTE{?KwnzY)Rlo-dG$L!$ztuWMik3d0l_Jy5*Mxl_|&w<16heidrxfg91g z#w;4-cRYVbvmTFe^~eq-3Mvidca781*PbxJ3S-o`pBT0ECB@M9q^fX_=6F)~A8C+A ziD@o45;U#_$&Dr;X(FxiyV`XaL|3N7bh#R6Uz%WrG|`e&kt%2=p8D@~7;_osL;oGq zB=PKJ#M1?Fi|9w5#_UXtp5sewhzE?i<4a`-hY>*}TAHp?h3CVJ0z8;T3s>w`iFDT` zYc+YfAxe1{6zl5@dhota?0LopGFsEUMh-&e#>e%4=#ScjSxC~%RrF96=p zF=j^LzXbS0$uP_A%>tbEj10OcwL9Pae)-nGihPxzxsa^RI5$*lqtJL9YNOEeIIGdd zF*bCCjTzx@HJ0<#cBAj4RoFdDtKUSnlZ$UJv&G{#lfQQoaCF%)lj3O(T2hw z^}F)B%K0IY^K-C+7d+yZTK9_SOK|TT+`}1G0sJELobiy<0$UPQ8j5#(wGbmbG%W?Y zVB8zygV+<3UNM0T^=%dIq0fcBHcDvBrl`Vv7)GnGF7V!jedpEUy~gDH6vG&zUk$;t z9@0RmagH^j(x)N;Rmcn|A~Zae-$w-R1=s>SwZz~w4G3fC1FdE>n=l$W#1KcnO+oWp zk+P5`5S)f@P%_|S79HM;^(GwQMk3pz3x#`)V*4`4YvjgD!1h!zppQ4lde)$rg?q;U z?|9-+1vJu6Mj*dqstY~eL@L1Za6J4%KO95jD7I0QRM~QoLQudDz$lQd9LxCyz!7!a zXP`I-wJU&tbBz0EfkF^RGcfxKn;l2diRw%s%{e(=d{0wKY848ann*Jg>8B>>vEv}? ziS{*;6dp;^hzqV0XGifwmy48zavmA)GT;cs_GPG1kw9ZJvw$j;<&=xjw@331xqprc z7S-=X*g+J4C{W@orp^=geo}|$)*3mEwq8~|v=Hj3qH^UeR5qA;T z5E-4?>@*5D?Liz0cqA!Wqi)~=;JpjBl^)=s7R5tI`g#a`kAc3Vjsl*(kB&!@zIS5i z7j3ElfMSit=iPS?Qu-ayL1Uvco_kdMqS1y-iPBrUTxDpSV>LVhz$G0MIbUEKf`72PDn80uThNXDLsVw45+=oF_#vk=)Hv5ACti!vL^C)R41JQTOkWvIgL80JORUSPQONfh)+ zG){~5JpC*L;(_|psBh`F22iq7dk~{PNM~sB(|04PtcHOl)aX@M8QSwI=kW*|d6Q;c zA@{E^Mn~@&t$RuuC~c--XnL%Jo+9R19Rq%+srl4|!~Mj2sPxggr`W8*J$gt^KOcjN z9j#G1dzE#e>dbvG)Z%@GiBYtyE_t*zjh-XD;2ABBLXTMx0Ov#De-)OiHH^>WCR0nTdCv4_>lp83jbupU;FJqwH;?D-}+Uxhux z8pc2Ar=8Hnr!iHu5@@&xV|moNpa%%_=nd@<3@;vKT?FsNXnR(R(I%U?=#iofwO&^E zdcNaP=>hXp-~CSgrHdx~L6JZAB{l(W~>t;HjFRCZo^Q8)iNAJEan* zEs9Vrir|cET8M}(iV08@5nB{-UliF|6dh0$%c>}vxi|;+YHVn1JPTu9f-X9rD28Gk z*0KQe={>aTFeHom7V&_dK22goY@+T*0gvJr184LFH(Vf%(9yIF)IcbSs?byUUHtcW zcNOm!00HxWoN3r{-1%uw<2*z7H1wPYI-3GC));sVdR~ITRKH_NDz$gfNke-HRV2D> zD1MQBX@U+)Z!|8E3TSu{ser!x<_j?BU`4 zgk^i`F&gW@hSPwmW!QNX_tUI43jkD;oP{pu*t9dAG3S5==V6@~r==%bGzk?Y19A>B zP!$HFAKgIYAoXDQ6nUEF`XMb<;Th;Cl;2g(%lk6%4pOx{2V1VSC8n+~LC-6IF%%H9 zaPKl>`+3kgQ%nQZJj8H};e7!1WsEh_4{}hNpa*zWhG|qNzN2<@x#>4a&?w z35Oer`-$HnK(UQ}05$!P2GIhrgbKbx%ESnu?5OJyHQpXr^I$l)}^mN1jHFfC8e*r~pk4 zG%i)n4}+XX8yW??YTt`M8YpmQKo}!2pm~78V+w}WQC0z<&dBB>)KEVt-tpB!^xx@u z2hDG#+>Z!Ap2njv^#7325phTpNCl$|4s}iyWdfszy7O=U(8qWi1pvQih5tzx^$vzh41> zS(pzE0*n?eFmSZ%aCfP9hQb(;Qw4Ag<)X8N1|7}2z=^61j%H|3cO0K+ly$)qTea&# zD#)Ab_TLrCm#-I(OJQpzsg;t(iWFngI0pAdTY8p~#xZC-4n?J9lrq3!Pj?TOi=$`} z?ovM3Y09rd?Msu+t5BdyM#=7TxzX{X(O$$6Cdbn_t~w+jMRfU>CHZ&0knw2N6-|7hnKCr*0u5`thY=n$x~X?Y zV{#aj8Dij7XvAm7MWFqHp?{xoLf;)gWrGW(97n;?Bccb|DAwrbf9RnZJ!PY>kmDxP zmm<)0Mm*4O(V*>yt~xR^ePtHC5E?_GlttmHFazQUH;X2hVHXa{8*6&$Ag} zT__1xVP&`$e0KVRDLt#hO{TtYKmK$jxS-UXvzqk$sFHl&N~1kO-kPlP*x&u8FXHw;FL zJoLm}!X+61z*Tp%Y4Q0eI6hbo#>9IDx&*9-GZ+|*ZX}W$g5y10HJuj88>cO@q49|f z6gUy3a^##Yb`QbnV2B60GDLp>6H&w=Gb1q=XvCezwcx4^yN8&@C!%}pV`c=$r!6vY zyob{k7yt?gyr%_nI8q(f5P%XG7D%)OPmL}RM?nTc7DaFrEGRBe1R)X8?ZLf9j3K6R zz5=Tu{%|cF$PnBY1Eb*f;F!n{2!LCFNX1oCb%x8up^Kg_h$ z5X8YKR*<}ltU$J>rWjTFlr-qJq7KM2a03ANU^xaZ-fmgk-11RRG01uzgTV}}6Q7n+ z4;CoT&^(~J7#Y?l1aL6gg#zHVQpN7`vV4oDstzgAKqGQL^>gU8T|BI9& z%k`g)EC_f(zTIA!mm;Ex1n<<~p$-7G&!}5XNdt#AP|#qN0RZZYVnH1$N(D5kY1lm- zCECxrXv(^1%Fn(S^whAC-s_7BS#9zUMQRc0faOB;w7G)((?f-_6i>^e?3V(S{yR1n zsOivop<#2x7I-s3d{pNjiKt3}DdBh=izldi&alsvph4MA#BXk#xcN>G#mF52sSjCU$&yIG@Q0KOoQ&ll}t z1vNkr@0jjUg#wQw?H z!qEUNvOU^-igy8sQA}}*h{IG61i(ZhJgr6)(-Ux%A<@bLK~+7*w6d?_RuO&$YKZqK zD8vv9WD8qtXB>FNaeJc|s15GcX)I*cp74Oj^>ctaLqbTp3sOwzuq;yfqt~@Q^qV#idp*h9mwzWrGKi! z>1UW|;$W4Hf^U}kKH!4<9*q)|#X2mjT_=MzDR%5B%q-MniZW&{+Jfk-8Mf$!nieG} zdRQT9KBrEF;vIc-8a<~e9X=0?Qh|YjhDB)XSNR?7I?OJj8NyUDAfeC%Im`_AeNO7rEV1PF2TYW0$1+K}it4bG@^NM%vsRo?_oRFR}W>>TYIl*62l%ZCF zMy>TiO$+4r4vscxP>tFJhIOH_UjyJYtgMZw-|1^zD4J;|a|f@G<9kc^{tAkFBovI> zsMeMTjO{w1m)LVtMpsJ!EkM=pB15`mv$|!lZeic<5)|}aFYAf?P7@O7sUFqL9UR#E zR0utW$3ppC{W$zg17&Uc?#CFb(f9wb5e-N*9aLN(zbiJ?T0^@|o%33%vDpFyd%jzO z{esk-pg1W?Q0s-Bz0gGqwC9Z!cyvSa%W2w;VFk@IHUN%2k>63xqaTNOpscM0O6UQ0 z58n->0s>VzeH9$}Jp#|gW*S;+SEZ)f{-pFo3-E;Wlri<(uh0ucnboxD@c<~lqaR0e z{pg9Ufr6fE<6tz2m_~GI_LSmXRGpltL7SVF^#fT!9A^seBmc)^fd^Y zXoI?#%4@@FH1_KoljgfoLy+b*qpjgP6!c}H)8r3&L_xzMXhYL=(vuMbL(??L4)Q$?5l_Seg?x)TO6(#gg5NniIP#eEPASk+e71gRMz1Wbu)0P z4JoF5f((VYVKtwepuB0IC-q@{8i?B09rz?j47e{eB#ZlESWlXCPR~>6DGdV9=mF(8 z>Jd>dlCl#uc&PB8G>>q;ATu>w=m`}y4-AY#ZAC<}Ph3!I)TeXu48Z5oP>D%39_|Yj zYk3AvvxIQf^xT)4MkuVPJfj(B^l*W)nlB%PqROz@=_XUdj2?yg3~SsFs&jlU6tcZf z5>Y|z+iRM-=Q{!Li9^(eRC*f#Kmwx*g^EhVn9r>DDK{#;eX$F};HYatNy@NCp?)fI zKUD?zn7)UmluRiL5l8p4z`9UbM@=!GNo-(DUkrlECAzW>;u@51sXgHfvC*K6fx;J1 z#AnAXpjw7XBAkJS9%-DG2IHv4qrB-mA*L+q^Z%%YOXUQ0ntVnS4HQttK$S2RmDJj% zI)Ea8syb@VcYux#_KcbpRQpoqqRdRsop4&J)o5&uK8o+0*>^C8B!!QHHAI!q6+)uL z9i(!XMrLuZJK$*pV^W5t0a{DED@+^4H3LF1lL8A4-nRIuRCJ`0zg4j|qs z-%_iKdV|#MG|+{*G`PtI3Sa1-M%rlb0T)3{71~JSQdI8xBKcHo`;0x@R(km53u{r6 zgFXYbE~q6yjdcSH^cgi&15v{T9}}O5ZUGIFbP&;{^kO_Kqai&y7_~!uqRSV^!X5O* z?kUD77yAz4a9ZE_Ce{5^q*J?x#$RY`o2q$V=!05$$guil8sFDvd@q~RE}eharUQTu zYS@VHvHMt*Dh(R_q22_If>YztXHw-D01d9tz&zzfUm#0+%9m***T_f$l4<$~jTU39 zu>$vKtpjkJme$4?05(Q=Z+_3s#{racs8;hG>QXuHvpDI{9l%jlj^;A;^eJ7-%fkiz z7>T^jP&g}vSAA9$`>U@jfbno#Z%jx9*N)+DZlH-hja%OIp?I9fkN?s zI9h-%isDrmYzD^Ei58*DG>o|d_m)^sT$1Kr)A!t{eNA0U>hbyPc@)3&4260ORPFjK z)gmisw3piDNczg}ig(@Cx)h2(MGk(6N^3P}oQK*q7!!pD@-#MT4QZ(cjq_{~hb#D|La9 zP!R9aQ0TTQIurrOYASva9diJOvH%;edEk~|XcR0f(0D-#s^1YShz=U&^?7wvvQyLD zcdBk!4N+_W+~+|Xn1L!O8oX8gE^p5?>CHJP^d9n`7V}V`c}EeB0v=ggyPSm?uCOks z?J2)g))6wGR!+(*%&-DZXMka8cFm8BI#g098ulj8`?;LbVj|i0_*8J4WtiE zT^b}>+!spMG`o+6plBoYLeOxb2gUe&ba#DcHq<^dtP$5jt+XBQbO&9PTSJIPEpY7%g!}v@;!{G4w zsJ5pP)5ov|)=9SjXP`QOCS3S3&S*S~azB-(^z_3paC-Vry&eRoN725W_Z{?8G$OH6 zCz~FnP(1h|iAZ(0KpMWGqKon^#XD6txLhoJ*#^Gk2)Z&F`NHino_*=uRe5Vd7 z)rM5s;7(9elOE@F5XGT-lTtyR6*x2{T6)GuwG4IfsowM*FjHQ^F{v9t!{0b2O-7^= z(?F4DT@cgs9JT{>O%F(@Ura-R)K}|b_qy3Abe&YMP@4}`7S4{lL8Bt zw3PIHR=V$8hKfq6bEs2D;~KchG!@ckL{WbCHBzsfCJRyfntF;c3DSDpci_jTXP?NX=$w#Ba)Wx9Q3l+xrv@}dXx4^KSpgM>8qP_zXDnon} zLfvZI7wVGw{3?2^L+A7D61BFe4Dp@PAW}Oxy!4$h1gxO6+kurp_k}wA^cawe1OqjS z2O9FCJJ^B$N4cMF7L8p{dzZ4Wj{|57f(E&;ppGUbc4~i8i;T`-pphPBeztLTU$~ay z&v!&Y{Kd-rVdvfM|8_A3xk8ad0+_;f{4?Lf-)|b|A@9efdWw2*9 zv|o1yK%|ds-k6@|A~oZoNSu17mZAmrVA~zPfY!Q3@v&_nfa}&Z+yeki9=8 zx<3jxnGI(s+`b5|v9@(%`!0jL;)M*KakI8_o9^qYna>zQPGvy8w=Lv!2IOm*ki8M) z^5o>%1%^sXHkN3+GNxQzU)#whb^B{2b%kuK-OYv!e2{mxZe~N~eUMul>)DW9KFAyQ zx3VF-eULl5jcmw*5AvoDvX}|E_F()3eol8NOOU*@bAR(@ zCS=|Rd3U3+o(UQFAa8BlWvktbAiF7~5d2Hqjg8DHbG|7bZf_HPqL9YB*zL`1$O45_ z>2)?+dRd*AC4*kMcjt{e8*F>5gnnSMB=p*Y&9(cxjpntQy`bBCT?+)xX|?8!7OiQY zvYpm^W_@=m1$ogyK4=5EdTXswYt<*=bDn6kS_?Uq3E9dd4)WFA?Yq~TR~zl^W}7)* zy}rG+JJpQOaI(NMW-CTgkPmM+oUX1!tNrw+E#!8a9eQPB^Dd`%O9}6%^IFK)UfkM# zI5}Bs#b?Q!%6v{swXaRjE<88aLbV~>&360qaC2vithHonUdY#8ymx!+joNe*PNF&O z<$elj#ldUOUH_8FnRzzl?`yZ%ovn<;wuQX)C=HpAUdg=F2J+d54_hIlIq$ZE+(|=T zJmBV>Z3nsCNI_OA2pJJi2I)O}YdhmPEo8wr=dBIToZY@T*BhQWTWy&X=XOp>%k7>UwYxu?cyW98TIS+jG+q&9NDGX=M8QCKbUVZNR=JRZ^XZ?UL zzq)q!#Vtm!KX1fF%zDaaAKp##NJ>E8%bYS+U|X+qrD@;3^D4~{a-Pg7kUm*pA!n$% zwUF7|C6Ketkl~ykk05=rz(URuNSU+MSVPF^%+-2Td~oRj||j$Uo7ynMGdp9Oia z9ppk5sutA6Iz#6^?8L*sBUi)$YOhSyWU9Sq-jpCiodmyf~+(_dd-Sk+nzbSrp$K3 zGpE;l+F5&egP3eVdy_50ASt#nNFoFCcC(*hX>Xvdf%)S4=7Sctj#k^(4)SJb%n)@w z(8iPrAsctLm_QiF;7kYk(%nbtsAK;HhFg!eHt1^GISjAfzO%`MZD3BegWPRwyiOE$x5tZf2F+FGaAq4cv1Zs#BPCgE zw^?roxwCtHlR!?G-`@`M;YK>tpCZ1FT+S&*d>6G(24rM`*J@2$8-+WvE%21>KHId~ z4j|VaY`$@0H#KL)LHaU*(-La*%JZtzGsw$cbfpX{VJ@a_ueK zP&MojVVmnjH8gG#$m<=chU_`psfKJwziN1xf^@1O*H$%zqT~kia&sP0;@JB0TCus( zo~^fc?(8&6%um=U7rc-vF%zPFTb!zDY$|5k2+Ev9Gpt0AJ-*fMyiT3RadWiYbFSZ{ zkSiI&AU)?+#++?9<-xPd*Pp%Gip?&M`IWHP)dmCXfW;m$0_tkdV(X1t%!cwo%3_~Q zhwLj&fbC4ETZB)Jc=*Io`zx&&>1s>u)(&DeNmpAt$m(il_jh@8wToerGrX?0k;rE^ zH`bUJS29nuTkQJHjOb@Ju`A`>+ew8jQd9myyTxXB4EH+m7b=d)mc!9EE#&z&k7-5FeL_L?PH1oEdM0E$L2{iCLcJqG*d&DTiO)Lp@QJpehmeHs z-6t}r9v$CtQIeqZnFlQg48fZobUGG$Z!0$6Y|morjR)K0Dc|fwp=>@NIC--Zh2ojB z4IOyqIt+P#=9I}%+DsN)_EA~{H^nyR@iv?D z;+-4YJV)^H%#|wStuMX4O(Aczf5uuCoOi8jZ`|Q2K|I25e~xzM7~pGdrhIjC_cojI zyI z$esI*R;gtnUv3Ba5TdN9)qX(-kW(!?)CuG>uW#N<#YPgjZh|k5J7(o+g^*L99b{wU zW-{l+%10gKpXdNGne(HSi|+*T_30K5BZBy*6E^LW1^%Rr2yUi%rGB{hxOsQCRkB;ibL}A4*Kcz^d7&L-DD)l@p=Sb~p_PW* zXxwcve(anxUdY#;-LR+4iSVktQ%IVv*9LO6vGTcFwXByoon(yKT_$W}gyA-!>fyIV7NG9mpA#@6heR_aqo zZ#u}`t+{MSZ#u}`t@&(7Z`#YAsezCC1gWpQ^IR6J-^-9ooqfXte2Ftvnk=V zt81YwajAYEh)qS)E<1Yd!5i0a?M}AHA-lK#%nKPl=j$7fCR^+h3&~Gi9As8}_szEP z-50|LT_X?fbhajg<1mCfY&nY;0VokT05cG1HBWMkZvt zIUi+0`cBg9jIGr7Y14kr7w>P~Nvz_x48vBKRfz)@;ig2=#ce?c`TBWIOql z4cShUWkY(Ov$47Ph{}>Wg@QS!xK^=qwy{j6?qx!9?$mVUT6XrGoiQu>?zN2vTl5Xi z;8^BUO8oMr)wKu7l+BsdzU_Ht`3qipAyEt$6L+lrOFpk|JleQRmF|caa_vE5{nke6 z4TR)~V9)#u|6@ks2NIr|;Pw*e&E8YDdF z10?US2w>3g<_JigEdF!LGT)8-j78Ts>kfQ_vil#wrM zHym`bppY6dzrD73Gb>{L;cUPMuUuPgkWhP91eo`&b?0Ft zd#y#!T5YU7Q)GRa@iSK@S6-!nvewm*@c3Km&uYI`LrdSsnA+G(PR^Q-Fs`)#xhbzc zTuWsOxU*h?eA)x~%FeyUo!u;4J~Td*>~1&iW3uWqX__=#fvdp=_#H5b2L z!&7TgkkeU^2iif-WI^&*iBgcYEXeG4XhlC~niBTSxmF7LzK}F}3U23q=Gxs4+A=sd z^gNx({j56}f+ptl<1@ehv55ZzOKzon)Mc*~E+o)o9p%(@spuS^K;4mpUCyQX{;&JC zpFM0>YL{Gg4vU?lw=eppLiGE|@DaMN#_K5LgM|n~%CeSI!?^{LPOMH4YqG~VHS z;J%H){^)oI`&`&%^V}yV0^29ZN9!x*kL808h0i__{_PLPcohD9ZTNRT{F}C829Ftw z&^M$1%kLHQUh}efHE9Xw-OHH!NNVo8;oP~s)ZV-ij(;NjtA>B?5C3*`-bc~AcH|YE z<8oBi!WB)W*5R3VFg5R1IBz~Z?+3#1?}dL~8U8iGzb_B}cEZ0e%ihCdsqwbM@w%cu z!NU;Z0}5$J`Dz{UE3-!`rAE3Jj#P;I(B3^IN8?}k$?v@rJRUzsPv#7Ry)N;|-X89C zDHpar((4%6){ph3#8A(WlPq7q@zRe*k7=@mM)I@c>>qNyJ{*Job~(de2GIT?*Xym+ zto-dm06je9&b+_Df!4Wy51>bf+yvj~7Tk&K86I*+INy|7a0j5HL*__ae>)ADofi{> zR))+#T>nTKwAvPQe8}ale`^Xk=1uRiEoiYP^}^vtzgGH}ddyHuW4R~wrgPG>*Ae_q z!a634;CqaVc*H~W^rj*U$z;8$H@xD;LXSD#+E{XpfhG0N-`=h6Y*%lt?d+a8b0(6h zi8N{Yi(nMCP&Tbu0R7$W6}6%DZ~jnZx&)S*`||zyz>aY~HU5&ZUVzj^#^LXkNV2}y zq}!sc3W7K76xG%(Ge`Hk_s$kS{M1Tpj8qDam!}{5sh`iA;H^l2W<0888!~+QXSg@7sDUyahXm7y)G?&;E=mM)a{Ib`p16! zyYrEA8CY~@{bg`KCG~BU-x~eAZwVBe<4w>0mieXpTjoQD-!ccD$b9xwMfXnhyY{(^ zq$dx!gWU@ScPRLDvGu5UW}i9J+;*Yg8QSkU>=JxWkGgl}>>?*e%~ZO6anwww>x-ji zCS9K$b=jrS{b4f)@WbxqOk2M&;a(uRaMI;$UOef(^Dx_Il5~de3+=d2h6dkd86)D- zAb&I$95SzkH2uj?4>m%b{_`PGOht9u9&>5gv~sKc6`~!V4fi=8+LPU(ZCDERYhTEE z2SbrN9EyxeNX+jJ_wea(Z>GYvoDb1|!CW+#%;%VA%;$%5TsP~X)$?X(H+)rSLwwMD z$b5b1T7E;s_hGZJWsvXAw{X#g{Vwfzq0eQxedq4)_2(mD91I&PQ-g4Utzs^N2B-Vo zEBqsU_)`9S$w`T&LrH+aTpUcI6y_^z{YVn-Fi&?SF%whkN&+wD-CfC-^vrZu@=ZoF z+vPmKvt8jf#dCL=&rP1ZD~kF=zj{}=k@`E=mAIbfJ2rpq^xl_hg+A%ILWltUEgnd| z;uUpWIAESn*BAG>uOdeG_n8;c^|^iSJj~W7itdHFul?W;2O&a3bXcFG81&@(@&~O6 zd^ALSZ@9zVL~wF~Yf=p%VG7xNO=!4(T}U=>r3mMXbJf;oHY59-hw%%chhaWqjDCMA zUkv*42SUwtFgOwnR=zxK zUN@Vejr(tu(MSJUFM>;bcHreqX8UDeKbgO6SS-0E4OfTnMT_#EN zd!pbh()WGj=Ux`gTgpECc8e=jOp>E$Ik%_czQk$o@2j}4R@!=B#eIu1vfeB13zN2f zsNyoUZGE6(zA#-MtRzXJVoX=uE7~I!_q~bHiuvOBdaPpB;_GT9`Mjh#QE~5xJXUde z#XaN6w@*#)xO-py@rpYbD~!8S=>6mFWt;=!?&QCJJS1HG9U6CEcRM`pzJ?ICCu!nG z#?5x>I{BE+z_`onw(qdkc6O_c?T6K;s+aCGc6+9_ZltQ!)72N(wr=!HH{w=X4O_1^ z)^>J!W_E9Dv(#sky8T{T&m2_kSP$3k-s_on&9)x4zp#D(*6p4J+isUNG;SL45-k}V z8%Hu29S^UikAr|RS5?$dpdI2?~cY)O6pgn1)D#Y89u_180j!X&Av0~67sM8BgGQJ^mRS$nRt-L<`kC#)=WJ2Ns7 ziriHF_yq1$CG}!_WZF)I;yG16IblASuJ@h_WooKkI2FoQ{p~;1v>T@!vm89-&T0>x za?En%lw+2oryR2ko^s4`?381c(Nm6Dj-Lu;r2Z=g zE*o?j?T!Pd(PTV$8coW>r_tmbIE^Oh;Au2jkDW%7cJwrwpkt?a-n%EqlbTU&RwZbf$@ zR!{BR{xi<9J95T+WBN&so(WZbsy=kaeS$99i8GO*;CAAS`Q~)Jat6ux#2MVFlV=?1 zPMmS1QmC14NuIW5lKkqPnj`6bHAm8YHAm71YL282)f`D5sX3B9T65%jWYTp8cZ6{_-lAK?dPf7QA+_8Q0=1->Uee<|u{qyF# z)Ad91=1-;T1M}ujr|X0B=Fg<-!}I2QlHHhhT4HqGDdY+F)aggWhks`6K}3PodGlw} z4?QvOwz#lpK9)S_qWN?1po`{v;XxP8pN9usG=CvoKfH(!Hn4~f_T-}Zi*fzvqWQk~ zdT`Nve|-J6doa8Fo|kTKRIRD;;=S9Cw%(|2Z&i20mRHuoN3P!4sjjW>-e23?d=#~A zZ-p0f>$ymNR3F~Hvwpj}zI}i5M)mgEgN^F-jg779z0I}tjT`Y;oAw;K=gRix=Jvy# z#p?a7r#A0wy>9P|42s0p6R9m5S~Pztz8;uyI%asr{N?!i;2HB*(pz3xG=DX&fBZ9h z2dazauchk~i{`J#^@+3Q2a@aa=5Hj|OXhDT*XPU+#@Ay@7(G9}gwgZSC5)brESbNR zt{+=6e>+_tUNS!v*QfWHA5PyJE18eS^h5;+=AB!&qqyd1C>qB?)4dux#3w=}xf{xi`=Mg_um;!aD+eNh znalb7rojGkK@fU;<=nn-$o;8E_ftom0y}jK6ZxJTG(QqwpFHS7W%-KvyGi?q`Frto zb=3U*_&RsO{Db8BxcP_i_1LKSWPE*W)cj~N<`MH_$#vfRqXf2KemuD@n}3{K?=$}- zz8*+@|EM?h6{W#Z^OL6OH!x~`Dp~sh^V9Kl@vuvZ&JVe?&)iA#v&r>{`R7TW11{t3 zV!`}eGUt-{m+|${CG)S6d6%LWN~7PACHI+(!%OC0$JYm!%%|e(154)9@%8>C^KX*p zTQa{GU-vATe;Z$ym(0J5uS-kj-^bU5CG#JWK6UdSlX2?imy+@8=0C;P^%LfobUZ;Rg;_yoLn`z_!Wq zY&`{APr=q5Y`8y3H20Uk=vRJA{#^zeuE_<3pcsr;sP(XQ-SmWiIW5P-zsQS=B1dZ( zF=-d^%3zlMZqpUd9;E_UebLa;fxJk zxX}&;N8JENfa*mp6KVA+VkK21w z?KAs>C*t;=RQufi;8fhcKh<8|7d)A)zdRhgE2*6B3r@!sOFI!aWSmCYdyn@8iNkQF zFG!5H<9$J*?ne#cruuduV?#%nGRA9{@?umH&ZhvXcCD50NAu$cK`qY literal 0 HcmV?d00001 diff --git a/config/terrains.json b/config/terrains.json index 3b10c56d2..816e8ecb4 100644 --- a/config/terrains.json +++ b/config/terrains.json @@ -9,7 +9,8 @@ "code" : "dt", "river" : "rm", "battleFields" : ["dirt_birches", "dirt_hills", "dirt_pines"], - "terrainViewPatterns" : "dirt" + "terrainViewPatterns" : "dirt", + "horseSoundId" : 0 }, "sand" : { @@ -22,7 +23,8 @@ "river" : "rm", "battleFields" : ["sand_mesas"], "transitionRequired" : true, - "terrainViewPatterns" : "sand" + "terrainViewPatterns" : "sand", + "horseSoundId" : 1 }, "grass" : { @@ -33,7 +35,8 @@ "tiles" : "GRASTL", "code" : "gr", "river" : "rw", - "battleFields" : ["grass_hills", "grass_pines"] + "battleFields" : ["grass_hills", "grass_pines"], + "horseSoundId" : 2 }, "snow" : { @@ -44,7 +47,8 @@ "tiles" : "SNOWTL", "code" : "sn", "river" : "ri", - "battleFields" : ["snow_mountains", "snow_trees"] + "battleFields" : ["snow_mountains", "snow_trees"], + "horseSoundId" : 3 }, "swamp" : { @@ -55,7 +59,8 @@ "tiles" : "SWMPTL", "code" : "sw", "river" : "rw", - "battleFields" : ["swamp_trees"] + "battleFields" : ["swamp_trees"], + "horseSoundId" : 4 }, "rough" : { @@ -66,7 +71,8 @@ "tiles" : "ROUGTL", "code" : "rg", "river" : "rm", - "battleFields" : ["rough"] + "battleFields" : ["rough"], + "horseSoundId" : 5 }, "subterra" : { @@ -79,7 +85,8 @@ "code" : "sb", "river" : "rw", "battleFields" : ["subterranean"], - "rockTerrain" : "rock" + "rockTerrain" : "rock", + "horseSoundId" : 6 }, "lava" : { @@ -91,7 +98,8 @@ "code" : "lv", "river" : "rl", "battleFields" : ["lava"], - "rockTerrain" : "rock" + "rockTerrain" : "rock", + "horseSoundId" : 7 }, "water" : { @@ -104,7 +112,8 @@ "code" : "wt", "battleFields" : ["ship"], "transitionRequired" : true, - "terrainViewPatterns" : "water" + "terrainViewPatterns" : "water", + "horseSoundId" : 8 }, "rock" : { @@ -117,6 +126,7 @@ "code" : "rc", "battleFields" : ["rocklands"], "transitionRequired" : true, - "terrainViewPatterns" : "rock" + "terrainViewPatterns" : "rock", + "horseSoundId" : 9 } } diff --git a/lib/GameConstants.cpp b/lib/GameConstants.cpp index f9a5d84a2..ac9658bb1 100644 --- a/lib/GameConstants.cpp +++ b/lib/GameConstants.cpp @@ -51,9 +51,9 @@ const TeamID TeamID::NO_TEAM = TeamID(255); namespace GameConstants { #ifdef VCMI_NO_EXTRA_VERSION - const std::string VCMI_VERSION = std::string("VCMI 0.99"); + const std::string VCMI_VERSION = std::string("VCMI 1.0.0"); #else - const std::string VCMI_VERSION = std::string("VCMI 0.99 ") + GIT_SHA1; + const std::string VCMI_VERSION = std::string("VCMI 1.0.0.") + GIT_SHA1; #endif } From dac4628c45e537b192b8a9ce7137c07ce0a6b22c Mon Sep 17 00:00:00 2001 From: DjWarmonger Date: Sun, 4 Sep 2022 08:54:06 +0200 Subject: [PATCH 15/15] Mine roads (#808) * Create dirt roads to mines if there are no roads in a zone. --- config/randomMap.json | 1 + lib/rmg/CMapGenerator.cpp | 1 + lib/rmg/CMapGenerator.h | 1 + lib/rmg/ObjectManager.cpp | 15 +++++++++++++++ lib/rmg/ObjectManager.h | 2 ++ lib/rmg/RoadPlacer.cpp | 26 ++++++++++++++++++++++---- lib/rmg/RoadPlacer.h | 2 +- 7 files changed, 43 insertions(+), 5 deletions(-) diff --git a/config/randomMap.json b/config/randomMap.json index 4a9a9a50e..e5c341c7a 100644 --- a/config/randomMap.json +++ b/config/randomMap.json @@ -22,6 +22,7 @@ }, "minGuardStrength" : 2000, "defaultRoadType" : "pc", //pd - dirt, pg - gravel, pc - cobblestone + "secondaryRoadType": "pd", "treasureValueLimit" : 20000, //generate pandora with gold for treasure above this limit "prisons" : { diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index d4e6a98dc..3dbf3efd7 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -64,6 +64,7 @@ void CMapGenerator::loadConfig() config.mineExtraResources = randomMapJson["mines"]["extraResourcesLimit"].Integer(); config.minGuardStrength = randomMapJson["minGuardStrength"].Integer(); config.defaultRoadType = randomMapJson["defaultRoadType"].String(); + config.secondaryRoadType = randomMapJson["secondaryRoadType"].String(); config.treasureValueLimit = randomMapJson["treasureValueLimit"].Integer(); for(auto & i : randomMapJson["prisons"]["experience"].Vector()) config.prisonExperience.push_back(i.Integer()); diff --git a/lib/rmg/CMapGenerator.h b/lib/rmg/CMapGenerator.h index 34ca69be0..29fa72411 100644 --- a/lib/rmg/CMapGenerator.h +++ b/lib/rmg/CMapGenerator.h @@ -38,6 +38,7 @@ public: int mineExtraResources; int minGuardStrength; std::string defaultRoadType; + std::string secondaryRoadType; int treasureValueLimit; std::vector prisonExperience, prisonValues; std::vector scrollValues; diff --git a/lib/rmg/ObjectManager.cpp b/lib/rmg/ObjectManager.cpp index 06f037e00..b995941d9 100644 --- a/lib/rmg/ObjectManager.cpp +++ b/lib/rmg/ObjectManager.cpp @@ -77,6 +77,21 @@ const rmg::Area & ObjectManager::getVisitableArea() const return objectsVisitableArea; } +std::vector ObjectManager::getMines() const +{ + std::vector mines; + + for (auto object : objects) + { + if (object->ID == Obj::MINE) + { + mines.push_back(object); + } + } + + return mines; +} + int3 ObjectManager::findPlaceForObject(const rmg::Area & searchArea, rmg::Object & obj, std::function weightFunction, OptimizeType optimizer) const { float bestWeight = 0.f; diff --git a/lib/rmg/ObjectManager.h b/lib/rmg/ObjectManager.h index 3349d293d..ff3148af6 100644 --- a/lib/rmg/ObjectManager.h +++ b/lib/rmg/ObjectManager.h @@ -63,6 +63,8 @@ public: void createDistancesPriorityQueue(); const rmg::Area & getVisitableArea() const; + + std::vector getMines() const; protected: //content info diff --git a/lib/rmg/RoadPlacer.cpp b/lib/rmg/RoadPlacer.cpp index 03860fbae..8f605a0d6 100644 --- a/lib/rmg/RoadPlacer.cpp +++ b/lib/rmg/RoadPlacer.cpp @@ -10,6 +10,7 @@ #include "StdInc.h" #include "RoadPlacer.h" +#include "ObjectManager.h" #include "Functions.h" #include "CMapGenerator.h" #include "RmgMap.h" @@ -63,12 +64,13 @@ bool RoadPlacer::createRoad(const int3 & dst) } -void RoadPlacer::drawRoads() +void RoadPlacer::drawRoads(bool secondary) { zone.areaPossible().subtract(roads); zone.freePaths().unite(roads); map.getEditManager()->getTerrainSelection().setSelection(roads.getTilesVector()); - map.getEditManager()->drawRoad(generator.getConfig().defaultRoadType, &generator.rand); + std::string roadType = (secondary ? generator.getConfig().secondaryRoadType : generator.getConfig().defaultRoadType); + map.getEditManager()->drawRoad(roadType, &generator.rand); } void RoadPlacer::addRoadNode(const int3& node) @@ -78,7 +80,22 @@ void RoadPlacer::addRoadNode(const int3& node) void RoadPlacer::connectRoads() { - if(roadNodes.empty()) + bool noRoadNodes = false; + //Assumes objects are already placed + if (roadNodes.size() < 2) + { + //If there are no nodes, draw roads to mines + noRoadNodes = true; + if (auto* m = zone.getModificator()) + { + for (auto object : m->getMines()) + { + addRoadNode(object->visitablePos()); + } + } + } + + if(roadNodes.size() < 2) return; //take any tile from road nodes as destination zone for all other road nodes @@ -90,7 +107,8 @@ void RoadPlacer::connectRoads() createRoad(node); } - drawRoads(); + //Draw dirt roads if there are only mines + drawRoads(noRoadNodes); } char RoadPlacer::dump(const int3 & t) diff --git a/lib/rmg/RoadPlacer.h b/lib/rmg/RoadPlacer.h index 5c7f44337..585679ee6 100644 --- a/lib/rmg/RoadPlacer.h +++ b/lib/rmg/RoadPlacer.h @@ -28,7 +28,7 @@ public: protected: bool createRoad(const int3 & dst); - void drawRoads(); //actually updates tiles + void drawRoads(bool secondary = false); //actually updates tiles protected: rmg::Tileset roadNodes; //tiles to be connected with roads