diff --git a/config/factions/dungeon.json b/config/factions/dungeon.json index 6e9235a3c..d03e436e6 100644 --- a/config/factions/dungeon.json +++ b/config/factions/dungeon.json @@ -4,6 +4,7 @@ "index" : 5, "nativeTerrain": "subterra", "alignment" : "evil", + "preferUndergroundPlacement": true, "creatureBackground" : { "120px" : "TPCASDUN", diff --git a/config/factions/inferno.json b/config/factions/inferno.json index 505f209fd..bacb77cfa 100644 --- a/config/factions/inferno.json +++ b/config/factions/inferno.json @@ -4,6 +4,7 @@ "index" : 3, "nativeTerrain": "lava", "alignment" : "evil", + "preferUndergroundPlacement": true, "creatureBackground" : { "120px" : "TPCASINF", diff --git a/config/factions/necropolis.json b/config/factions/necropolis.json index 05b2cf291..e7fe155d9 100644 --- a/config/factions/necropolis.json +++ b/config/factions/necropolis.json @@ -4,6 +4,7 @@ "index" : 4, "nativeTerrain": "dirt", "alignment" : "evil", + "preferUndergroundPlacement": true, "creatureBackground" : { "120px" : "TPCASNEC", diff --git a/config/schemas/faction.json b/config/schemas/faction.json index a31d0f718..f54afbd64 100644 --- a/config/schemas/faction.json +++ b/config/schemas/faction.json @@ -73,6 +73,10 @@ "type":"string", "description": "Native terrain for creatures. Creatures fighting on native terrain receive several bonuses" }, + "preferUndergroundPlacement": { + "type":"bool", + "description": "Random map generator places player/cpu-owned towns underground if true is specified and on the ground otherwise. Parameter is unused for maps without underground. False by default." + }, "puzzleMap": { "type":"object", "additionalProperties" : false, diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index f0ff2e5f0..a0fcdad80 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -853,9 +853,9 @@ void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRan CStopWatch sw; // Gen map - CMapGenerator mapGenerator; + CMapGenerator mapGenerator(*scenarioOps->mapGenOptions, scenarioOps->seedToBeUsed); - std::unique_ptr randomMap = mapGenerator.generate(scenarioOps->mapGenOptions.get(), scenarioOps->seedToBeUsed); + std::unique_ptr randomMap = mapGenerator.generate(); if(allowSavingRandomMap) { diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index 4ea32f2da..f057997cb 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -211,6 +211,7 @@ CFaction::CFaction() town = nullptr; index = 0; alignment = EAlignment::NEUTRAL; + preferUndergroundPlacement = false; } CFaction::~CFaction() @@ -1099,6 +1100,9 @@ CFaction * CTownHandler::loadFromJson(const std::string & scope, const JsonNode 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 diff --git a/lib/CTownHandler.h b/lib/CTownHandler.h index e199b1f36..7b38870af 100644 --- a/lib/CTownHandler.h +++ b/lib/CTownHandler.h @@ -207,6 +207,7 @@ public: ETerrainType nativeTerrain; EAlignment::EAlignment alignment; + bool preferUndergroundPlacement; CTown * town; //NOTE: can be null @@ -399,8 +400,8 @@ class DLL_LINKAGE CTownHandler : public CHandlerBase createBonus(CBuilding * build, Bonus::BonusType type, int val, int subtype = -1); - std::shared_ptr createBonus(CBuilding * build, Bonus::BonusType type, int val, TPropagatorPtr & prop, int subtype = -1); + std::shared_ptr createBonus(CBuilding * build, Bonus::BonusType type, int val, int subtype = -1); + std::shared_ptr createBonus(CBuilding * build, Bonus::BonusType type, int val, TPropagatorPtr & prop, int subtype = -1); std::shared_ptr createBonusImpl(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr & prop, const std::string & description, int subtype = -1); /// loads CStructure's into town diff --git a/lib/rmg/CMapGenOptions.cpp b/lib/rmg/CMapGenOptions.cpp index 7d377da49..3b93d45f7 100644 --- a/lib/rmg/CMapGenOptions.cpp +++ b/lib/rmg/CMapGenOptions.cpp @@ -205,11 +205,6 @@ void CMapGenOptions::setMapTemplate(const CRmgTemplate * value) assert(0); } -const std::map & CMapGenOptions::getAvailableTemplates() const -{ - return VLC->tplh->getTemplates(); -} - void CMapGenOptions::finalize(CRandomGenerator & rand) { logGlobal->info("RMG settings: players %d, teams %d, computer players %d, computer teams %d, water %d, monsters %d", @@ -394,59 +389,15 @@ bool CMapGenOptions::checkOptions() const const CRmgTemplate * CMapGenOptions::getPossibleTemplate(CRandomGenerator & rand) const { - // Find potential templates - const auto & tpls = getAvailableTemplates(); - std::list potentialTpls; - for(const auto & tplPair : tpls) - { - const auto & tpl = tplPair.second; - int3 tplSize(width, height, (hasTwoLevels ? 2 : 1)); - if(tpl->matchesSize(tplSize)) - { - bool isPlayerCountValid = false; - if (getPlayerCount() != RANDOM_SIZE) - { - if (tpl->getPlayers().isInRange(getPlayerCount())) - isPlayerCountValid = true; - } - else - { - // Human players shouldn't be banned when playing with random player count - auto playerNumbers = tpl->getPlayers().getNumbers(); - if(countHumanPlayers() <= *boost::min_element(playerNumbers)) - { - isPlayerCountValid = true; - } - } - - if (isPlayerCountValid) - { - bool isCpuPlayerCountValid = false; - if(compOnlyPlayerCount != RANDOM_SIZE) - { - if (tpl->getCpuPlayers().isInRange(compOnlyPlayerCount)) - isCpuPlayerCountValid = true; - } - else - { - isCpuPlayerCountValid = true; - } - - if(isCpuPlayerCountValid) - potentialTpls.push_back(tpl); - } - } - } + int3 tplSize(width, height, (hasTwoLevels ? 2 : 1)); + + auto templates = VLC->tplh->getTemplates(tplSize, getPlayerCount(), countHumanPlayers(), compOnlyPlayerCount); // Select tpl - if(potentialTpls.empty()) - { + if(templates.empty()) return nullptr; - } - else - { - return *RandomGeneratorUtil::nextItem(potentialTpls, rand); - } + + return *RandomGeneratorUtil::nextItem(templates, rand); } CMapGenOptions::CPlayerSettings::CPlayerSettings() : color(0), startingTown(RANDOM_TOWN), playerType(EPlayerType::AI) diff --git a/lib/rmg/CMapGenOptions.h b/lib/rmg/CMapGenOptions.h index 68c58517b..72e926435 100644 --- a/lib/rmg/CMapGenOptions.h +++ b/lib/rmg/CMapGenOptions.h @@ -94,6 +94,7 @@ public: }; CMapGenOptions(); + CMapGenOptions(const CMapGenOptions&) = delete; si32 getWidth() const; void setWidth(si32 value); @@ -141,8 +142,6 @@ public: const CRmgTemplate * getMapTemplate() const; void setMapTemplate(const CRmgTemplate * value); - const std::map & getAvailableTemplates() const; - /// Finalizes the options. All random sizes for various properties will be overwritten by numbers from /// a random number generator by keeping the options in a valid state. Check options should return true, otherwise /// this function fails. diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index f7e33fcdd..29bf8f500 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -46,7 +46,7 @@ void CMapGenerator::foreachDirectNeighbour(const int3& pos, std::function foo) +void CMapGenerator::foreachDiagonalNeighbour(const int3& pos, std::function foo) { for (const int3 &dir : dirsDiagonal) { @@ -57,11 +57,13 @@ void CMapGenerator::foreachDiagonaltNeighbour(const int3& pos, std::functionrandomSeed); + mapGenOptions.finalize(rand); } void CMapGenerator::initTiles() @@ -89,8 +91,8 @@ CMapGenerator::~CMapGenerator() { if (tiles) { - int width = mapGenOptions->getWidth(); - int height = mapGenOptions->getHeight(); + int width = mapGenOptions.getWidth(); + int height = mapGenOptions.getHeight(); for (int i=0; i < width; i++) { for(int j=0; j < height; j++) @@ -111,7 +113,7 @@ void CMapGenerator::initPrisonsRemaining() if (isAllowed) prisonsRemaining++; } - prisonsRemaining = std::max (0, prisonsRemaining - 16 * mapGenOptions->getPlayerCount()); //so at least 16 heroes will be available for every player + prisonsRemaining = std::max (0, prisonsRemaining - 16 * mapGenOptions.getPlayerCount()); //so at least 16 heroes will be available for every player } void CMapGenerator::initQuestArtsRemaining() @@ -123,26 +125,27 @@ void CMapGenerator::initQuestArtsRemaining() } } -std::unique_ptr CMapGenerator::generate(CMapGenOptions * mapGenOptions, int randomSeed) +const CMapGenOptions& CMapGenerator::getMapGenOptions() const { - this->mapGenOptions = mapGenOptions; - this->randomSeed = randomSeed; + return mapGenOptions; +} - assert(mapGenOptions); - - rand.setSeed(this->randomSeed); - mapGenOptions->finalize(rand); +CMapEditManager* CMapGenerator::getEditManager() const +{ + if(!map) + return nullptr; + return map->getEditManager(); +} +std::unique_ptr CMapGenerator::generate() +{ map = make_unique(); - editManager = map->getEditManager(); - try { - editManager->getUndoManager().setUndoRedoLimit(0); + map->getEditManager()->getUndoManager().setUndoRedoLimit(0); //FIXME: somehow mapGenOption is nullptr at this point :? addHeaderInfo(); initTiles(); - initPrisonsRemaining(); initQuestArtsRemaining(); genZones(); @@ -160,14 +163,13 @@ std::unique_ptr CMapGenerator::generate(CMapGenOptions * mapGenOptions, in std::string CMapGenerator::getMapDescription() const { - assert(mapGenOptions); assert(map); const std::string waterContentStr[3] = { "none", "normal", "islands" }; const std::string monsterStrengthStr[3] = { "weak", "normal", "strong" }; - int monsterStrengthIndex = mapGenOptions->getMonsterStrength() - EMonsterStrength::GLOBAL_WEAK; //does not start from 0 - const auto * mapTemplate = mapGenOptions->getMapTemplate(); + int monsterStrengthIndex = mapGenOptions.getMonsterStrength() - EMonsterStrength::GLOBAL_WEAK; //does not start from 0 + const auto * mapTemplate = mapGenOptions.getMapTemplate(); if(!mapTemplate) throw rmgException("Map template for Random Map Generator is not found. Could not start the game."); @@ -175,11 +177,11 @@ 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()) % - static_cast(mapGenOptions->getCompOnlyPlayerCount()) % waterContentStr[mapGenOptions->getWaterContent()] % + randomSeed % map->width % map->height % (map->twoLevel ? "2" : "1") % static_cast(mapGenOptions.getPlayerCount()) % + static_cast(mapGenOptions.getCompOnlyPlayerCount()) % waterContentStr[mapGenOptions.getWaterContent()] % monsterStrengthStr[monsterStrengthIndex]); - for(const auto & pair : mapGenOptions->getPlayersSettings()) + for(const auto & pair : mapGenOptions.getPlayersSettings()) { const auto & pSettings = pair.second; if(pSettings.getPlayerType() == EPlayerType::HUMAN) @@ -211,13 +213,13 @@ void CMapGenerator::addPlayerInfo() { if (i == CPHUMAN) { - playerCount = mapGenOptions->getPlayerCount(); - teamCount = mapGenOptions->getTeamCount(); + playerCount = mapGenOptions.getPlayerCount(); + teamCount = mapGenOptions.getTeamCount(); } else { - playerCount = mapGenOptions->getCompOnlyPlayerCount(); - teamCount = mapGenOptions->getCompOnlyTeamCount(); + playerCount = mapGenOptions.getCompOnlyPlayerCount(); + teamCount = mapGenOptions.getCompOnlyTeamCount(); } if(playerCount == 0) @@ -246,7 +248,7 @@ void CMapGenerator::addPlayerInfo() // Team numbers are assigned randomly to every player //TODO: allow customize teams in rmg template - for(const auto & pair : mapGenOptions->getPlayersSettings()) + for(const auto & pair : mapGenOptions.getPlayersSettings()) { const auto & pSettings = pair.second; PlayerInfo player; @@ -262,36 +264,34 @@ void CMapGenerator::addPlayerInfo() logGlobal->error("Not enough places in team for %s player", ((j == CPUONLY) ? "CPU" : "CPU or human")); assert (teamNumbers[j].size()); } - auto itTeam = RandomGeneratorUtil::nextItem(teamNumbers[j], rand); + auto itTeam = RandomGeneratorUtil::nextItem(teamNumbers[j], rand); player.team = TeamID(*itTeam); teamNumbers[j].erase(itTeam); map->players[pSettings.getColor().getNum()] = player; } - map->howManyTeams = (mapGenOptions->getTeamCount() == 0 ? mapGenOptions->getPlayerCount() : mapGenOptions->getTeamCount()) - + (mapGenOptions->getCompOnlyTeamCount() == 0 ? mapGenOptions->getCompOnlyPlayerCount() : mapGenOptions->getCompOnlyTeamCount()); + map->howManyTeams = (mapGenOptions.getTeamCount() == 0 ? mapGenOptions.getPlayerCount() : mapGenOptions.getTeamCount()) + + (mapGenOptions.getCompOnlyTeamCount() == 0 ? mapGenOptions.getCompOnlyPlayerCount() : mapGenOptions.getCompOnlyTeamCount()); } void CMapGenerator::genZones() { - editManager->clearTerrain(&rand); - editManager->getTerrainSelection().selectRange(MapRect(int3(0, 0, 0), mapGenOptions->getWidth(), mapGenOptions->getHeight())); - editManager->drawTerrain(ETerrainType::GRASS, &rand); + getEditManager()->clearTerrain(&rand); + getEditManager()->getTerrainSelection().selectRange(MapRect(int3(0, 0, 0), mapGenOptions.getWidth(), mapGenOptions.getHeight())); + getEditManager()->drawTerrain(ETerrainType::GRASS, &rand); - auto tmpl = mapGenOptions->getMapTemplate(); + auto tmpl = mapGenOptions.getMapTemplate(); zones.clear(); for(const auto & option : tmpl->getZones()) { - auto zone = std::make_shared(); + auto zone = std::make_shared(this); zone->setOptions(option.second.get()); zones[zone->getId()] = zone; - //todo: move to CRmgTemplateZone constructor - zone->setGenPtr(this);//immediately set gen pointer before taking any actions on zones } CZonePlacer placer(this); - placer.placeZones(mapGenOptions, &rand); - placer.assignZones(mapGenOptions); + placer.placeZones(&rand); + placer.assignZones(); logGlobal->info("Zones generated successfully"); } @@ -310,18 +310,19 @@ void CMapGenerator::fillZones() //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(); - + createDirectConnections(); //direct + //make sure all connections are passable before creating borders for (auto it : zones) it.second->createBorder(); //once direct connections are done - + createConnections2(); //subterranean gates and monoliths - + std::vector> treasureZones; for (auto it : zones) { @@ -329,12 +330,13 @@ void CMapGenerator::fillZones() if (it.second->getType() == ETemplateZoneType::TREASURE) treasureZones.push_back(it.second); } - + //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) @@ -345,7 +347,7 @@ void CMapGenerator::fillZones() #define PRINT_MAP_BEFORE_ROADS false if (PRINT_MAP_BEFORE_ROADS) //enable to debug { - std::ofstream out("road debug"); + std::ofstream out("road_debug.txt"); int levels = map->twoLevel ? 2 : 1; int width = map->width; int height = map->height; @@ -413,8 +415,8 @@ void CMapGenerator::createObstaclesCommon1() } } } - editManager->getTerrainSelection().setSelection(rockTiles); - editManager->drawTerrain(ETerrainType::ROCK, &rand); + getEditManager()->getTerrainSelection().setSelection(rockTiles); + getEditManager()->drawTerrain(ETerrainType::ROCK, &rand); } } @@ -483,7 +485,7 @@ void CMapGenerator::findZonesForQuestArts() { //we want to place arties in zones that were not yet filled (higher index) - for (auto connection : mapGenOptions->getMapTemplate()->getConnections()) + for (auto connection : mapGenOptions.getMapTemplate()->getConnections()) { auto zoneA = zones[connection.getZoneA()]; auto zoneB = zones[connection.getZoneB()]; @@ -501,7 +503,7 @@ void CMapGenerator::findZonesForQuestArts() void CMapGenerator::createDirectConnections() { - for (auto connection : mapGenOptions->getMapTemplate()->getConnections()) + for (auto connection : mapGenOptions.getMapTemplate()->getConnections()) { auto zoneA = zones[connection.getZoneA()]; auto zoneB = zones[connection.getZoneB()]; @@ -703,9 +705,9 @@ void CMapGenerator::createConnections2() void CMapGenerator::addHeaderInfo() { map->version = EMapFormat::VCMI; - map->width = mapGenOptions->getWidth(); - map->height = mapGenOptions->getHeight(); - map->twoLevel = mapGenOptions->getHasTwoLevels(); + map->width = mapGenOptions.getWidth(); + map->height = mapGenOptions.getHeight(); + map->twoLevel = mapGenOptions.getHasTwoLevels(); map->name = VLC->generaltexth->allTexts[740]; map->description = getMapDescription(); map->difficulty = 1; diff --git a/lib/rmg/CMapGenerator.h b/lib/rmg/CMapGenerator.h index f714975fe..54470c81e 100644 --- a/lib/rmg/CMapGenerator.h +++ b/lib/rmg/CMapGenerator.h @@ -52,16 +52,16 @@ class DLL_LINKAGE CMapGenerator public: using Zones = std::map>; - explicit CMapGenerator(); + explicit CMapGenerator(CMapGenOptions& mapGenOptions, int RandomSeed = std::time(nullptr)); ~CMapGenerator(); // required due to std::unique_ptr - - std::unique_ptr generate(CMapGenOptions * mapGenOptions, int RandomSeed = std::time(nullptr)); - - CMapGenOptions * mapGenOptions; - std::unique_ptr map; + + mutable std::unique_ptr map; CRandomGenerator rand; - int randomSeed; - CMapEditManager * editManager; + + CMapEditManager* getEditManager() const; + const CMapGenOptions& getMapGenOptions() const; + + std::unique_ptr generate(); Zones & getZones(); void createDirectConnections(); @@ -69,7 +69,7 @@ public: void findZonesForQuestArts(); void foreach_neighbour(const int3 &pos, std::function foo); void foreachDirectNeighbour(const int3 &pos, std::function foo); - void foreachDiagonaltNeighbour(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; @@ -101,6 +101,9 @@ public: void setZoneID(const int3& tile, TRmgTemplateZoneId zid); private: + int randomSeed; + CMapGenOptions& mapGenOptions; + std::list connectionsLeft; Zones zones; std::map zonesPerFaction; diff --git a/lib/rmg/CRmgTemplateStorage.cpp b/lib/rmg/CRmgTemplateStorage.cpp index 85626cf82..ac9747c0a 100644 --- a/lib/rmg/CRmgTemplateStorage.cpp +++ b/lib/rmg/CRmgTemplateStorage.cpp @@ -17,11 +17,6 @@ using namespace rmg; -const std::map & CRmgTemplateStorage::getTemplates() const -{ - return templates; -} - void CRmgTemplateStorage::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) { //unused @@ -30,31 +25,20 @@ void CRmgTemplateStorage::loadObject(std::string scope, std::string name, const void CRmgTemplateStorage::loadObject(std::string scope, std::string name, const JsonNode & data) { - auto tpl = new CRmgTemplate(); try { JsonDeserializer handler(nullptr, data); - auto fullKey = normalizeIdentifier(scope, "core", name); - tpl->setId(name); - tpl->serializeJson(handler); - tpl->validate(); - templates[fullKey] = tpl; + auto fullKey = normalizeIdentifier(scope, "core", name); //actually it's not used + templates[fullKey].setId(name); + templates[fullKey].serializeJson(handler); + templates[fullKey].validate(); } catch(const std::exception & e) { - logGlobal->error("Template %s has errors. Message: %s.", tpl->getName(), std::string(e.what())); + logGlobal->error("Template %s has errors. Message: %s.", name, std::string(e.what())); } } -CRmgTemplateStorage::CRmgTemplateStorage() -{ -} - -CRmgTemplateStorage::~CRmgTemplateStorage() -{ - for (auto & pair : templates) delete pair.second; -} - std::vector CRmgTemplateStorage::getDefaultAllowed() const { //all templates are allowed @@ -66,3 +50,54 @@ std::vector CRmgTemplateStorage::loadLegacyData(size_t dataSize) return std::vector(); //it would be cool to load old rmg.txt files } + +const CRmgTemplate * CRmgTemplateStorage::getTemplate(const std::string & templateName) const +{ + auto iter = templates.find(templateName); + if(iter==templates.end()) + return nullptr; + return &iter->second; +} + +std::vector CRmgTemplateStorage::getTemplates() const +{ + std::vector result; + for(auto i=templates.cbegin(); i!=templates.cend(); ++i) + { + result.push_back(&i->second); + } + 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 f2b27ce4e..2e0ef1f25 100644 --- a/lib/rmg/CRmgTemplateStorage.h +++ b/lib/rmg/CRmgTemplateStorage.h @@ -11,27 +11,29 @@ #pragma once #include "../IHandlerBase.h" +#include "../int3.h" +#include "CRmgTemplate.h" class JsonNode; -class CRmgTemplate; /// The CJsonRmgTemplateLoader loads templates from a JSON file. class DLL_LINKAGE CRmgTemplateStorage : public IHandlerBase { public: - CRmgTemplateStorage(); - ~CRmgTemplateStorage(); - - const std::map & getTemplates() const; - + CRmgTemplateStorage() = default; + std::vector getDefaultAllowed() const override; std::vector loadLegacyData(size_t dataSize) override; /// loads single object into game. Scope is namespace of this object, same as name of source mod 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; + std::vector getTemplates() const; + std::vector getTemplates(const int3& filterSize, si8 filterPlayers, si8 filterHumanPlayers, si8 filterCpuPlayers) const; private: - std::map templates; + std::map templates; }; diff --git a/lib/rmg/CRmgTemplateZone.cpp b/lib/rmg/CRmgTemplateZone.cpp index afb0dc791..b8b1e47b9 100644 --- a/lib/rmg/CRmgTemplateZone.cpp +++ b/lib/rmg/CRmgTemplateZone.cpp @@ -103,27 +103,27 @@ void CTileInfo::setRoadType(ERoadType::ERoadType value) } -CRmgTemplateZone::CRmgTemplateZone() +CRmgTemplateZone::CRmgTemplateZone(CMapGenerator * Gen) : ZoneOptions(), townType(ETownType::NEUTRAL), terrainType (ETerrainType::GRASS), minGuardedValue(0), questArtZone(), - gen(nullptr) + gen(Gen) { } +bool CRmgTemplateZone::isUnderground() const +{ + return getPos().z; +} + void CRmgTemplateZone::setOptions(const ZoneOptions * options) { ZoneOptions::operator=(*options); } -void CRmgTemplateZone::setGenPtr(CMapGenerator * Gen) -{ - gen = Gen; -} - void CRmgTemplateZone::setQuestArtZone(std::shared_ptr otherZone) { questArtZone = otherZone; @@ -384,7 +384,7 @@ void CRmgTemplateZone::fractalize() #define PRINT_FRACTALIZED_MAP false if (PRINT_FRACTALIZED_MAP) //enable to debug { - std::ofstream out(boost::to_string(boost::format("zone %d") % id)); + 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; @@ -618,7 +618,7 @@ bool CRmgTemplateZone::createRoad(const int3& src, const int3& dst) if (!directNeighbourFound) { movementCost = 2.1f; //moving diagonally is penalized over moving two tiles straight - gen->foreachDiagonaltNeighbour(currentNode, foo); + gen->foreachDiagonalNeighbour(currentNode, foo); } } @@ -805,7 +805,7 @@ bool CRmgTemplateZone::addMonster(int3 &pos, si32 strength, bool clearSurroundin //precalculate actual (randomized) monster strength based on this post //http://forum.vcmi.eu/viewtopic.php?p=12426#12426 - int mapMonsterStrength = gen->mapGenOptions->getMonsterStrength(); + 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}; @@ -1181,10 +1181,10 @@ void CRmgTemplateZone::initTownType () if (playerInfo.canAnyonePlay()) { player = PlayerColor(player_id); - townType = gen->mapGenOptions->getPlayersSettings().find(player)->second.getStartingTown(); + townType = gen->getMapGenOptions().getPlayersSettings().find(player)->second.getStartingTown(); if (townType == CMapGenOptions::CPlayerSettings::RANDOM_TOWN) - randomizeTownType(); + randomizeTownType(true); } else //no player - randomize town { @@ -1261,12 +1261,25 @@ void CRmgTemplateZone::initTownType () } } -void CRmgTemplateZone::randomizeTownType () +void CRmgTemplateZone::randomizeTownType(bool matchUndergroundType) { - if (townTypes.size()) - townType = *RandomGeneratorUtil::nextItem(townTypes, gen->rand); - else - townType = *RandomGeneratorUtil::nextItem(getDefaultTownTypes(), gen->rand); //it is possible to have zone with no towns allowed, we still need some + 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 () @@ -1278,7 +1291,7 @@ void CRmgTemplateZone::initTerrainType () terrainType = *RandomGeneratorUtil::nextItem(terrainTypes, gen->rand); //TODO: allow new types of terrain? - if (pos.z) + if (isUnderground()) { if (terrainType != ETerrainType::LAVA) terrainType = ETerrainType::SUBTERRANEAN; @@ -1295,8 +1308,8 @@ void CRmgTemplateZone::initTerrainType () void CRmgTemplateZone::paintZoneTerrain (ETerrainType terrainType) { std::vector tiles(tileinfo.begin(), tileinfo.end()); - gen->editManager->getTerrainSelection().setSelection(tiles); - gen->editManager->drawTerrain(terrainType, &gen->rand); + gen->getEditManager()->getTerrainSelection().setSelection(tiles); + gen->getEditManager()->drawTerrain(terrainType, &gen->rand); } bool CRmgTemplateZone::placeMines () @@ -1493,7 +1506,7 @@ bool CRmgTemplateZone::createRequiredObjects() void CRmgTemplateZone::createTreasures() { - int mapMonsterStrength = gen->mapGenOptions->getMonsterStrength(); + int mapMonsterStrength = gen->getMapGenOptions().getMonsterStrength(); int monsterStrength = zoneMonsterStrength + mapMonsterStrength - 1; //array index from 0 to 4 static int minGuardedValues[] = { 6500, 4167, 3000, 1833, 1333 }; @@ -1570,8 +1583,8 @@ void CRmgTemplateZone::createObstacles1() accessibleTiles.push_back(tile); } } - gen->editManager->getTerrainSelection().setSelection(accessibleTiles); - gen->editManager->drawTerrain(terrainType, &gen->rand); + gen->getEditManager()->getTerrainSelection().setSelection(accessibleTiles); + gen->getEditManager()->drawTerrain(terrainType, &gen->rand); } } @@ -1610,7 +1623,7 @@ void CRmgTemplateZone::createObstacles2() return p1.first > p2.first; //bigger obstacles first }); - auto sel = gen->editManager->getTerrainSelection(); + auto sel = gen->getEditManager()->getTerrainSelection(); sel.clearSelection(); auto tryToPlaceObstacleHere = [this, &possibleObstacles](int3& tile, int index)-> bool @@ -1705,8 +1718,8 @@ void CRmgTemplateZone::drawRoads() tiles.push_back(tile); } - gen->editManager->getTerrainSelection().setSelection(tiles); - gen->editManager->drawRoad(ERoadType::COBBLESTONE_ROAD, &gen->rand); + gen->getEditManager()->getTerrainSelection().setSelection(tiles); + gen->getEditManager()->drawRoad(ERoadType::COBBLESTONE_ROAD, &gen->rand); } @@ -1903,7 +1916,7 @@ void CRmgTemplateZone::checkAndPlaceObject(CGObjectInstance* object, const int3 object->appearance = templates.front(); } - gen->editManager->insertObject(object); + gen->getEditManager()->insertObject(object); } void CRmgTemplateZone::placeObject(CGObjectInstance* object, const int3 &pos, bool updateDistance) diff --git a/lib/rmg/CRmgTemplateZone.h b/lib/rmg/CRmgTemplateZone.h index 31018abe9..2b6f977bb 100644 --- a/lib/rmg/CRmgTemplateZone.h +++ b/lib/rmg/CRmgTemplateZone.h @@ -89,11 +89,10 @@ struct DLL_LINKAGE CTreasurePileInfo class DLL_LINKAGE CRmgTemplateZone : public rmg::ZoneOptions { public: - CRmgTemplateZone(); + CRmgTemplateZone(CMapGenerator * Gen); void setOptions(const rmg::ZoneOptions * options); - - void setGenPtr(CMapGenerator * Gen); + bool isUnderground() const; float3 getCenter() const; void setCenter(const float3 &f); @@ -119,7 +118,7 @@ public: bool placeMines (); void initTownType (); void paintZoneTerrain (ETerrainType terrainType); - void randomizeTownType(); //helper function + void randomizeTownType(bool matchUndergroundType = false); //helper function void initTerrainType (); void createBorder(); void fractalize(); @@ -170,7 +169,7 @@ private: std::vector possibleObjects; int minGuardedValue; - + //content info std::vector> requiredObjects; std::vector> closeObjects; diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index e7d70b7b6..375a8b4d1 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -40,15 +40,15 @@ float CZonePlacer::getDistance (float distance) const return (distance ? distance * distance : 1e-6f); } -void CZonePlacer::placeZones(const CMapGenOptions * mapGenOptions, CRandomGenerator * rand) +void CZonePlacer::placeZones(CRandomGenerator * rand) { logGlobal->info("Starting zone placement"); - width = mapGenOptions->getWidth(); - height = mapGenOptions->getHeight(); + width = gen->getMapGenOptions().getWidth(); + height = gen->getMapGenOptions().getHeight(); auto zones = gen->getZones(); - bool underground = mapGenOptions->getHasTwoLevels(); + bool underground = gen->getMapGenOptions().getHasTwoLevels(); /* gravity-based algorithm @@ -174,7 +174,7 @@ void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const if (boost::optional owner = zone.second->getOwner()) { auto player = PlayerColor(*owner - 1); - auto playerSettings = gen->mapGenOptions->getPlayersSettings(); + auto playerSettings = gen->getMapGenOptions().getPlayersSettings(); si32 faction = CMapGenOptions::CPlayerSettings::RANDOM_TOWN; if (vstd::contains(playerSettings, player)) faction = playerSettings[player].getStartingTown(); @@ -453,12 +453,12 @@ 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(const CMapGenOptions * mapGenOptions) +void CZonePlacer::assignZones() { logGlobal->info("Starting zone colouring"); - auto width = mapGenOptions->getWidth(); - auto height = mapGenOptions->getHeight(); + auto width = gen->getMapGenOptions().getWidth(); + auto height = gen->getMapGenOptions().getHeight(); //scale to Medium map to ensure smooth results scaleX = 72.f / width; @@ -554,7 +554,7 @@ void CZonePlacer::assignZones(const CMapGenOptions * mapGenOptions) //TODO: similiar for islands #define CREATE_FULL_UNDERGROUND true //consider linking this with water amount - if (zone.second->getPos().z) + if (zone.second->isUnderground()) { if (!CREATE_FULL_UNDERGROUND) zone.second->discardDistantTiles((float)(zone.second->getSize() + 1)); diff --git a/lib/rmg/CZonePlacer.h b/lib/rmg/CZonePlacer.h index 6c7b87b50..86799a700 100644 --- a/lib/rmg/CZonePlacer.h +++ b/lib/rmg/CZonePlacer.h @@ -34,12 +34,14 @@ public: float getDistance(float distance) const; //additional scaling without 0 divison ~CZonePlacer(); + void placeZones(CRandomGenerator * rand); + void assignZones(); + +private: void prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const bool underground, CRandomGenerator * rand); void attractConnectedZones(TZoneMap &zones, TForceVector &forces, TDistanceVector &distances); void separateOverlappingZones(TZoneMap &zones, TForceVector &forces, TDistanceVector &overlaps); void moveOneZone(TZoneMap &zones, TForceVector &totalForces, TDistanceVector &distances, TDistanceVector &overlaps); - void placeZones(const CMapGenOptions * mapGenOptions, CRandomGenerator * rand); - void assignZones(const CMapGenOptions * mapGenOptions); private: int width; diff --git a/test/map/CMapFormatTest.cpp b/test/map/CMapFormatTest.cpp index d6219fc39..bd497f6ac 100644 --- a/test/map/CMapFormatTest.cpp +++ b/test/map/CMapFormatTest.cpp @@ -59,9 +59,9 @@ TEST(MapFormat, Random) opt.setPlayerTypeForStandardPlayer(PlayerColor(2), EPlayerType::AI); opt.setPlayerTypeForStandardPlayer(PlayerColor(3), EPlayerType::AI); - CMapGenerator gen; + CMapGenerator gen(opt, TEST_RANDOM_SEED); - std::unique_ptr initialMap = gen.generate(&opt, TEST_RANDOM_SEED); + std::unique_ptr initialMap = gen.generate(); initialMap->name = "Test"; SCOPED_TRACE("MapFormat_Random generated");