diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index bba77f2fb..7853eaa36 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -86,7 +86,6 @@ set(lib_SRCS rmg/CRmgTemplate.cpp rmg/CRmgTemplateStorage.cpp rmg/CRmgTemplateZone.cpp - rmg/CZoneGraphGenerator.cpp rmg/CZonePlacer.cpp serializer/BinaryDeserializer.cpp @@ -292,7 +291,6 @@ set(lib_HEADERS rmg/CRmgTemplate.h rmg/CRmgTemplateStorage.h rmg/CRmgTemplateZone.h - rmg/CZoneGraphGenerator.h rmg/CZonePlacer.h rmg/float3.h diff --git a/lib/mapObjects/ObjectTemplate.cpp b/lib/mapObjects/ObjectTemplate.cpp index f8763d2a3..5fd9d24a0 100644 --- a/lib/mapObjects/ObjectTemplate.cpp +++ b/lib/mapObjects/ObjectTemplate.cpp @@ -255,6 +255,7 @@ void ObjectTemplate::readJson(const JsonNode &node, const bool withTerrain) allowedTerrains.insert(ETerrainType((si32)i)); allowedTerrains.erase(ETerrainType::ROCK); + allowedTerrains.erase(ETerrainType::WATER); } if(withTerrain && allowedTerrains.empty()) @@ -327,8 +328,8 @@ void ObjectTemplate::writeJson(JsonNode & node, const bool withTerrain) const if(withTerrain) { - //assumed that ROCK terrain not included - if(allowedTerrains.size() < (GameConstants::TERRAIN_TYPES - 1)) + //assumed that ROCK and WATER terrains are not included + if(allowedTerrains.size() < (GameConstants::TERRAIN_TYPES - 2)) { JsonVector & data = node["allowedTerrains"].Vector(); diff --git a/lib/rmg/CMapGenOptions.cpp b/lib/rmg/CMapGenOptions.cpp index 3b93d45f7..2adfab190 100644 --- a/lib/rmg/CMapGenOptions.cpp +++ b/lib/rmg/CMapGenOptions.cpp @@ -14,6 +14,7 @@ #include "../mapping/CMap.h" #include "CRmgTemplateStorage.h" #include "CRmgTemplate.h" +#include "CRandomGenerator.h" #include "../VCMI_Lib.h" #include "../CTownHandler.h" @@ -390,9 +391,39 @@ bool CMapGenOptions::checkOptions() const const CRmgTemplate * CMapGenOptions::getPossibleTemplate(CRandomGenerator & rand) const { int3 tplSize(width, height, (hasTwoLevels ? 2 : 1)); + auto humanPlayers = countHumanPlayers(); + + auto templates = VLC->tplh->getTemplates(); + + vstd::erase_if(templates, [this, &tplSize, humanPlayers](const CRmgTemplate * tmpl) + { + if(!tmpl->matchesSize(tplSize)) + return true; + + if(!tmpl->isWaterContentAllowed(getWaterContent())) + return true; + + if(getPlayerCount() != -1) + { + if (!tmpl->getPlayers().isInRange(getPlayerCount())) + return true; + } + else + { + // Human players shouldn't be banned when playing with random player count + if(humanPlayers > *boost::min_element(tmpl->getPlayers().getNumbers())) + return true; + } + + if(compOnlyPlayerCount != -1) + { + if (!tmpl->getCpuPlayers().isInRange(compOnlyPlayerCount)) + return true; + } + + return false; + }); - auto templates = VLC->tplh->getTemplates(tplSize, getPlayerCount(), countHumanPlayers(), compOnlyPlayerCount); - // Select tpl if(templates.empty()) return nullptr; diff --git a/lib/rmg/CMapGenOptions.h b/lib/rmg/CMapGenOptions.h index 72e926435..8ee413c65 100644 --- a/lib/rmg/CMapGenOptions.h +++ b/lib/rmg/CMapGenOptions.h @@ -11,9 +11,9 @@ #pragma once #include "../GameConstants.h" -#include "../CRandomGenerator.h" class CRmgTemplate; +class CRandomGenerator; namespace EWaterContent { diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index 29bf8f500..637cc26b8 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -285,17 +285,41 @@ void CMapGenerator::genZones() for(const auto & option : tmpl->getZones()) { auto zone = std::make_shared(this); - zone->setOptions(option.second.get()); + zone->setOptions(*option.second.get()); zones[zone->getId()] = zone; } CZonePlacer placer(this); 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); + } logGlobal->info("Zones generated successfully"); } +void CMapGenerator::createWaterTreasures() +{ + //add treasures on water + getZoneWater().second->addTreasureInfo(CTreasureInfo{100, 1000, 5}); + getZoneWater().second->addTreasureInfo(CTreasureInfo{2000, 6000, 1}); +} + +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 @@ -308,41 +332,67 @@ void CMapGenerator::fillZones() //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) + for(auto it : zones) it.second->initTownType(); //make sure there are some free tiles in the zone - for (auto it : zones) + for(auto it : zones) it.second->initFreeTiles(); - createDirectConnections(); //direct - - //make sure all connections are passable before creating borders - for (auto it : zones) + 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 : zones) { it.second->fill(); 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) + for(auto it : zones) it.second->createObstacles1(); createObstaclesCommon2(); //place actual obstacles matching zone terrain - for (auto it : zones) - { + 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 @@ -379,15 +429,15 @@ void CMapGenerator::fillZones() out << std::endl; } - for (auto it : zones) + for(auto it : zones) { it.second->connectRoads(); //draw roads after everything else has been placed } //find place for Grail - if (treasureZones.empty()) + if(treasureZones.empty()) { - for (auto it : zones) + for(auto it : zones) treasureZones.push_back(it.second); } auto grailZone = *RandomGeneratorUtil::nextItem(treasureZones, rand); @@ -503,30 +553,29 @@ void CMapGenerator::findZonesForQuestArts() 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 - auto tilesCopy = zoneA->getTileInfo(); - std::vector tiles(tilesCopy.begin(), tilesCopy.end()); + const auto & tiles = zoneA->getTileInfo(); int3 guardPos(-1,-1,-1); - auto otherZoneTiles = zoneB->getTileInfo(); - 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 (auto tile : tilesCopy) + for (const auto& tile : tiles) { - if (isBlocked(tile)) //tiles may be occupied by subterranean gates already placed + 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 { @@ -559,8 +608,8 @@ void CMapGenerator::createDirectConnections() if (guardPos.valid()) { //zones can make paths only in their own area - zoneA->connectWithCenter(guardPos, true); - zoneB->connectWithCenter(guardPos, true); + 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 @@ -575,9 +624,12 @@ void CMapGenerator::createDirectConnections() } } } - + if (!guardPos.valid()) - connectionsLeft.push_back(connection); + { + if(!waterMode || posA.z != posB.z || !zoneWater.second->waterKeepConnection(connection.getZoneA(), connection.getZoneB())) + connectionsLeft.push_back(connection); + } } } @@ -865,3 +917,48 @@ ui32 CMapGenerator::getTotalZoneCount() const { return zonesTotal; } +CMapGenerator::Zones::value_type CMapGenerator::getZoneWater() const +{ + return zoneWater; +} + +void CMapGenerator::dump(bool zoneId) +{ + static int id = 0; + std::ofstream out(boost::to_string(boost::format("zone_%d.txt") % id++)); + int levels = map->twoLevel ? 2 : 1; + int width = map->width; + int height = map->height; + for (int k = 0; k < levels; k++) + { + for(int j=0; j JsonVector; class rmgException : public std::exception @@ -96,18 +98,26 @@ public: void registerZone (TFaction faction); ui32 getZoneCount(TFaction faction); ui32 getTotalZoneCount() const; + + Zones::value_type getZoneWater() const; + void createWaterTreasures(); + void prepareWaterTiles(); TRmgTemplateZoneId getZoneID(const int3& tile) const; void setZoneID(const int3& tile, TRmgTemplateZoneId zid); + + void dump(bool zoneId); private: int randomSeed; CMapGenOptions& mapGenOptions; - std::list connectionsLeft; + 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] diff --git a/lib/rmg/CRmgTemplate.cpp b/lib/rmg/CRmgTemplate.cpp index 084e8716b..1774b941f 100644 --- a/lib/rmg/CRmgTemplate.cpp +++ b/lib/rmg/CRmgTemplate.cpp @@ -39,6 +39,12 @@ CTreasureInfo::CTreasureInfo() } +CTreasureInfo::CTreasureInfo(ui32 imin, ui32 imax, ui16 idensity) + : min(imin), max(imax), density(idensity) +{ + +} + bool CTreasureInfo::operator==(const CTreasureInfo & other) const { return (min == other.min) && (max == other.max) && (density == other.density); @@ -196,6 +202,11 @@ ETemplateZoneType::ETemplateZoneType ZoneOptions::getType() const { return type; } + +void ZoneOptions::setType(ETemplateZoneType::ETemplateZoneType value) +{ + type = value; +} int ZoneOptions::getSize() const { @@ -264,6 +275,11 @@ void ZoneOptions::setTreasureInfo(const std::vector & value) { treasureInfo = value; } + +void ZoneOptions::addTreasureInfo(const CTreasureInfo & value) +{ + treasureInfo.push_back(value); +} const std::vector & ZoneOptions::getTreasureInfo() const { @@ -302,7 +318,8 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler) "playerStart", "cpuStart", "treasure", - "junction" + "junction", + "water" }; handler.serializeEnum("type", type, zoneTypes); @@ -421,6 +438,11 @@ bool CRmgTemplate::matchesSize(const int3 & value) const return minSquare <= square && square <= maxSquare; } +bool CRmgTemplate::isWaterContentAllowed(EWaterContent::EWaterContent waterContent) const +{ + return waterContent == EWaterContent::EWaterContent::RANDOM || allowedWaterContent.count(waterContent); +} + void CRmgTemplate::setId(const std::string & value) { id = value; @@ -623,6 +645,14 @@ void CRmgTemplate::afterLoad() zone1->addConnection(id2); zone2->addConnection(id1); } + + if(allowedWaterContent.empty() || allowedWaterContent.count(EWaterContent::EWaterContent::RANDOM)) + { + allowedWaterContent.insert(EWaterContent::EWaterContent::NONE); + allowedWaterContent.insert(EWaterContent::EWaterContent::NORMAL); + allowedWaterContent.insert(EWaterContent::EWaterContent::ISLANDS); + } + allowedWaterContent.erase(EWaterContent::EWaterContent::RANDOM); } void CRmgTemplate::serializeSize(JsonSerializeFormat & handler, int3 & value, const std::string & fieldName) diff --git a/lib/rmg/CRmgTemplate.h b/lib/rmg/CRmgTemplate.h index 9ddcad48a..94af9017f 100644 --- a/lib/rmg/CRmgTemplate.h +++ b/lib/rmg/CRmgTemplate.h @@ -24,7 +24,8 @@ namespace ETemplateZoneType PLAYER_START, CPU_START, TREASURE, - JUNCTION + JUNCTION, + WATER }; } @@ -35,6 +36,7 @@ public: ui32 max; ui16 density; CTreasureInfo(); + CTreasureInfo(ui32 min, ui32 max, ui16 density); bool operator ==(const CTreasureInfo & other) const; @@ -93,6 +95,8 @@ public: void setId(TRmgTemplateZoneId value); ETemplateZoneType::ETemplateZoneType getType() const; + void setType(ETemplateZoneType::ETemplateZoneType value); + int getSize() const; void setSize(int value); boost::optional getOwner() const; @@ -110,6 +114,7 @@ public: std::map getMinesInfo() const; void setTreasureInfo(const std::vector & value); + void addTreasureInfo(const CTreasureInfo & value); const std::vector & getTreasureInfo() const; TRmgTemplateZoneId getMinesLikeZone() const; @@ -175,6 +180,7 @@ public: ~CRmgTemplate(); bool matchesSize(const int3 & value) const; + bool isWaterContentAllowed(EWaterContent::EWaterContent waterContent) const; void setId(const std::string & value); const std::string & getName() const; @@ -195,6 +201,7 @@ private: CPlayerCountRange players, cpuPlayers; Zones zones; std::vector connections; + std::set allowedWaterContent; void afterLoad(); void serializeSize(JsonSerializeFormat & handler, int3 & value, const std::string & fieldName); diff --git a/lib/rmg/CRmgTemplateStorage.cpp b/lib/rmg/CRmgTemplateStorage.cpp index ac9747c0a..d47b297dc 100644 --- a/lib/rmg/CRmgTemplateStorage.cpp +++ b/lib/rmg/CRmgTemplateStorage.cpp @@ -68,36 +68,3 @@ std::vector CRmgTemplateStorage::getTemplates() const } return result; } - -std::vector CRmgTemplateStorage::getTemplates(const int3& filterSize, si8 filterPlayers, si8 filterHumanPlayers, si8 filterCpuPlayers) const -{ - std::vector result; - for(auto i=templates.cbegin(); i!=templates.cend(); ++i) - { - auto& tmpl = i->second; - - if (!tmpl.matchesSize(filterSize)) - continue; - - if (filterPlayers != -1) - { - if (!tmpl.getPlayers().isInRange(filterPlayers)) - continue; - } - else - { - // Human players shouldn't be banned when playing with random player count - if (filterHumanPlayers > *boost::min_element(tmpl.getPlayers().getNumbers())) - continue; - } - - if(filterCpuPlayers != -1) - { - if (!tmpl.getCpuPlayers().isInRange(filterCpuPlayers)) - continue; - } - - result.push_back(&i->second); - } - return result; -} diff --git a/lib/rmg/CRmgTemplateStorage.h b/lib/rmg/CRmgTemplateStorage.h index 2e0ef1f25..b2f3e386a 100644 --- a/lib/rmg/CRmgTemplateStorage.h +++ b/lib/rmg/CRmgTemplateStorage.h @@ -29,9 +29,8 @@ public: virtual void loadObject(std::string scope, std::string name, const JsonNode & data) override; virtual void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override; - const CRmgTemplate * getTemplate(const std::string & templateName) const; + const CRmgTemplate* getTemplate(const std::string & templateName) const; std::vector getTemplates() const; - std::vector getTemplates(const int3& filterSize, si8 filterPlayers, si8 filterHumanPlayers, si8 filterCpuPlayers) const; private: std::map templates; diff --git a/lib/rmg/CRmgTemplateZone.cpp b/lib/rmg/CRmgTemplateZone.cpp index b8b1e47b9..78bb96b85 100644 --- a/lib/rmg/CRmgTemplateZone.cpp +++ b/lib/rmg/CRmgTemplateZone.cpp @@ -119,9 +119,9 @@ bool CRmgTemplateZone::isUnderground() const return getPos().z; } -void CRmgTemplateZone::setOptions(const ZoneOptions * options) +void CRmgTemplateZone::setOptions(const ZoneOptions& options) { - ZoneOptions::operator=(*options); + ZoneOptions::operator=(options); } void CRmgTemplateZone::setQuestArtZone(std::shared_ptr otherZone) @@ -134,6 +134,12 @@ 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; @@ -148,9 +154,9 @@ void CRmgTemplateZone::setCenter(const float3 &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 + 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) + if(center.y < 0) center.y = 1 - std::abs(center.y); } @@ -169,9 +175,15 @@ void CRmgTemplateZone::setPos(const int3 &Pos) pos = Pos; } -void CRmgTemplateZone::addTile (const int3 &pos) +void CRmgTemplateZone::addTile (const int3 &Pos) { - tileinfo.insert(pos); + tileinfo.insert(Pos); +} + +void CRmgTemplateZone::removeTile(const int3 & Pos) +{ + tileinfo.erase(Pos); + possibleTiles.erase(Pos); } std::set CRmgTemplateZone::getTileInfo () const @@ -183,7 +195,7 @@ std::set CRmgTemplateZone::getPossibleTiles() const return possibleTiles; } -void CRmgTemplateZone::discardDistantTiles (float distance) +std::set CRmgTemplateZone::collectDistantTiles (float distance) const { //TODO: mark tiles beyond zone as unavailable, but allow to connect with adjacent zones @@ -195,10 +207,15 @@ void CRmgTemplateZone::discardDistantTiles (float distance) // //gen->setOccupied(tile, ETileType::BLOCKED); //fixme: crash at rendering? // } //} - vstd::erase_if (tileinfo, [distance, this](const int3 &tile) -> bool + std::set discardedTiles; + for(auto& tile : tileinfo) { - return tile.dist2d(this->pos) > distance; - }); + if(tile.dist2d(this->pos) > distance) + { + discardedTiles.insert(tile); + } + }; + return discardedTiles; } void CRmgTemplateZone::clearTiles() @@ -208,20 +225,19 @@ void CRmgTemplateZone::clearTiles() void CRmgTemplateZone::initFreeTiles () { - vstd::copy_if (tileinfo, vstd::set_inserter(possibleTiles), [this](const int3 &tile) -> bool + vstd::copy_if(tileinfo, vstd::set_inserter(possibleTiles), [this](const int3 &tile) -> bool { return gen->isPossible(tile); }); - if (freePaths.empty()) + if(freePaths.empty()) { - gen->setOccupied(pos, ETileType::FREE); - freePaths.insert(pos); //zone must have at least one free tile where other paths go - for instance in the center + 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) + for(auto tile : tileinfo) { bool edge = false; gen->foreach_neighbour(tile, [this, &edge](int3 &pos) @@ -230,6 +246,9 @@ void CRmgTemplateZone::createBorder() 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) @@ -243,6 +262,427 @@ void CRmgTemplateZone::createBorder() } } +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, 3500); + 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) @@ -256,26 +696,24 @@ void CRmgTemplateZone::fractalize() //the more treasure density, the greater distance between paths. Scaling is experimental. int totalDensity = 0; - for (auto ti : treasureInfo) + for(auto ti : treasureInfo) totalDensity += ti.density; const float minDistance = 10 * 10; //squared - for (auto tile : tileinfo) + for(auto tile : tileinfo) { - if (gen->isFree(tile)) - clearedTiles.push_back(tile); - else if (gen->isPossible(tile)) + 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) + if(type != ETemplateZoneType::JUNCTION) { //junction is not fractalized, has only one straight path //everything else remains blocked - while (possibleTiles.size()) + while(!possibleTiles.empty()) { //link tiles in random order std::vector tilesToMakePath(possibleTiles.begin(), possibleTiles.end()); @@ -283,22 +721,22 @@ void CRmgTemplateZone::fractalize() int3 nodeFound(-1, -1, -1); - for (auto tileToMakePath : tilesToMakePath) + for(auto tileToMakePath : tilesToMakePath) { //find closest free tile float currentDistance = 1e10; int3 closestTile(-1, -1, -1); - for (auto clearTile : clearedTiles) + for(auto clearTile : clearedTiles) { float distance = static_cast(tileToMakePath.dist2dSQ(clearTile)); - if (distance < currentDistance) + if(distance < currentDistance) { currentDistance = distance; closestTile = clearTile; } - if (currentDistance <= minDistance) + if(currentDistance <= minDistance) { //this tile is close enough. Forget about it and check next one tilesToIgnore.insert(tileToMakePath); @@ -315,12 +753,12 @@ void CRmgTemplateZone::fractalize() } } - for (auto tileToClear : tilesToIgnore) + 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 (?) + if(!nodeFound.valid()) //nothing else can be done (?) break; tilesToIgnore.clear(); } @@ -329,20 +767,20 @@ void CRmgTemplateZone::fractalize() //cut straight paths towards the center. A* is too slow for that. for (auto node : nodes) { - boost::sort(nodes, [&node](const int3& ourNode, const int3& otherNode) -> bool + 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 (nodes.size() >= 2) + if (subnodes.size() >= 2) { - nearbyNodes.push_back(nodes[1]); //node[0] is our node we want to connect + nearbyNodes.push_back(subnodes[1]); //node[0] is our node we want to connect } - if (nodes.size() >= 3) + if (subnodes.size() >= 3) { - nearbyNodes.push_back(nodes[2]); + nearbyNodes.push_back(subnodes[2]); } //connect with all the paths @@ -350,7 +788,7 @@ void CRmgTemplateZone::fractalize() //connect with nearby nodes for (auto nearbyNode : nearbyNodes) { - crunchPath(node, nearbyNode, true, &freePaths); + crunchPath(node, nearbyNode, true, &freePaths); //do not allow to make another path network } } for (auto node : nodes) @@ -362,16 +800,19 @@ void CRmgTemplateZone::fractalize() for (auto tile : tileinfo) { - if (!gen->isPossible(tile)) + if(!gen->isPossible(tile)) + continue; + + if(freePaths.count(tile)) continue; bool closeTileFound = false; - for (auto clearTile : freePaths) + for(auto clearTile : freePaths) { float distance = static_cast(tile.dist2dSQ(clearTile)); - if (distance < blockDistance) + if(distance < blockDistance) { closeTileFound = true; break; @@ -419,7 +860,7 @@ void CRmgTemplateZone::fractalize() void CRmgTemplateZone::connectLater() { - for (const int3& node : tilesToConnectLater) + for (const int3 & node : tilesToConnectLater) { if (!connectWithCenter(node, true)) logGlobal->error("Failed to connect node %s with center of the zone", node.toString()); @@ -699,13 +1140,14 @@ bool CRmgTemplateZone::connectPath(const int3& src, bool onlyStraight) } for (auto tile : closed) //these tiles are sealed off and can't be connected anymore { - gen->setOccupied (tile, ETileType::BLOCKED); + 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 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 @@ -741,7 +1183,7 @@ bool CRmgTemplateZone::connectWithCenter(const int3& src, bool onlyStraight) } else { - auto foo = [this, &open, &closed, &cameFrom, ¤tNode, &distances](int3& pos) -> void + auto foo = [this, &open, &closed, &cameFrom, ¤tNode, &distances, passThroughBlocked](int3& pos) -> void { if (vstd::contains(closed, pos)) return; @@ -750,10 +1192,13 @@ bool CRmgTemplateZone::connectWithCenter(const int3& src, bool onlyStraight) 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; @@ -794,6 +1239,11 @@ void CRmgTemplateZone::addNearbyObject(CGObjectInstance * obj, CGObjectInstance { 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) { @@ -920,6 +1370,7 @@ bool CRmgTemplateZone::createTreasurePile(int3 &pos, float minDistance, const CT else { object = oi.generateObject(); + object->appearance = oi.templ; //remove from possible objects auto oiptr = std::find(possibleObjects.begin(), possibleObjects.end(), oi); @@ -962,12 +1413,12 @@ bool CRmgTemplateZone::createTreasurePile(int3 &pos, float minDistance, const CT for (auto tile : boundaryCopy) { - if (gen->isPossible(tile)) //we can place new treasure only on possible tile + 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->getNearestObjectDistance(pos) < minDistance) + if (!(gen->isBlocked(pos) || gen->isPossible(pos)) || gen->getZoneID(pos) != getId() || gen->getNearestObjectDistance(pos) < minDistance) here = false; }); if (here) @@ -1013,7 +1464,7 @@ bool CRmgTemplateZone::createTreasurePile(int3 &pos, float minDistance, const CT for (auto tile : info.occupiedPositions) { - if (gen->map->isInTheMap(tile)) //pile boundary may reach map border + 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 } @@ -1041,7 +1492,7 @@ bool CRmgTemplateZone::createTreasurePile(int3 &pos, float minDistance, const CT }); } - bool isPileGuarded = currentValue >= minGuardedValue; + bool isPileGuarded = isGuardNeededForTreasure(currentValue); for (auto tile : boundary) //guard must be standing there { @@ -1057,11 +1508,9 @@ bool CRmgTemplateZone::createTreasurePile(int3 &pos, float minDistance, const CT for (auto treasure : treasures) { int3 visitableOffset = treasure.second->getVisitableOffset(); - if (treasure.second->ID == Obj::SEER_HUT) //FIXME: find generic solution or figure out why Seer Hut doesn't behave correctly - visitableOffset.x += 1; placeObject(treasure.second, treasure.first + visitableOffset); } - if (addMonster(guardPos, currentValue, false)) + if (isPileGuarded && addMonster(guardPos, currentValue, false)) {//block only if the object is guarded for (auto tile : boundary) { @@ -1075,12 +1524,6 @@ bool CRmgTemplateZone::createTreasurePile(int3 &pos, float minDistance, const CT gen->setOccupied(pos, ETileType::FREE); }); } - else //mo monster in this pile, make some free space (needed?) - { - for (auto tile : boundary) - if (gen->isPossible(tile)) - gen->setOccupied(tile, ETileType::FREE); - } } else if (isPileGuarded)//we couldn't make a connection to this location, block it { @@ -1097,7 +1540,8 @@ bool CRmgTemplateZone::createTreasurePile(int3 &pos, float minDistance, const CT } else //we did not place eveyrthing successfully { - gen->setOccupied(pos, ETileType::BLOCKED); //TODO: refactor stop condition + if(gen->isPossible(pos)) + gen->setOccupied(pos, ETileType::BLOCKED); //TODO: refactor stop condition vstd::erase_if_present(possibleTiles, pos); return false; } @@ -1157,6 +1601,7 @@ void CRmgTemplateZone::initTownType () 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 @@ -1284,24 +1729,31 @@ void CRmgTemplateZone::randomizeTownType(bool matchUndergroundType) void CRmgTemplateZone::initTerrainType () { - - 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 (type==ETemplateZoneType::WATER) { - if (terrainType != ETerrainType::LAVA) - terrainType = ETerrainType::SUBTERRANEAN; + terrainType = ETerrainType::WATER; } else { - if (terrainType == ETerrainType::SUBTERRANEAN) - terrainType = ETerrainType::DIRT; - } + 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 (terrainType != ETerrainType::LAVA) + terrainType = ETerrainType::SUBTERRANEAN; + } + else + { + if (terrainType == ETerrainType::SUBTERRANEAN) + terrainType = ETerrainType::DIRT; + } + } + } paintZoneTerrain (terrainType); } @@ -1351,7 +1803,7 @@ bool CRmgTemplateZone::placeMines () return true; } -EObjectPlacingResult::EObjectPlacingResult CRmgTemplateZone::tryToPlaceObjectAndConnectToPath(CGObjectInstance *obj, int3 &pos) +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; @@ -1418,20 +1870,27 @@ bool CRmgTemplateZone::createRequiredObjects() boost::remove_if(tiles, [obj, this](int3 &tile)-> bool { //object must be accessible from at least one surounding tile - return !this->isAccessibleFromAnywhere(obj.first->appearance, 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](const int3 & lhs, const int3 & rhs) -> bool + auto isCloser = [this, &targetPosition, &tilesBlockedByObject](const int3 & lhs, const int3 & rhs) -> bool { - float lDist = static_cast(this->pos.dist2d(lhs)); - float rDist = static_cast(this->pos.dist2d(rhs)); + 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()) @@ -1442,9 +1901,7 @@ bool CRmgTemplateZone::createRequiredObjects() for (auto tile : tiles) { //code partially adapted from findPlaceForObject() - if(areAllTilesAvailable(obj.first, tile, tilesBlockedByObject)) - gen->setOccupied(pos, ETileType::BLOCKED); //why? - else + if(!areAllTilesAvailable(obj.first, tile, tilesBlockedByObject)) continue; attempt = true; @@ -1478,7 +1935,7 @@ bool CRmgTemplateZone::createRequiredObjects() { gen->foreachDirectNeighbour(blockedTile, [this, &possiblePositions](int3 pos) { - if (!gen->isBlocked(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) @@ -1500,10 +1957,210 @@ bool CRmgTemplateZone::createRequiredObjects() 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())); + 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) +{ + auto subObjects = VLC->objtypeh->knownSubObjects(Obj::SHIPYARD); + auto shipyard = (CGShipyard*) VLC->objtypeh->getHandlerFor(Obj::SHIPYARD, *RandomGeneratorUtil::nextItem(subObjects, gen->rand))->create(ObjectTemplate()); + shipyard->tempOwner = PlayerColor::NEUTRAL; + + setTemplateForObject(shipyard); + std::vector offsets; + auto tilesBlockedByObject = shipyard->getBlockedOffsets(); + tilesBlockedByObject.insert(shipyard->getVisitableOffset()); + shipyard->getOutOffsets(offsets); + + int3 targetTile(-1, -1, -1); + std::set shipAccessCandidates; + + for(auto& candidateTile : possibleTiles) + { + bool foundTargetPosition = false; + for(const auto & offset : offsets) + { + if(candidateTile+offset == position) + { + 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 + break; + + //prepare temporary map + for(auto& blockedPos : tilesBlockedAbsolute) + gen->setOccupied(blockedPos, ETileType::USED); + + //check if 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(crunchPath(v, findClosestTile(freePaths, v), false, nullptr)) + shipAccessCandidates.insert(v); + } + }); + + //rollback temporary map + for(auto& blockedPos : tilesBlockedAbsolute) + gen->setOccupied(blockedPos, ETileType::POSSIBLE); + + if(!shipAccessCandidates.empty()) + { + foundTargetPosition = true; + } + + break; //no need to check other offsets as we already found position + } + } + + if(foundTargetPosition && isAccessibleFromSomewhere(shipyard->appearance, candidateTile)) + { + targetTile = candidateTile; + break; + } + + shipAccessCandidates.clear(); //invalidate positions + } + + if(!targetTile.valid()) + { + delete shipyard; + return false; + } + + placeObject(shipyard, targetTile); + guardObject(shipyard, guardStrength, false, true); + + for(auto& accessPosition : shipAccessCandidates) + { + if(connectPath(accessPosition, false)) + { + gen->setOccupied(accessPosition, ETileType::FREE); + return true; + } + } + + throw rmgException("Cannot find path to shipyard boarding position"); +} + void CRmgTemplateZone::createTreasures() { int mapMonsterStrength = gen->getMapGenOptions().getMonsterStrength(); @@ -1549,7 +2206,7 @@ void CRmgTemplateZone::createTreasures() //optimization - don't check tiles which are not allowed vstd::erase_if(possibleTiles, [this](const int3 &tile) -> bool { - return !gen->isPossible(tile); + return (!gen->isPossible(tile)) || gen->getZoneID(tile)!=getId(); }); @@ -1642,8 +2299,8 @@ void CRmgTemplateZone::createObstacles2() //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 or are just possible (with some probability) - if (gen->shouldBeBlocked(tile) || (gen->isPossible(tile) && gen->rand.nextInt(1,100) < 60)) + //fill tiles that should be blocked with obstacles + if (gen->shouldBeBlocked(tile)) { //start from biggets obstacles for (int i = 0; i < possibleObstacles.size(); i++) @@ -1726,18 +2383,29 @@ void CRmgTemplateZone::drawRoads() bool CRmgTemplateZone::fill() { initTerrainType(); - - //zone center should be always clear to allow other tiles to connect - gen->setOccupied(pos, ETileType::FREE); - freePaths.insert(pos); - - addAllPossibleObjects (); - - connectLater(); //ideally this should work after fractalize, but fails - fractalize(); - placeMines(); - createRequiredObjects(); - createTreasures(); + + addAllPossibleObjects(); + + if(type==ETemplateZoneType::WATER) + { + initFreeTiles(); + connectLater(); + createRequiredObjects(); + fractalize(); + createTreasures(); + } + else + { + //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(); + } + + gen->dump(false); logGlobal->info("Zone %d filled successfully", id); return true; @@ -1748,7 +2416,7 @@ bool CRmgTemplateZone::findPlaceForTreasurePile(float min_dist, int3 &pos, int v float best_distance = 0; bool result = false; - bool needsGuard = value > minGuardedValue; + bool needsGuard = isGuardNeededForTreasure(value); //logGlobal->info("Min dist for density %f is %d", density, min_dist); for(auto tile : possibleTiles) @@ -1760,7 +2428,7 @@ bool CRmgTemplateZone::findPlaceForTreasurePile(float min_dist, int3 &pos, int v bool allTilesAvailable = true; gen->foreach_neighbour (tile, [this, &allTilesAvailable, needsGuard](int3 neighbour) { - if (!(gen->isPossible(neighbour) || gen->shouldBeBlocked(neighbour) || (!needsGuard && gen->isFree(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 } @@ -1790,7 +2458,7 @@ bool CRmgTemplateZone::canObstacleBePlacedHere(ObjectTemplate &temp, int3 &pos) for (auto blockingTile : tilesBlockedByObject) { int3 t = pos + blockingTile; - if (!gen->map->isInTheMap(t) || !(gen->isPossible(t) || gen->shouldBeBlocked(t))) + 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 } @@ -1798,12 +2466,12 @@ bool CRmgTemplateZone::canObstacleBePlacedHere(ObjectTemplate &temp, int3 &pos) return true; } -bool CRmgTemplateZone::isAccessibleFromAnywhere (ObjectTemplate &appearance, int3 &tile) const +bool CRmgTemplateZone::isAccessibleFromSomewhere(ObjectTemplate & appearance, const int3 & tile) const { return getAccessibleOffset(appearance, tile).valid(); } -int3 CRmgTemplateZone::getAccessibleOffset(ObjectTemplate &appearance, int3 &tile) const +int3 CRmgTemplateZone::getAccessibleOffset(ObjectTemplate & appearance, const int3 & tile) const { auto tilesBlockedByObject = appearance.getBlockedOffsets(); @@ -1820,7 +2488,7 @@ int3 CRmgTemplateZone::getAccessibleOffset(ObjectTemplate &appearance, int3 &til int3 nearbyPos = tile + offset; if (gen->map->isInTheMap(nearbyPos)) { - if (appearance.isVisitableFrom(x, y) && !gen->isBlocked(nearbyPos)) + if (appearance.isVisitableFrom(x, y) && !gen->isBlocked(nearbyPos) && tileinfo.find(nearbyPos) != tileinfo.end()) ret = nearbyPos; } } @@ -1842,12 +2510,12 @@ void CRmgTemplateZone::setTemplateForObject(CGObjectInstance* obj) } } -bool CRmgTemplateZone::areAllTilesAvailable(CGObjectInstance* obj, int3& tile, std::set& tilesBlockedByObject) const +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)) + 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; @@ -1869,7 +2537,7 @@ bool CRmgTemplateZone::findPlaceForObject(CGObjectInstance* obj, si32 min_dist, for (auto tile : tileinfo) { //object must be accessible from at least one surounding tile - if (!isAccessibleFromAnywhere(obj->appearance, tile)) + if (!isAccessibleFromSomewhere(obj->appearance, tile)) continue; auto ti = gen->getTile(tile); @@ -2005,6 +2673,11 @@ std::vector CRmgTemplateZone::getAccessibleOffsets (const CGObjectInstance 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); @@ -2027,13 +2700,13 @@ bool CRmgTemplateZone::guardObject(CGObjectInstance* object, si32 str, bool zone { for (auto pos : tiles) { - if (!gen->isFree(pos)) + if (gen->isPossible(pos) && gen->getZoneID(pos) == id) gen->setOccupied(pos, ETileType::BLOCKED); } gen->foreach_neighbour (guardTile, [&](int3& pos) { - if (gen->isPossible(pos)) - gen->setOccupied (pos, ETileType::FREE); + if (gen->isPossible(pos) && gen->getZoneID(pos) == id) + gen->setOccupied(pos, ETileType::FREE); }); gen->setOccupied (guardTile, ETileType::USED); @@ -2042,7 +2715,7 @@ bool CRmgTemplateZone::guardObject(CGObjectInstance* object, si32 str, bool zone { for (auto tile : tiles) if (gen->isPossible(tile)) - gen->setOccupied (tile, ETileType::FREE); + gen->setOccupied(tile, ETileType::FREE); } return true; @@ -2074,7 +2747,7 @@ ObjectInfo CRmgTemplateZone::getRandomObject(CTreasurePileInfo &info, ui32 desir //objectsVisitableFromBottom++; //there must be free tiles under object auto blockedOffsets = oi.templ.getBlockedOffsets(); - if (!isAccessibleFromAnywhere(oi.templ, newVisitablePos)) + if (!isAccessibleFromSomewhere(oi.templ, newVisitablePos)) continue; } @@ -2207,15 +2880,6 @@ void CRmgTemplateZone::addAllPossibleObjects() int numZones = static_cast(gen->getZones().size()); - std::vector creatures; //native creatures for this zone - for (auto cre : VLC->creh->objects) - { - if (!cre->special && cre->faction == townType) - { - creatures.push_back(cre); - } - } - for (auto primaryID : VLC->objtypeh->knownObjects()) { for (auto secondaryID : VLC->objtypeh->knownSubObjects(primaryID)) @@ -2243,6 +2907,9 @@ void CRmgTemplateZone::addAllPossibleObjects() } } } + + if(type == ETemplateZoneType::WATER) + return; //prisons //levels 1, 5, 10, 20, 30 @@ -2283,6 +2950,15 @@ void CRmgTemplateZone::addAllPossibleObjects() //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}; @@ -2669,5 +3345,13 @@ ObjectInfo::ObjectInfo() void ObjectInfo::setTemplate (si32 type, si32 subtype, ETerrainType terrainType) { - templ = VLC->objtypeh->getHandlerFor(type, subtype)->getTemplates(terrainType).front(); + 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 index 2b6f977bb..83a7778c2 100644 --- a/lib/rmg/CRmgTemplateZone.h +++ b/lib/rmg/CRmgTemplateZone.h @@ -91,26 +91,29 @@ class DLL_LINKAGE CRmgTemplateZone : public rmg::ZoneOptions public: CRmgTemplateZone(CMapGenerator * Gen); - void setOptions(const rmg::ZoneOptions * options); + 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 isAccessibleFromAnywhere(ObjectTemplate &appearance, int3 &tile) const; - int3 getAccessibleOffset(ObjectTemplate &appearance, int3 &tile) const; + bool isAccessibleFromSomewhere(ObjectTemplate & appearance, const int3 & tile) const; + int3 getAccessibleOffset(ObjectTemplate & appearance, const int3 & tile) const; - void addTile (const int3 &pos); + void addTile (const int3 & pos); + void removeTile(const int3 & pos); void initFreeTiles (); std::set getTileInfo() const; std::set getPossibleTiles() const; - void discardDistantTiles (float distance); + 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); @@ -123,21 +126,35 @@ public: void createBorder(); void fractalize(); void connectLater(); - EObjectPlacingResult::EObjectPlacingResult tryToPlaceObjectAndConnectToPath(CGObjectInstance *obj, int3 &pos); //return true if the position cna be connected + 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 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, std::set& tilesBlockedByObject) const; + 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); @@ -160,9 +177,20 @@ public: 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; ETerrainType terrainType; std::weak_ptr questArtZone; //artifacts required for Seer Huts will be placed here - or not if null @@ -173,8 +201,10 @@ private: //content info std::vector> requiredObjects; std::vector> closeObjects; + std::vector> instantObjects; std::vector> nearbyObjects; std::vector objects; + std::map requestedPositions; //placement info int3 pos; @@ -182,11 +212,14 @@ private: 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 @@ -197,4 +230,6 @@ private: bool canObstacleBePlacedHere(ObjectTemplate &temp, int3 &pos); void setTemplateForObject(CGObjectInstance* obj); void checkAndPlaceObject(CGObjectInstance* object, const int3 &pos); + + bool isGuardNeededForTreasure(int value); }; diff --git a/lib/rmg/CZoneGraphGenerator.cpp b/lib/rmg/CZoneGraphGenerator.cpp deleted file mode 100644 index 1f7df0d41..000000000 --- a/lib/rmg/CZoneGraphGenerator.cpp +++ /dev/null @@ -1,33 +0,0 @@ -/* - * CZoneGraphGenerator.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 "CZoneGraphGenerator.h" - -CZoneCell::CZoneCell(const CRmgTemplateZone * zone)// : zone(zone) -{ - -} - - -CZoneGraph::CZoneGraph() -{ - -} - -CZoneGraphGenerator::CZoneGraphGenerator()// : gen(nullptr) -{ - -} - -std::unique_ptr CZoneGraphGenerator::generate(const CMapGenOptions & options, CRandomGenerator * gen) -{ - return make_unique(); -} diff --git a/lib/rmg/CZoneGraphGenerator.h b/lib/rmg/CZoneGraphGenerator.h deleted file mode 100644 index 4cb1f8d2c..000000000 --- a/lib/rmg/CZoneGraphGenerator.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * CZoneGraphGenerator.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 - -class CRmgTemplateZone; -class CRandomGenerator; -class CMapGenOptions; - -class CZoneCell -{ -public: - explicit CZoneCell(const CRmgTemplateZone * zone); - -private: - //const CRmgTemplateZone * zone; - - //TODO additional data -}; - -class CZoneGraph -{ -public: - CZoneGraph(); - -private: - //TODO zone graph storage -}; - -class CZoneGraphGenerator -{ -public: - CZoneGraphGenerator(); - - std::unique_ptr generate(const CMapGenOptions & options, CRandomGenerator * gen); - -private: - std::unique_ptr graph; - //CRandomGenerator * gen; -}; diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 375a8b4d1..567d7723c 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -13,8 +13,7 @@ #include "CZonePlacer.h" #include "CRmgTemplateZone.h" #include "../mapping/CMap.h" - -#include "CZoneGraphGenerator.h" +#include "../mapping/CMapEditManager.h" class CRandomGenerator; @@ -557,7 +556,11 @@ void CZonePlacer::assignZones() if (zone.second->isUnderground()) { if (!CREATE_FULL_UNDERGROUND) - zone.second->discardDistantTiles((float)(zone.second->getSize() + 1)); + { + auto discardTile = zone.second->collectDistantTiles((float)(zone.second->getSize() + 1)); + for(auto& t : discardTile) + zone.second->removeTile(t); + } //make sure that terrain inside zone is not a rock //FIXME: reorder actions?