diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index 810e53fe2..c9d72aa24 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -1,14 +1,3 @@ - -/* - * CMapGenerator.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 "CMapGenerator.h" @@ -20,32 +9,420 @@ #include "../CDefObjInfoHandler.h" #include "../CTownHandler.h" #include "../StringConstants.h" -#include "CRmgTemplate.h" +#include "../filesystem/Filesystem.h" -CMapGenerator::CMapGenerator() : mapGenOptions(nullptr), randomSeed(0) +CMapGenOptions::CMapGenOptions() : width(CMapHeader::MAP_SIZE_MIDDLE), height(CMapHeader::MAP_SIZE_MIDDLE), hasTwoLevels(false), + playerCount(RANDOM_SIZE), teamCount(RANDOM_SIZE), compOnlyPlayerCount(0), compOnlyTeamCount(RANDOM_SIZE), + waterContent(EWaterContent::RANDOM), monsterStrength(EMonsterStrength::RANDOM), mapTemplate(nullptr) +{ + resetPlayersMap(); +} + +si32 CMapGenOptions::getWidth() const +{ + return width; +} + +void CMapGenOptions::setWidth(si32 value) +{ + assert(value >= 1); + width = value; +} + +si32 CMapGenOptions::getHeight() const +{ + return height; +} + +void CMapGenOptions::setHeight(si32 value) +{ + assert(value >= 1); + height = value; +} + +bool CMapGenOptions::getHasTwoLevels() const +{ + return hasTwoLevels; +} + +void CMapGenOptions::setHasTwoLevels(bool value) +{ + hasTwoLevels = value; +} + +si8 CMapGenOptions::getPlayerCount() const +{ + return playerCount; +} + +void CMapGenOptions::setPlayerCount(si8 value) +{ + assert((value >= 1 && value <= PlayerColor::PLAYER_LIMIT_I) || value == RANDOM_SIZE); + playerCount = value; + resetPlayersMap(); +} + +si8 CMapGenOptions::getTeamCount() const +{ + return teamCount; +} + +void CMapGenOptions::setTeamCount(si8 value) +{ + assert(playerCount == RANDOM_SIZE || (value >= 0 && value < playerCount) || value == RANDOM_SIZE); + teamCount = value; +} + +si8 CMapGenOptions::getCompOnlyPlayerCount() const +{ + return compOnlyPlayerCount; +} + +void CMapGenOptions::setCompOnlyPlayerCount(si8 value) +{ + assert(value == RANDOM_SIZE || (value >= 0 && value <= PlayerColor::PLAYER_LIMIT_I - playerCount)); + compOnlyPlayerCount = value; + resetPlayersMap(); +} + +si8 CMapGenOptions::getCompOnlyTeamCount() const +{ + return compOnlyTeamCount; +} + +void CMapGenOptions::setCompOnlyTeamCount(si8 value) +{ + assert(value == RANDOM_SIZE || compOnlyPlayerCount == RANDOM_SIZE || (value >= 0 && value <= std::max(compOnlyPlayerCount - 1, 0))); + compOnlyTeamCount = value; +} + +EWaterContent::EWaterContent CMapGenOptions::getWaterContent() const +{ + return waterContent; +} + +void CMapGenOptions::setWaterContent(EWaterContent::EWaterContent value) +{ + waterContent = value; +} + +EMonsterStrength::EMonsterStrength CMapGenOptions::getMonsterStrength() const +{ + return monsterStrength; +} + +void CMapGenOptions::setMonsterStrength(EMonsterStrength::EMonsterStrength value) +{ + monsterStrength = value; +} + +void CMapGenOptions::resetPlayersMap() +{ + players.clear(); + int realPlayersCnt = playerCount == RANDOM_SIZE ? static_cast(PlayerColor::PLAYER_LIMIT_I) : playerCount; + int realCompOnlyPlayersCnt = compOnlyPlayerCount == RANDOM_SIZE ? (PlayerColor::PLAYER_LIMIT_I - realPlayersCnt) : compOnlyPlayerCount; + for(int color = 0; color < (realPlayersCnt + realCompOnlyPlayersCnt); ++color) + { + CPlayerSettings player; + player.setColor(PlayerColor(color)); + player.setPlayerType((color >= realPlayersCnt) ? EPlayerType::COMP_ONLY : EPlayerType::AI); + players[PlayerColor(color)] = player; + } +} + +const std::map & CMapGenOptions::getPlayersSettings() const +{ + return players; +} + +void CMapGenOptions::setStartingTownForPlayer(PlayerColor color, si32 town) +{ + auto it = players.find(color); + if(it == players.end()) assert(0); + it->second.setStartingTown(town); +} + +void CMapGenOptions::setPlayerTypeForStandardPlayer(PlayerColor color, EPlayerType::EPlayerType playerType) +{ + assert(playerType != EPlayerType::COMP_ONLY); + auto it = players.find(color); + if(it == players.end()) assert(0); + it->second.setPlayerType(playerType); +} + +const CRmgTemplate * CMapGenOptions::getMapTemplate() const +{ + return mapTemplate; +} + +void CMapGenOptions::setMapTemplate(const CRmgTemplate * value) +{ + mapTemplate = value; + //TODO validate & adapt options according to template + assert(0); +} + +const std::map & CMapGenOptions::getAvailableTemplates() const +{ + return CRmgTemplateStorage::get().getTemplates(); +} + +void CMapGenOptions::finalize() +{ + CRandomGenerator gen; + finalize(gen); +} + +void CMapGenOptions::finalize(CRandomGenerator & gen) +{ + if(!mapTemplate) + { + mapTemplate = getPossibleTemplate(gen); + assert(mapTemplate); + } + + if(playerCount == RANDOM_SIZE) + { + auto possiblePlayers = mapTemplate->getPlayers().getNumbers(); + possiblePlayers.erase(possiblePlayers.begin(), possiblePlayers.lower_bound(countHumanPlayers())); + assert(!possiblePlayers.empty()); + playerCount = *std::next(possiblePlayers.begin(), gen.getInteger(0, possiblePlayers.size() - 1)); + updatePlayers(); + } + if(teamCount == RANDOM_SIZE) + { + teamCount = gen.getInteger(0, playerCount - 1); + } + if(compOnlyPlayerCount == RANDOM_SIZE) + { + auto possiblePlayers = mapTemplate->getCpuPlayers().getNumbers(); + compOnlyPlayerCount = *std::next(possiblePlayers.begin(), gen.getInteger(0, possiblePlayers.size() - 1)); + updateCompOnlyPlayers(); + } + if(compOnlyTeamCount == RANDOM_SIZE) + { + compOnlyTeamCount = gen.getInteger(0, std::max(compOnlyPlayerCount - 1, 0)); + } + + // 1 team isn't allowed + if(teamCount == 1 && compOnlyPlayerCount == 0) + { + teamCount = 0; + } + + if(waterContent == EWaterContent::RANDOM) + { + waterContent = static_cast(gen.getInteger(0, 2)); + } + if(monsterStrength == EMonsterStrength::RANDOM) + { + monsterStrength = static_cast(gen.getInteger(0, 2)); + } +} + +void CMapGenOptions::updatePlayers() +{ + // Remove AI players only from the end of the players map if necessary + for(auto itrev = players.end(); itrev != players.begin();) + { + auto it = itrev; + --it; + if(players.size() == playerCount) break; + if(it->second.getPlayerType() == EPlayerType::AI) + { + players.erase(it); + } + else + { + --itrev; + } + } +} + +void CMapGenOptions::updateCompOnlyPlayers() +{ + auto totalPlayersCnt = playerCount + compOnlyPlayerCount; + + // Remove comp only players only from the end of the players map if necessary + for(auto itrev = players.end(); itrev != players.begin();) + { + auto it = itrev; + --it; + if(players.size() <= totalPlayersCnt) break; + if(it->second.getPlayerType() == EPlayerType::COMP_ONLY) + { + players.erase(it); + } + else + { + --itrev; + } + } + + // Add some comp only players if necessary + auto compOnlyPlayersToAdd = totalPlayersCnt - players.size(); + for(int i = 0; i < compOnlyPlayersToAdd; ++i) + { + CPlayerSettings pSettings; + pSettings.setPlayerType(EPlayerType::COMP_ONLY); + pSettings.setColor(getNextPlayerColor()); + players[pSettings.getColor()] = pSettings; + } +} + +int CMapGenOptions::countHumanPlayers() const +{ + return static_cast(boost::count_if(players, [](const std::pair & pair) + { + return pair.second.getPlayerType() == EPlayerType::HUMAN; + })); +} + +PlayerColor CMapGenOptions::getNextPlayerColor() const +{ + for(PlayerColor i = PlayerColor(0); i < PlayerColor::PLAYER_LIMIT; i.advance(1)) + { + if(!players.count(i)) + { + return i; + } + } + assert(0); + return PlayerColor(0); +} + +bool CMapGenOptions::checkOptions() const +{ + assert(countHumanPlayers() > 0); + if(mapTemplate) + { + return true; + } + else + { + CRandomGenerator gen; + return getPossibleTemplate(gen) != nullptr; + } +} + +const CRmgTemplate * CMapGenOptions::getPossibleTemplate(CRandomGenerator & gen) const +{ + // Find potential templates + const auto & tpls = getAvailableTemplates(); + std::list potentialTpls; + for(const auto & tplPair : tpls) + { + const auto & tpl = tplPair.second; + CRmgTemplate::CSize tplSize(width, height, hasTwoLevels); + if(tplSize >= tpl.getMinSize() && tplSize <= tpl.getMaxSize()) + { + bool isPlayerCountValid = false; + if(playerCount != RANDOM_SIZE) + { + if(tpl.getPlayers().isInRange(playerCount)) isPlayerCountValid = true; + } + else + { + // Human players shouldn't be banned when playing with random player count + auto playerNumbers = tpl.getPlayers().getNumbers(); + if(playerNumbers.lower_bound(countHumanPlayers()) != playerNumbers.end()) + { + 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); + } + } + } + + // Select tpl + if(potentialTpls.empty()) + { + return nullptr; + } + else + { + return *std::next(potentialTpls.begin(), gen.getInteger(0, potentialTpls.size() - 1)); + } +} + +CMapGenOptions::CPlayerSettings::CPlayerSettings() : color(0), startingTown(RANDOM_TOWN), playerType(EPlayerType::AI) { } +PlayerColor CMapGenOptions::CPlayerSettings::getColor() const +{ + return color; +} + +void CMapGenOptions::CPlayerSettings::setColor(PlayerColor value) +{ + assert(value >= PlayerColor(0) && value < PlayerColor::PLAYER_LIMIT); + color = value; +} + +si32 CMapGenOptions::CPlayerSettings::getStartingTown() const +{ + return startingTown; +} + +void CMapGenOptions::CPlayerSettings::setStartingTown(si32 value) +{ + assert(value >= -1); + if(value >= 0) + { + assert(value < static_cast(VLC->townh->factions.size())); + assert(VLC->townh->factions[value]->town != nullptr); + } + startingTown = value; +} + +EPlayerType::EPlayerType CMapGenOptions::CPlayerSettings::getPlayerType() const +{ + return playerType; +} + +void CMapGenOptions::CPlayerSettings::setPlayerType(EPlayerType::EPlayerType value) +{ + playerType = value; +} + +CMapGenerator::CMapGenerator(const CMapGenOptions & mapGenOptions, int randomSeed /*= std::time(nullptr)*/) : + mapGenOptions(mapGenOptions), randomSeed(randomSeed) +{ + gen.seed(randomSeed); +} + CMapGenerator::~CMapGenerator() { } -std::unique_ptr CMapGenerator::generate(CMapGenOptions * mapGenOptions, int randomSeed /*= std::time(nullptr)*/) +std::unique_ptr CMapGenerator::generate() { - this->randomSeed = randomSeed; - rand.setSeed(this->randomSeed); - this->mapGenOptions = mapGenOptions; - this->mapGenOptions->finalize(rand); + mapGenOptions.finalize(gen); map = make_unique(); editManager = map->getEditManager(); editManager->getUndoManager().setUndoRedoLimit(0); addHeaderInfo(); - genTerrain(); - genTowns(); + genZones(); + fillZones(); return std::move(map); } @@ -57,12 +434,12 @@ 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, humans %d, computers %d, water %s, monster %s, second expansion map") % mapGenOptions->getMapTemplate()->getName() % - randomSeed % map->width % map->height % (map->twoLevel ? "2" : "1") % static_cast(mapGenOptions->getPlayerCount()) % - static_cast(mapGenOptions->getCompOnlyPlayerCount()) % waterContentStr[mapGenOptions->getWaterContent()] % - monsterStrengthStr[mapGenOptions->getMonsterStrength()]); + ", levels %s, humans %d, computers %d, water %s, monster %s, second expansion map") % mapGenOptions.getMapTemplate()->getName() % + randomSeed % map->width % map->height % (map->twoLevel ? "2" : "1") % static_cast(mapGenOptions.getPlayerCount()) % + static_cast(mapGenOptions.getCompOnlyPlayerCount()) % waterContentStr[mapGenOptions.getWaterContent()] % + monsterStrengthStr[mapGenOptions.getMonsterStrength()]); - for(const auto & pair : mapGenOptions->getPlayersSettings()) + for(const auto & pair : mapGenOptions.getPlayersSettings()) { const auto & pSettings = pair.second; if(pSettings.getPlayerType() == EPlayerType::HUMAN) @@ -72,7 +449,7 @@ std::string CMapGenerator::getMapDescription() const if(pSettings.getStartingTown() != CMapGenOptions::CPlayerSettings::RANDOM_TOWN) { ss << ", " << GameConstants::PLAYER_COLOR_NAMES[pSettings.getColor().getNum()] - << " town choice is " << VLC->townh->factions[pSettings.getStartingTown()]->name; + << " town choice is " << ETownType::names[pSettings.getStartingTown()]; } } @@ -86,8 +463,8 @@ void CMapGenerator::addPlayerInfo() int teamOffset = 0; for(int i = 0; i < 2; ++i) { - int playerCount = i == 0 ? mapGenOptions->getPlayerCount() : mapGenOptions->getCompOnlyPlayerCount(); - int teamCount = i == 0 ? mapGenOptions->getTeamCount() : mapGenOptions->getCompOnlyTeamCount(); + int playerCount = i == 0 ? mapGenOptions.getPlayerCount() : mapGenOptions.getCompOnlyPlayerCount(); + int teamCount = i == 0 ? mapGenOptions.getTeamCount() : mapGenOptions.getCompOnlyTeamCount(); if(playerCount == 0) { @@ -115,7 +492,7 @@ void CMapGenerator::addPlayerInfo() } // Team numbers are assigned randomly to every player - for(const auto & pair : mapGenOptions->getPlayersSettings()) + for(const auto & pair : mapGenOptions.getPlayersSettings()) { const auto & pSettings = pair.second; PlayerInfo player; @@ -125,69 +502,1037 @@ void CMapGenerator::addPlayerInfo() { player.canHumanPlay = true; } - - auto itTeam = RandomGeneratorUtil::nextItem(teamNumbers[j], rand); + auto itTeam = std::next(teamNumbers[j].begin(), gen.getInteger(0, teamNumbers[j].size() - 1)); 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::genTerrain() +void CMapGenerator::genZones() { map->initTerrain(); - editManager->clearTerrain(&rand); - editManager->getTerrainSelection().selectRange(MapRect(int3(4, 4, 0), 24, 30)); - editManager->drawTerrain(ETerrainType::GRASS, &rand); + editManager->clearTerrain(&gen); + editManager->getTerrainSelection().selectRange(MapRect(int3(0, 0, 0), mapGenOptions.getWidth(), mapGenOptions.getHeight())); + editManager->drawTerrain(ETerrainType::GRASS, &gen); + + auto pcnt = mapGenOptions.getPlayerCount(); + auto w = mapGenOptions.getWidth(); + auto h = mapGenOptions.getHeight(); + + + auto tmpl = mapGenOptions.getMapTemplate(); + auto zones = tmpl->getZones(); + + int player_per_side = zones.size() > 4 ? 3 : 2; + int zones_cnt = zones.size() > 4 ? 9 : 4; + + logGlobal->infoStream() << boost::format("Map size %d %d, players per side %d") % w % h % player_per_side; + + int i = 0; + int part_w = w/player_per_side; + int part_h = h/player_per_side; + for(auto const it : zones) + { + CRmgTemplateZone zone = it.second; + std::vector shape; + int left = part_w*(i%player_per_side); + int top = part_h*(i/player_per_side); + shape.push_back(int3(left, top, 0)); + shape.push_back(int3(left + part_w, top, 0)); + shape.push_back(int3(left + part_w, top + part_h, 0)); + shape.push_back(int3(left, top + part_h, 0)); + zone.setShape(shape); + zone.setType(i < pcnt ? ETemplateZoneType::PLAYER_START : ETemplateZoneType::TREASURE); + this->zones[it.first] = zone; + ++i; + } + logGlobal->infoStream() << "Zones generated successfully"; } -void CMapGenerator::genTowns() -{ - //FIXME mock gen - const int3 townPos[2] = { int3(11, 7, 0), int3(19,7, 0) }; - - for(size_t i = 0; i < map->players.size(); ++i) +void CMapGenerator::fillZones() +{ + logGlobal->infoStream() << "Started filling zones"; + for(auto it = zones.begin(); it != zones.end(); ++it) { - auto & playerInfo = map->players[i]; - if(!playerInfo.canAnyonePlay()) break; - - PlayerColor owner(i); - int side = i % 2; - auto town = new CGTownInstance(); - town->ID = Obj::TOWN; - int townId = mapGenOptions->getPlayersSettings().find(PlayerColor(i))->second.getStartingTown(); - if(townId == CMapGenOptions::CPlayerSettings::RANDOM_TOWN) - { - // select default towns - townId = rand.nextInt(8); - } - town->subID = townId; - town->tempOwner = owner; - town->appearance = VLC->dobjinfo->pickCandidates(town->ID, town->subID, map->getTile(townPos[side]).terType).front(); - town->builtBuildings.insert(BuildingID::FORT); - town->builtBuildings.insert(BuildingID::DEFAULT); - editManager->insertObject(town, int3(townPos[side].x, townPos[side].y + (i / 2) * 5, 0)); - - // Update player info - playerInfo.allowedFactions.clear(); - playerInfo.allowedFactions.insert(townId); - playerInfo.hasMainTown = true; - playerInfo.posOfMainTown = town->pos - int3(2, 0, 0); - playerInfo.generateHeroAtMainTown = true; - } + it->second.fill(this); + } + logGlobal->infoStream() << "Zones filled successfully"; } void CMapGenerator::addHeaderInfo() { map->version = EMapFormat::SOD; - 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; addPlayerInfo(); } + +CRmgTemplateZone::CTownInfo::CTownInfo() : townCount(0), castleCount(0), townDensity(0), castleDensity(0) +{ + +} + +int CRmgTemplateZone::CTownInfo::getTownCount() const +{ + return townCount; +} + +void CRmgTemplateZone::CTownInfo::setTownCount(int value) +{ + if(value < 0) throw std::runtime_error("Negative value for town count not allowed."); + townCount = value; +} + +int CRmgTemplateZone::CTownInfo::getCastleCount() const +{ + return castleCount; +} + +void CRmgTemplateZone::CTownInfo::setCastleCount(int value) +{ + if(value < 0) throw std::runtime_error("Negative value for castle count not allowed."); + castleCount = value; +} + +int CRmgTemplateZone::CTownInfo::getTownDensity() const +{ + return townDensity; +} + +void CRmgTemplateZone::CTownInfo::setTownDensity(int value) +{ + if(value < 0) throw std::runtime_error("Negative value for town density not allowed."); + townDensity = value; +} + +int CRmgTemplateZone::CTownInfo::getCastleDensity() const +{ + return castleDensity; +} + +void CRmgTemplateZone::CTownInfo::setCastleDensity(int value) +{ + if(value < 0) throw std::runtime_error("Negative value for castle density not allowed."); + castleDensity = value; +} + +CRmgTemplateZone::CTileInfo::CTileInfo():nearestObjectDistance(INT_MAX), obstacle(false), occupied(false), terrain(ETerrainType::WRONG) +{ + +} + +int CRmgTemplateZone::CTileInfo::getNearestObjectDistance() const +{ + return nearestObjectDistance; +} + +void CRmgTemplateZone::CTileInfo::setNearestObjectDistance(int value) +{ + if(value < 0) throw std::runtime_error("Negative value for nearest object distance not allowed."); + nearestObjectDistance = value; +} + +bool CRmgTemplateZone::CTileInfo::isObstacle() const +{ + return obstacle; +} + +void CRmgTemplateZone::CTileInfo::setObstacle(bool value) +{ + obstacle = value; +} + +bool CRmgTemplateZone::CTileInfo::isOccupied() const +{ + return occupied; +} + +void CRmgTemplateZone::CTileInfo::setOccupied(bool value) +{ + occupied = value; +} + +ETerrainType CRmgTemplateZone::CTileInfo::getTerrainType() const +{ + return terrain; +} + +void CRmgTemplateZone::CTileInfo::setTerrainType(ETerrainType value) +{ + terrain = value; +} + +CRmgTemplateZone::CRmgTemplateZone() : id(0), type(ETemplateZoneType::PLAYER_START), size(1), + townsAreSameType(false), matchTerrainToTown(true) +{ + townTypes = getDefaultTownTypes(); + terrainTypes = getDefaultTerrainTypes(); +} + +TRmgTemplateZoneId CRmgTemplateZone::getId() const +{ + return id; +} + +void CRmgTemplateZone::setId(TRmgTemplateZoneId value) +{ + if(value <= 0) throw std::runtime_error("Zone id should be greater than 0."); + id = value; +} + +ETemplateZoneType::ETemplateZoneType CRmgTemplateZone::getType() const +{ + return type; +} +void CRmgTemplateZone::setType(ETemplateZoneType::ETemplateZoneType value) +{ + type = value; +} + +int CRmgTemplateZone::getSize() const +{ + return size; +} + +void CRmgTemplateZone::setSize(int value) +{ + if(value <= 0) throw std::runtime_error("Zone size needs to be greater than 0."); + size = value; +} + +boost::optional CRmgTemplateZone::getOwner() const +{ + return owner; +} + +void CRmgTemplateZone::setOwner(boost::optional value) +{ + if(!(*value >= 0 && *value <= PlayerColor::PLAYER_LIMIT_I)) throw std::runtime_error("Owner has to be in range 0 to max player count."); + owner = value; +} + +const CRmgTemplateZone::CTownInfo & CRmgTemplateZone::getPlayerTowns() const +{ + return playerTowns; +} + +void CRmgTemplateZone::setPlayerTowns(const CTownInfo & value) +{ + playerTowns = value; +} + +const CRmgTemplateZone::CTownInfo & CRmgTemplateZone::getNeutralTowns() const +{ + return neutralTowns; +} + +void CRmgTemplateZone::setNeutralTowns(const CTownInfo & value) +{ + neutralTowns = value; +} + +bool CRmgTemplateZone::getTownsAreSameType() const +{ + return townsAreSameType; +} + +void CRmgTemplateZone::setTownsAreSameType(bool value) +{ + townsAreSameType = value; +} + +const std::set & CRmgTemplateZone::getTownTypes() const +{ + return townTypes; +} + +void CRmgTemplateZone::setTownTypes(const std::set & value) +{ + townTypes = value; +} + +std::set CRmgTemplateZone::getDefaultTownTypes() const +{ + std::set defaultTowns; + auto towns = VLC->townh->getDefaultAllowed(); + for(int i = 0; i < towns.size(); ++i) + { + if(towns[i]) defaultTowns.insert(i); + } + return defaultTowns; +} + +bool CRmgTemplateZone::getMatchTerrainToTown() const +{ + return matchTerrainToTown; +} + +void CRmgTemplateZone::setMatchTerrainToTown(bool value) +{ + matchTerrainToTown = value; +} + +const std::set & CRmgTemplateZone::getTerrainTypes() const +{ + return terrainTypes; +} + +void CRmgTemplateZone::setTerrainTypes(const std::set & value) +{ + assert(value.find(ETerrainType::WRONG) == value.end() && value.find(ETerrainType::BORDER) == value.end() && + value.find(ETerrainType::WATER) == value.end() && value.find(ETerrainType::ROCK) == value.end()); + terrainTypes = value; +} + +std::set CRmgTemplateZone::getDefaultTerrainTypes() const +{ + std::set terTypes; + static const ETerrainType::EETerrainType allowedTerTypes[] = { ETerrainType::DIRT, ETerrainType::SAND, ETerrainType::GRASS, ETerrainType::SNOW, + ETerrainType::SWAMP, ETerrainType::ROUGH, ETerrainType::SUBTERRANEAN, ETerrainType::LAVA }; + for(auto & allowedTerType : allowedTerTypes) terTypes.insert(allowedTerType); + return terTypes; +} + +boost::optional CRmgTemplateZone::getTerrainTypeLikeZone() const +{ + return terrainTypeLikeZone; +} + +void CRmgTemplateZone::setTerrainTypeLikeZone(boost::optional value) +{ + terrainTypeLikeZone = value; +} + +boost::optional CRmgTemplateZone::getTownTypeLikeZone() const +{ + return townTypeLikeZone; +} + +void CRmgTemplateZone::setTownTypeLikeZone(boost::optional value) +{ + townTypeLikeZone = value; +} + +bool CRmgTemplateZone::pointIsIn(int x, int y) +{ + int i, j; + bool c = false; + int nvert = shape.size(); + for (i = 0, j = nvert-1; i < nvert; j = i++) { + if ( ((shape[i].y>y) != (shape[j].y>y)) && + (x < (shape[j].x-shape[i].x) * (y-shape[i].y) / (shape[j].y-shape[i].y) + shape[i].x) ) + c = !c; + } + return c; +} + +void CRmgTemplateZone::setShape(std::vector shape) +{ + int z = -1; + si32 minx = INT_MAX; + si32 maxx = -1; + si32 miny = INT_MAX; + si32 maxy = -1; + for(auto &point : shape) + { + if (z == -1) + z = point.z; + if (point.z != z) + throw std::runtime_error("Zone shape points should lie on same z."); + minx = std::min(minx, point.x); + maxx = std::max(maxx, point.x); + miny = std::min(miny, point.y); + maxy = std::max(maxy, point.y); + } + this->shape = shape; + for(int x = minx; x <= maxx; ++x) + { + for(int y = miny; y <= maxy; ++y) + { + if (pointIsIn(x, y)) + { + tileinfo[int3(x,y,z)] = CTileInfo(); + } + } + } +} + +int3 CRmgTemplateZone::getCenter() +{ + si32 cx = 0; + si32 cy = 0; + si32 area = 0; + si32 sz = shape.size(); + //include last->first too + for(si32 i = 0, j = sz-1; i < sz; j = i++) { + si32 sf = (shape[i].x * shape[j].y - shape[j].x * shape[i].y); + cx += (shape[i].x + shape[j].x) * sf; + cy += (shape[i].y + shape[j].y) * sf; + area += sf; + } + area /= 2; + return int3(std::abs(cx/area/6), std::abs(cy/area/6), shape[0].z); +} + +bool CRmgTemplateZone::fill(CMapGenerator* gen) +{ + std::vector required_objects; + if ((type == ETemplateZoneType::CPU_START) || (type == ETemplateZoneType::PLAYER_START)) + { + logGlobal->infoStream() << "Preparing playing zone"; + int player_id = *owner - 1; + auto & playerInfo = gen->map->players[player_id]; + if (playerInfo.canAnyonePlay()) + { + PlayerColor player(player_id); + auto town = new CGTownInstance(); + town->ID = Obj::TOWN; + int townId = gen->mapGenOptions.getPlayersSettings().find(player)->second.getStartingTown(); + + static auto town_gen = gen->gen.getRangeI(0, 8); + + if(townId == CMapGenOptions::CPlayerSettings::RANDOM_TOWN) townId = town_gen(); // Default towns + town->subID = townId; + town->tempOwner = player; + town->defInfo = VLC->dobjinfo->gobjs[town->ID][town->subID]; + town->builtBuildings.insert(BuildingID::FORT); + town->builtBuildings.insert(BuildingID::DEFAULT); + + placeObject(gen, town, getCenter()); + logGlobal->infoStream() << "Placed object"; + + logGlobal->infoStream() << "Fill player info " << player_id; + auto & playerInfo = gen->map->players[player_id]; + // Update player info + playerInfo.allowedFactions.clear(); + playerInfo.allowedFactions.insert(town->subID); + playerInfo.hasMainTown = true; + playerInfo.posOfMainTown = town->pos - int3(2, 0, 0); + playerInfo.generateHeroAtMainTown = true; + + //required_objects.push_back(town); + + std::vector required_mines; + required_mines.push_back(Res::ERes::WOOD); + required_mines.push_back(Res::ERes::ORE); + + for(const auto res : required_mines) + { + auto mine = new CGMine(); + mine->ID = Obj::MINE; + mine->subID = static_cast(res); + mine->producedResource = res; + mine->producedQuantity = mine->defaultResProduction(); + mine->defInfo = VLC->dobjinfo->gobjs[mine->ID][mine->subID]; + required_objects.push_back(mine); + } + } + else + { + type = ETemplateZoneType::TREASURE; + logGlobal->infoStream() << "Skipping this zone cause no player"; + } + } + logGlobal->infoStream() << "Creating required objects"; + for(const auto &obj : required_objects) + { + int3 pos; + logGlobal->infoStream() << "Looking for place"; + if ( ! findPlaceForObject(gen, obj, 3, pos)) + { + logGlobal->errorStream() << "Failed to fill zone due to lack of space"; + //TODO CLEANUP! + return false; + } + logGlobal->infoStream() << "Place found"; + + placeObject(gen, obj, pos); + logGlobal->infoStream() << "Placed object"; + } + std::vector guarded_objects; + static auto res_gen = gen->gen.getRangeI(Res::ERes::WOOD, Res::ERes::GOLD); + const double res_mindist = 5; + do { + auto obj = new CGResource(); + auto restype = static_cast(res_gen()); + obj->ID = Obj::RESOURCE; + obj->subID = static_cast(restype); + obj->amount = 0; + obj->defInfo = VLC->dobjinfo->gobjs[obj->ID][obj->subID]; + + int3 pos; + if ( ! findPlaceForObject(gen, obj, res_mindist, pos)) + { + delete obj; + break; + } + placeObject(gen, obj, pos); + if ((restype != Res::ERes::WOOD) && (restype != Res::ERes::ORE)) + { + guarded_objects.push_back(obj); + } + } while(true); + + for(const auto &obj : guarded_objects) + { + if ( ! guardObject(gen, obj, 500)) + { + //TODO, DEL obj from map + } + } + + auto sel = gen->editManager->getTerrainSelection(); + sel.clearSelection(); + for(auto it = tileinfo.begin(); it != tileinfo.end(); ++it) + { + if (it->second.isObstacle()) + { + auto obj = new CGObjectInstance(); + obj->ID = static_cast(130); + obj->subID = 0; + obj->defInfo = VLC->dobjinfo->gobjs[obj->ID][obj->subID]; + placeObject(gen, obj, it->first); + } + } + logGlobal->infoStream() << boost::format("Filling %d with ROCK") % sel.getSelectedItems().size(); + //gen->editManager->drawTerrain(ETerrainType::ROCK, &gen->gen); + logGlobal->infoStream() << "Zone filled successfully"; + return true; +} + +bool CRmgTemplateZone::findPlaceForObject(CMapGenerator* gen, CGObjectInstance* obj, si32 min_dist, int3 &pos) +{ + //si32 min_dist = sqrt(tileinfo.size()/density); + int best_distance = 0; + bool result = false; + si32 w = gen->map->width; + si32 h = gen->map->height; + auto ow = obj->getWidth(); + auto oh = obj->getHeight(); + //logGlobal->infoStream() << boost::format("Min dist for density %f is %d") % density % min_dist; + for(auto it = tileinfo.begin(); it != tileinfo.end(); ++it) + { + auto &ti = it->second; + auto p = it->first; + auto dist = ti.getNearestObjectDistance(); + //avoid borders + if ((p.x < 3) || (w - p.x < 3) || (p.y < 3) || (h - p.y < 3)) + continue; + if (!ti.isOccupied() && !ti.isObstacle() && (dist >= min_dist) && (dist > best_distance)) + { + best_distance = dist; + pos = p; + result = true; + } + } + return result; +} + +void CRmgTemplateZone::placeObject(CMapGenerator* gen, CGObjectInstance* object, const int3 &pos) +{ + logGlobal->infoStream() << boost::format("Insert object at %d %d") % pos.x % pos.y; + object->pos = pos; + gen->editManager->insertObject(object, pos); + logGlobal->infoStream() << "Inserted object"; + auto points = object->getBlockedPos(); + if (object->isVisitable()) + points.emplace(pos + object->getVisitableOffset()); + points.emplace(pos); + for(auto const &p : points) + { + if (tileinfo.find(pos + p) != tileinfo.end()) + { + tileinfo[pos + p].setOccupied(true); + } + } + for(auto it = tileinfo.begin(); it != tileinfo.end(); ++it) + { + si32 d = pos.dist2d(it->first); + it->second.setNearestObjectDistance(std::min(d, it->second.getNearestObjectDistance())); + } +} + +bool CRmgTemplateZone::guardObject(CMapGenerator* gen, CGObjectInstance* object, si32 str) +{ + + logGlobal->infoStream() << boost::format("Guard object at %d %d") % object->pos.x % object->pos.y; + int3 visitable = object->pos + object->getVisitableOffset(); + std::vector tiles; + for(int i = -1; i < 2; ++i) + { + for(int j = -1; j < 2; ++j) + { + auto it = tileinfo.find(visitable + int3(i, j, 0)); + if (it != tileinfo.end()) + { + logGlobal->infoStream() << boost::format("Block at %d %d") % it->first.x % it->first.y; + if ( ! it->second.isOccupied() && ! it->second.isObstacle()) + { + tiles.push_back(it->first); + it->second.setObstacle(true); + } + } + } + } + if ( ! tiles.size()) + { + logGlobal->infoStream() << "Failed"; + return false; + } + auto guard_tile = *std::next(tiles.begin(), gen->gen.getInteger(0, tiles.size() - 1)); + tileinfo[guard_tile].setObstacle(false); + auto guard = new CGCreature(); + guard->ID = Obj::RANDOM_MONSTER; + guard->subID = 0; + auto hlp = new CStackInstance(); + hlp->count = 10; + //type will be set during initialization + guard->putStack(SlotID(0), hlp); + + guard->defInfo = VLC->dobjinfo->gobjs[guard->ID][guard->subID]; + guard->pos = guard_tile; + gen->editManager->insertObject(guard, guard->pos); + return true; +} + +CRmgTemplateZoneConnection::CRmgTemplateZoneConnection() : zoneA(0), zoneB(0), guardStrength(0) +{ + +} + +TRmgTemplateZoneId CRmgTemplateZoneConnection::getZoneA() const +{ + return zoneA; +} + +void CRmgTemplateZoneConnection::setZoneA(TRmgTemplateZoneId value) +{ + zoneA = value; +} + +TRmgTemplateZoneId CRmgTemplateZoneConnection::getZoneB() const +{ + return zoneB; +} + +void CRmgTemplateZoneConnection::setZoneB(TRmgTemplateZoneId value) +{ + zoneB = value; +} + +int CRmgTemplateZoneConnection::getGuardStrength() const +{ + return guardStrength; +} + +void CRmgTemplateZoneConnection::setGuardStrength(int value) +{ + if(value < 0) throw std::runtime_error("Negative value for guard strenth not allowed."); + guardStrength = value; +} + +CRmgTemplate::CSize::CSize() : width(CMapHeader::MAP_SIZE_MIDDLE), height(CMapHeader::MAP_SIZE_MIDDLE), under(true) +{ + +} + +CRmgTemplate::CSize::CSize(int width, int height, bool under) : under(under) +{ + setWidth(width); + setHeight(height); +} + +int CRmgTemplate::CSize::getWidth() const +{ + return width; +} + +void CRmgTemplate::CSize::setWidth(int value) +{ + if(value <= 0) throw std::runtime_error("Width > 0 failed."); + width = value; +} + +int CRmgTemplate::CSize::getHeight() const +{ + return height; +} + +void CRmgTemplate::CSize::setHeight(int value) +{ + if(value <= 0) throw std::runtime_error("Height > 0 failed."); + height = value; +} + +bool CRmgTemplate::CSize::getUnder() const +{ + return under; +} + +void CRmgTemplate::CSize::setUnder(bool value) +{ + under = value; +} + +bool CRmgTemplate::CSize::operator<=(const CSize & value) const +{ + if(width < value.width && height < value.height) + { + return true; + } + else if(width == value.width && height == value.height) + { + return under ? value.under : true; + } + else + { + return false; + } +} + +bool CRmgTemplate::CSize::operator>=(const CSize & value) const +{ + if(width > value.width && height > value.height) + { + return true; + } + else if(width == value.width && height == value.height) + { + return under ? true : !value.under; + } + else + { + return false; + } +} + +CRmgTemplate::CRmgTemplate() +{ + +} + +const std::string & CRmgTemplate::getName() const +{ + return name; +} + +void CRmgTemplate::setName(const std::string & value) +{ + name = value; +} + +const CRmgTemplate::CSize & CRmgTemplate::getMinSize() const +{ + return minSize; +} + +void CRmgTemplate::setMinSize(const CSize & value) +{ + minSize = value; +} + +const CRmgTemplate::CSize & CRmgTemplate::getMaxSize() const +{ + return maxSize; +} + +void CRmgTemplate::setMaxSize(const CSize & value) +{ + maxSize = value; +} + +const CRmgTemplate::CPlayerCountRange & CRmgTemplate::getPlayers() const +{ + return players; +} + +void CRmgTemplate::setPlayers(const CPlayerCountRange & value) +{ + players = value; +} + +const CRmgTemplate::CPlayerCountRange & CRmgTemplate::getCpuPlayers() const +{ + return cpuPlayers; +} + +void CRmgTemplate::setCpuPlayers(const CPlayerCountRange & value) +{ + cpuPlayers = value; +} + +const std::map & CRmgTemplate::getZones() const +{ + return zones; +} + +void CRmgTemplate::setZones(const std::map & value) +{ + zones = value; +} + +const std::list & CRmgTemplate::getConnections() const +{ + return connections; +} + +void CRmgTemplate::setConnections(const std::list & value) +{ + connections = value; +} + +void CRmgTemplate::validate() const +{ + //TODO add some validation checks, throw on failure +} + +void CRmgTemplate::CPlayerCountRange::addRange(int lower, int upper) +{ + range.push_back(std::make_pair(lower, upper)); +} + +void CRmgTemplate::CPlayerCountRange::addNumber(int value) +{ + range.push_back(std::make_pair(value, value)); +} + +bool CRmgTemplate::CPlayerCountRange::isInRange(int count) const +{ + for(const auto & pair : range) + { + if(count >= pair.first && count <= pair.second) return true; + } + return false; +} + +std::set CRmgTemplate::CPlayerCountRange::getNumbers() const +{ + std::set numbers; + for(const auto & pair : range) + { + for(int i = pair.first; i <= pair.second; ++i) numbers.insert(i); + } + return numbers; +} + +const std::map & CRmgTemplateLoader::getTemplates() const +{ + return templates; +} + +void CJsonRmgTemplateLoader::loadTemplates() +{ + const JsonNode rootNode(ResourceID("config/rmg.json")); + for(const auto & templatePair : rootNode.Struct()) + { + CRmgTemplate tpl; + try + { + tpl.setName(templatePair.first); + const auto & templateNode = templatePair.second; + + // Parse main template data + tpl.setMinSize(parseMapTemplateSize(templateNode["minSize"].String())); + tpl.setMaxSize(parseMapTemplateSize(templateNode["maxSize"].String())); + tpl.setPlayers(parsePlayers(templateNode["players"].String())); + tpl.setCpuPlayers(parsePlayers(templateNode["cpu"].String())); + + // Parse zones + std::map zones; + for(const auto & zonePair : templateNode["zones"].Struct()) + { + CRmgTemplateZone zone; + auto zoneId = boost::lexical_cast(zonePair.first); + zone.setId(zoneId); + const auto & zoneNode = zonePair.second; + zone.setType(parseZoneType(zoneNode["type"].String())); + zone.setSize(zoneNode["size"].Float()); + if(!zoneNode["owner"].isNull()) zone.setOwner(zoneNode["owner"].Float()); + zone.setPlayerTowns(parseTemplateZoneTowns(zoneNode["playerTowns"])); + zone.setNeutralTowns(parseTemplateZoneTowns(zoneNode["neutralTowns"])); + zone.setTownTypes(parseTownTypes(zoneNode["townTypes"].Vector(), zone.getDefaultTownTypes())); + zone.setMatchTerrainToTown(zoneNode["matchTerrainToTown"].Bool()); + zone.setTerrainTypes(parseTerrainTypes(zoneNode["terrainTypes"].Vector(), zone.getDefaultTerrainTypes())); + zone.setTownsAreSameType((zoneNode["townsAreSameType"].Bool())); + if(!zoneNode["terrainTypeLikeZone"].isNull()) zone.setTerrainTypeLikeZone(boost::lexical_cast(zoneNode["terrainTypeLikeZone"].String())); + if(!zoneNode["townTypeLikeZone"].isNull()) zone.setTownTypeLikeZone(boost::lexical_cast(zoneNode["townTypeLikeZone"].String())); + zones[zone.getId()] = zone; + } + tpl.setZones(zones); + + // Parse connections + std::list connections; + for(const auto & connPair : templateNode["connections"].Vector()) + { + CRmgTemplateZoneConnection conn; + conn.setZoneA(boost::lexical_cast(connPair["a"].String())); + conn.setZoneB(boost::lexical_cast(connPair["b"].String())); + conn.setGuardStrength(connPair["guard"].Float()); + connections.push_back(conn); + } + tpl.setConnections(connections); + tpl.validate(); + templates[tpl.getName()] = tpl; + } + catch(const std::exception & e) + { + logGlobal->errorStream() << boost::format("Template %s has errors. Message: %s.") % tpl.getName() % std::string(e.what()); + } + } +} + +CRmgTemplate::CSize CJsonRmgTemplateLoader::parseMapTemplateSize(const std::string & text) const +{ + CRmgTemplate::CSize size; + if(text.empty()) return size; + + std::vector parts; + boost::split(parts, text, boost::is_any_of("+")); + static const std::map mapSizeMapping = boost::assign::map_list_of("s", CMapHeader::MAP_SIZE_SMALL) + ("m", CMapHeader::MAP_SIZE_MIDDLE)("l", CMapHeader::MAP_SIZE_LARGE)("xl", CMapHeader::MAP_SIZE_XLARGE); + auto it = mapSizeMapping.find(parts[0]); + if(it == mapSizeMapping.end()) + { + // Map size is given as a number representation + const auto & numericalRep = parts[0]; + parts.clear(); + boost::split(parts, numericalRep, boost::is_any_of("x")); + assert(parts.size() == 3); + size.setWidth(boost::lexical_cast(parts[0])); + size.setHeight(boost::lexical_cast(parts[1])); + size.setUnder(boost::lexical_cast(parts[2]) == 1); + } + else + { + size.setWidth(it->second); + size.setHeight(it->second); + size.setUnder(parts.size() > 1 ? parts[1] == std::string("u") : false); + } + return size; +} + +ETemplateZoneType::ETemplateZoneType CJsonRmgTemplateLoader::parseZoneType(const std::string & type) const +{ + static const std::map zoneTypeMapping = boost::assign::map_list_of + ("playerStart", ETemplateZoneType::PLAYER_START)("cpuStart", ETemplateZoneType::CPU_START) + ("treasure", ETemplateZoneType::TREASURE)("junction", ETemplateZoneType::JUNCTION); + auto it = zoneTypeMapping.find(type); + if(it == zoneTypeMapping.end()) throw std::runtime_error("Zone type unknown."); + return it->second; +} + +CRmgTemplateZone::CTownInfo CJsonRmgTemplateLoader::parseTemplateZoneTowns(const JsonNode & node) const +{ + CRmgTemplateZone::CTownInfo towns; + towns.setTownCount(node["towns"].Float()); + towns.setCastleCount(node["castles"].Float()); + towns.setTownDensity(node["townDensity"].Float()); + towns.setCastleDensity(node["castleDensity"].Float()); + return towns; +} + +std::set CJsonRmgTemplateLoader::parseTownTypes(const JsonVector & townTypesVector, const std::set & defaultTownTypes) const +{ + std::set townTypes; + for(const auto & townTypeNode : townTypesVector) + { + auto townTypeStr = townTypeNode.String(); + if(townTypeStr == "all") return defaultTownTypes; + + bool foundFaction = false; + for(auto factionPtr : VLC->townh->factions) + { + if(factionPtr->town != nullptr && townTypeStr == factionPtr->name) + { + townTypes.insert(factionPtr->index); + foundFaction = true; + } + } + if(!foundFaction) throw std::runtime_error("Given faction is invalid."); + } + return townTypes; +} + +std::set CJsonRmgTemplateLoader::parseTerrainTypes(const JsonVector & terTypeStrings, const std::set & defaultTerrainTypes) const +{ + std::set terTypes; + for(const auto & node : terTypeStrings) + { + const auto & terTypeStr = node.String(); + if(terTypeStr == "all") return defaultTerrainTypes; + auto pos = vstd::find_pos(GameConstants::TERRAIN_NAMES, terTypeStr); + if (pos != -1) + { + terTypes.insert(ETerrainType(pos)); + } + else + { + throw std::runtime_error("Terrain type is invalid."); + } + } + return terTypes; +} + +CRmgTemplate::CPlayerCountRange CJsonRmgTemplateLoader::parsePlayers(const std::string & players) const +{ + CRmgTemplate::CPlayerCountRange playerRange; + if(players.empty()) + { + playerRange.addNumber(0); + return playerRange; + } + std::vector commaParts; + boost::split(commaParts, players, boost::is_any_of(",")); + for(const auto & commaPart : commaParts) + { + std::vector rangeParts; + boost::split(rangeParts, commaPart, boost::is_any_of("-")); + if(rangeParts.size() == 2) + { + auto lower = boost::lexical_cast(rangeParts[0]); + auto upper = boost::lexical_cast(rangeParts[1]); + playerRange.addRange(lower, upper); + } + else if(rangeParts.size() == 1) + { + auto val = boost::lexical_cast(rangeParts.front()); + playerRange.addNumber(val); + } + } + return playerRange; +} + +boost::mutex CRmgTemplateStorage::smx; + +CRmgTemplateStorage & CRmgTemplateStorage::get() +{ + TLockGuard _(smx); + static CRmgTemplateStorage storage; + return storage; +} + +const std::map & CRmgTemplateStorage::getTemplates() const +{ + return templates; +} + +CRmgTemplateStorage::CRmgTemplateStorage() +{ + auto jsonLoader = make_unique(); + jsonLoader->loadTemplates(); + const auto & tpls = jsonLoader->getTemplates(); + templates.insert(tpls.begin(), tpls.end()); +} + +CRmgTemplateStorage::~CRmgTemplateStorage() +{ + +} diff --git a/lib/rmg/CMapGenerator.h b/lib/rmg/CMapGenerator.h index b88bd08e8..ce276deeb 100644 --- a/lib/rmg/CMapGenerator.h +++ b/lib/rmg/CMapGenerator.h @@ -11,32 +11,441 @@ #pragma once +#include "../GameConstants.h" #include "../CRandomGenerator.h" -#include "CMapGenOptions.h" +#include "../CObjectHandler.h" +#include "../int3.h" class CMap; +class CTerrainViewPatternConfig; class CMapEditManager; +class JsonNode; + +typedef std::vector JsonVector; + +namespace ETemplateZoneType +{ +enum ETemplateZoneType +{ + PLAYER_START, + CPU_START, + TREASURE, + JUNCTION +}; +} + +typedef int TRmgTemplateZoneId; + +class CMapGenerator; + +/// The CRmgTemplateZone describes a zone in a template. +class DLL_LINKAGE CRmgTemplateZone +{ +public: + class DLL_LINKAGE CTownInfo + { + public: + CTownInfo(); + + int getTownCount() const; /// Default: 0 + void setTownCount(int value); + int getCastleCount() const; /// Default: 0 + void setCastleCount(int value); + int getTownDensity() const; /// Default: 0 + void setTownDensity(int value); + int getCastleDensity() const; /// Default: 0 + void setCastleDensity(int value); + + private: + int townCount, castleCount, townDensity, castleDensity; + }; + + class DLL_LINKAGE CTileInfo + { + public: + CTileInfo(); + + int getNearestObjectDistance() const; + void setNearestObjectDistance(int value); + bool isObstacle() const; + void setObstacle(bool value); + bool isOccupied() const; + void setOccupied(bool value); + ETerrainType getTerrainType() const; + void setTerrainType(ETerrainType value); + + private: + int nearestObjectDistance; + bool obstacle; + bool occupied; + ETerrainType terrain; + }; + + CRmgTemplateZone(); + + TRmgTemplateZoneId getId() const; /// Default: 0 + void setId(TRmgTemplateZoneId value); + ETemplateZoneType::ETemplateZoneType getType() const; /// Default: ETemplateZoneType::PLAYER_START + void setType(ETemplateZoneType::ETemplateZoneType value); + int getSize() const; /// Default: 1 + void setSize(int value); + boost::optional getOwner() const; + void setOwner(boost::optional value); + const CTownInfo & getPlayerTowns() const; + void setPlayerTowns(const CTownInfo & value); + const CTownInfo & getNeutralTowns() const; + void setNeutralTowns(const CTownInfo & value); + bool getTownsAreSameType() const; /// Default: false + void setTownsAreSameType(bool value); + const std::set & getTownTypes() const; /// Default: all + void setTownTypes(const std::set & value); + std::set getDefaultTownTypes() const; + bool getMatchTerrainToTown() const; /// Default: true + void setMatchTerrainToTown(bool value); + const std::set & getTerrainTypes() const; /// Default: all + void setTerrainTypes(const std::set & value); + std::set getDefaultTerrainTypes() const; + boost::optional getTerrainTypeLikeZone() const; + void setTerrainTypeLikeZone(boost::optional value); + boost::optional getTownTypeLikeZone() const; + void setTownTypeLikeZone(boost::optional value); + void setShape(std::vector shape); + bool fill(CMapGenerator* gen); + +private: + TRmgTemplateZoneId id; + ETemplateZoneType::ETemplateZoneType type; + int size; + boost::optional owner; + CTownInfo playerTowns, neutralTowns; + bool townsAreSameType; + std::set townTypes; + bool matchTerrainToTown; + std::set terrainTypes; + boost::optional terrainTypeLikeZone, townTypeLikeZone; + + std::vector shape; + std::map tileinfo; + std::vector objects; + + int3 getCenter(); + bool pointIsIn(int x, int y); + bool findPlaceForObject(CMapGenerator* gen, CGObjectInstance* obj, si32 min_dist, int3 &pos); + void placeObject(CMapGenerator* gen, CGObjectInstance* object, const int3 &pos); + bool guardObject(CMapGenerator* gen, CGObjectInstance* object, si32 str); +}; + +/// The CRmgTemplateZoneConnection describes the connection between two zones. +class DLL_LINKAGE CRmgTemplateZoneConnection +{ +public: + CRmgTemplateZoneConnection(); + + TRmgTemplateZoneId getZoneA() const; /// Default: 0 + void setZoneA(TRmgTemplateZoneId value); + TRmgTemplateZoneId getZoneB() const; /// Default: 0 + void setZoneB(TRmgTemplateZoneId value); + int getGuardStrength() const; /// Default: 0 + void setGuardStrength(int value); + +private: + TRmgTemplateZoneId zoneA, zoneB; + int guardStrength; +}; + +/// The CRmgTemplate describes a random map template. +class DLL_LINKAGE CRmgTemplate +{ +public: + class CSize + { + public: + CSize(); + CSize(int width, int height, bool under); + + int getWidth() const; /// Default: CMapHeader::MAP_SIZE_MIDDLE + void setWidth(int value); + int getHeight() const; /// Default: CMapHeader::MAP_SIZE_MIDDLE + void setHeight(int value); + bool getUnder() const; /// Default: true + void setUnder(bool value); + bool operator<=(const CSize & value) const; + bool operator>=(const CSize & value) const; + + private: + int width, height; + bool under; + }; + + class CPlayerCountRange + { + public: + void addRange(int lower, int upper); + void addNumber(int value); + bool isInRange(int count) const; + std::set getNumbers() const; + + private: + std::list > range; + }; + + CRmgTemplate(); + + const std::string & getName() const; + void setName(const std::string & value); + const CSize & getMinSize() const; + void setMinSize(const CSize & value); + const CSize & getMaxSize() const; + void setMaxSize(const CSize & value); + const CPlayerCountRange & getPlayers() const; + void setPlayers(const CPlayerCountRange & value); + const CPlayerCountRange & getCpuPlayers() const; + void setCpuPlayers(const CPlayerCountRange & value); + const std::map & getZones() const; + void setZones(const std::map & value); + const std::list & getConnections() const; + void setConnections(const std::list & value); + + void validate() const; /// Tests template on validity and throws exception on failure + +private: + std::string name; + CSize minSize, maxSize; + CPlayerCountRange players, cpuPlayers; + std::map zones; + std::list connections; +}; + +namespace EWaterContent +{ +enum EWaterContent +{ + RANDOM = -1, + NONE, + NORMAL, + ISLANDS +}; +} + +namespace EMonsterStrength +{ +enum EMonsterStrength +{ + RANDOM = -1, + WEAK, + NORMAL, + STRONG +}; +} + +namespace EPlayerType +{ +enum EPlayerType +{ + HUMAN, + AI, + COMP_ONLY +}; +} + +/// The map gen options class holds values about general map generation settings +/// e.g. the size of the map, the count of players,... +class DLL_LINKAGE CMapGenOptions +{ +public: + /// The player settings class maps the player color, starting town and human player flag. + class DLL_LINKAGE CPlayerSettings + { + public: + CPlayerSettings(); + + /// The color of the player ranging from 0 to PlayerColor::PLAYER_LIMIT - 1. + /// The default value is 0. + PlayerColor getColor() const; + void setColor(PlayerColor value); + + /// The starting town of the player ranging from 0 to town max count or RANDOM_TOWN. + /// The default value is RANDOM_TOWN. + si32 getStartingTown() const; + void setStartingTown(si32 value); + + /// The default value is EPlayerType::AI. + EPlayerType::EPlayerType getPlayerType() const; + void setPlayerType(EPlayerType::EPlayerType value); + + /// Constant for a random town selection. + static const si32 RANDOM_TOWN = -1; + + private: + PlayerColor color; + si32 startingTown; + EPlayerType::EPlayerType playerType; + + public: + template + void serialize(Handler & h, const int version) + { + h & color & startingTown & playerType; + } + }; + + CMapGenOptions(); + + si32 getWidth() const; + void setWidth(si32 value); + + si32 getHeight() const; + void setHeight(si32 value); + + bool getHasTwoLevels() const; + void setHasTwoLevels(bool value); + + /// The count of the players ranging from 1 to PlayerColor::PLAYER_LIMIT or RANDOM_SIZE for random. If you call + /// this method, all player settings are reset to default settings. + si8 getPlayerCount() const; + void setPlayerCount(si8 value); + + /// The count of the teams ranging from 0 to or RANDOM_SIZE for random. + si8 getTeamCount() const; + void setTeamCount(si8 value); + + /// The count of the computer only players ranging from 0 to or RANDOM_SIZE for random. + /// If you call this method, all player settings are reset to default settings. + si8 getCompOnlyPlayerCount() const; + void setCompOnlyPlayerCount(si8 value); + + /// The count of the computer only teams ranging from 0 to or RANDOM_SIZE for random. + si8 getCompOnlyTeamCount() const; + void setCompOnlyTeamCount(si8 value); + + EWaterContent::EWaterContent getWaterContent() const; + void setWaterContent(EWaterContent::EWaterContent value); + + EMonsterStrength::EMonsterStrength getMonsterStrength() const; + void setMonsterStrength(EMonsterStrength::EMonsterStrength value); + + /// The first player colors belong to standard players and the last player colors belong to comp only players. + /// All standard players are by default of type EPlayerType::AI. + const std::map & getPlayersSettings() const; + void setStartingTownForPlayer(PlayerColor color, si32 town); + /// Sets a player type for a standard player. A standard player is the opposite of a computer only player. The + /// values which can be chosen for the player type are EPlayerType::AI or EPlayerType::HUMAN. + void setPlayerTypeForStandardPlayer(PlayerColor color, EPlayerType::EPlayerType playerType); + + /// The random map template to generate the map with or empty/not set if the template should be chosen randomly. + /// Default: Not set/random. + 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. + void finalize(); + void finalize(CRandomGenerator & gen); + + /// Returns false if there is no template available which fits to the currently selected options. + bool checkOptions() const; + + static const si8 RANDOM_SIZE = -1; + +private: + void resetPlayersMap(); + int countHumanPlayers() const; + PlayerColor getNextPlayerColor() const; + void updateCompOnlyPlayers(); + void updatePlayers(); + const CRmgTemplate * getPossibleTemplate(CRandomGenerator & gen) const; + + si32 width, height; + bool hasTwoLevels; + si8 playerCount, teamCount, compOnlyPlayerCount, compOnlyTeamCount; + EWaterContent::EWaterContent waterContent; + EMonsterStrength::EMonsterStrength monsterStrength; + std::map players; + const CRmgTemplate * mapTemplate; + +public: + template + void serialize(Handler & h, const int version) + { + h & width & height & hasTwoLevels & playerCount & teamCount & compOnlyPlayerCount; + h & compOnlyTeamCount & waterContent & monsterStrength & players; + //TODO add name of template to class, enables selection of a template by a user + } +}; /// The map generator creates a map randomly. class DLL_LINKAGE CMapGenerator { public: - CMapGenerator(); + explicit CMapGenerator(const CMapGenOptions & mapGenOptions, int randomSeed = std::time(nullptr)); ~CMapGenerator(); // required due to unique_ptr - std::unique_ptr generate(CMapGenOptions * mapGenOptions, int randomSeed = std::time(nullptr)); + std::unique_ptr generate(); + + CMapGenOptions mapGenOptions; + std::unique_ptr map; + CRandomGenerator gen; + int randomSeed; + CMapEditManager * editManager; private: + std::map zones; + /// Generation methods std::string getMapDescription() const; void addPlayerInfo(); void addHeaderInfo(); - void genTerrain(); - void genTowns(); + void genZones(); + void fillZones(); - CMapGenOptions * mapGenOptions; - std::unique_ptr map; - CRandomGenerator rand; - int randomSeed; - CMapEditManager * editManager; +}; + +/* ---------------------------------------------------------------------------- */ +/* Implementation/Detail classes, Private API */ +/* ---------------------------------------------------------------------------- */ + +/// The CRmgTemplateLoader is a abstract base class for loading templates. +class DLL_LINKAGE CRmgTemplateLoader +{ +public: + virtual ~CRmgTemplateLoader() { }; + virtual void loadTemplates() = 0; + const std::map & getTemplates() const; + +protected: + std::map templates; +}; + +/// The CJsonRmgTemplateLoader loads templates from a JSON file. +class DLL_LINKAGE CJsonRmgTemplateLoader : public CRmgTemplateLoader +{ +public: + void loadTemplates() override; + +private: + CRmgTemplate::CSize parseMapTemplateSize(const std::string & text) const; + CRmgTemplateZone::CTownInfo parseTemplateZoneTowns(const JsonNode & node) const; + ETemplateZoneType::ETemplateZoneType parseZoneType(const std::string & type) const; + std::set parseTownTypes(const JsonVector & townTypesVector, const std::set & defaultTownTypes) const; + std::set parseTerrainTypes(const JsonVector & terTypeStrings, const std::set & defaultTerrainTypes) const; + CRmgTemplate::CPlayerCountRange parsePlayers(const std::string & players) const; +}; + +/// The CRmgTemplateStorage is a singleton object where templates are stored and which can be accessed from anywhere. +class DLL_LINKAGE CRmgTemplateStorage +{ +public: + static CRmgTemplateStorage & get(); + + const std::map & getTemplates() const; + +private: + CRmgTemplateStorage(); + ~CRmgTemplateStorage(); + + static boost::mutex smx; + std::map templates; /// Key: Template name };