mirror of
https://github.com/ihhub/fheroes2.git
synced 2026-10-11 21:59:51 +00:00
Improve road network and verify object placement immediately (#10374)
This commit is contained in:
@@ -856,14 +856,6 @@ namespace
|
||||
return ( type == MP2::OBJ_CASTLE ) || ( type == MP2::OBJ_RANDOM_TOWN ) || ( type == MP2::OBJ_RANDOM_CASTLE );
|
||||
}
|
||||
|
||||
void removeRoads( Maps::Map_Format::TileInfo & tile, const int32_t tileIndex )
|
||||
{
|
||||
tile.objects.erase( std::remove_if( tile.objects.begin(), tile.objects.end(), []( const auto & object ) { return object.group == Maps::ObjectGroup::ROADS; } ),
|
||||
tile.objects.end() );
|
||||
|
||||
world.getTile( tileIndex ).removeObjects( MP2::OBJ_ICN_TYPE_ROAD );
|
||||
}
|
||||
|
||||
bool doesContainCastleEntrance( const Maps::Map_Format::TileInfo & tile )
|
||||
{
|
||||
const auto & townObjects = Maps::getObjectsByGroup( Maps::ObjectGroup::KINGDOM_TOWNS );
|
||||
@@ -2050,7 +2042,7 @@ namespace Maps
|
||||
}
|
||||
}
|
||||
else {
|
||||
removeRoads( tile, tileIndex );
|
||||
removeRoadsFromTile( tile, tileIndex );
|
||||
|
||||
updateRoadSpritesAround( map, tileIndex );
|
||||
|
||||
@@ -2071,12 +2063,25 @@ namespace Maps
|
||||
// After the check this tile should not contain a road sprite.
|
||||
if ( !forceRoadOnTile && !doesContainRoads( tile ) ) {
|
||||
// We remove any existing road sprite if this tile does not contain (or was not forced to contain) the main road sprite.
|
||||
removeRoads( tile, tileIndex );
|
||||
removeRoadsFromTile( tile, tileIndex );
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
writeRoadSpriteToTile( tile, tileIndex, imageIndex );
|
||||
}
|
||||
|
||||
void removeRoadsFromTile( Maps::Map_Format::TileInfo & tile, const int32_t tileIndex )
|
||||
{
|
||||
tile.objects.erase( std::remove_if( tile.objects.begin(), tile.objects.end(), []( const auto & object ) { return object.group == Maps::ObjectGroup::ROADS; } ),
|
||||
tile.objects.end() );
|
||||
|
||||
world.getTile( tileIndex ).removeObjects( MP2::OBJ_ICN_TYPE_ROAD );
|
||||
}
|
||||
|
||||
void writeRoadSpriteToTile( Map_Format::TileInfo & tile, const int32_t tileIndex, const uint8_t imageIndex )
|
||||
{
|
||||
auto roadObjectIter = std::find_if( tile.objects.begin(), tile.objects.end(), []( const auto & object ) { return object.group == ObjectGroup::ROADS; } );
|
||||
if ( roadObjectIter != tile.objects.end() ) {
|
||||
// Since the tile has a road object, update it.
|
||||
@@ -2098,6 +2103,13 @@ namespace Maps
|
||||
}
|
||||
}
|
||||
|
||||
void updateAllRoads( Map_Format::MapFormat & map )
|
||||
{
|
||||
const int32_t centerTileIndex = Maps::GetIndexFromAbsPoint( map.width / 2, map.width / 2 );
|
||||
updateRoadSpritesInArea( map, centerTileIndex, map.width, false );
|
||||
updateRoadSpritesInArea( map, centerTileIndex, map.width, true );
|
||||
}
|
||||
|
||||
bool doesContainRoads( const Map_Format::TileInfo & tile )
|
||||
{
|
||||
for ( const auto & object : tile.objects ) {
|
||||
|
||||
@@ -102,6 +102,9 @@ namespace Maps
|
||||
bool updateRoadOnTile( Map_Format::MapFormat & map, const int32_t tileIndex, const bool setRoad );
|
||||
|
||||
void updateRoadSpriteOnTile( Map_Format::MapFormat & map, const int32_t tileIndex, const bool forceRoadOnTile );
|
||||
void removeRoadsFromTile( Map_Format::TileInfo & tile, const int32_t tileIndex );
|
||||
void writeRoadSpriteToTile( Map_Format::TileInfo & tile, const int32_t tileIndex, const uint8_t imageIndex );
|
||||
void updateAllRoads( Map_Format::MapFormat & map );
|
||||
|
||||
bool doesContainRoads( const Map_Format::TileInfo & tile );
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <ostream>
|
||||
#include <set>
|
||||
@@ -51,11 +50,8 @@
|
||||
#include "mp2.h"
|
||||
#include "rand.h"
|
||||
#include "resource.h"
|
||||
#include "route.h"
|
||||
#include "skill.h"
|
||||
#include "translations.h"
|
||||
#include "world.h"
|
||||
#include "world_pathfinding.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -330,7 +326,7 @@ namespace Maps::Random_Generator
|
||||
|
||||
Rand::PCG32 randomGenerator( generatorSeed );
|
||||
|
||||
NodeCache data( width, height );
|
||||
MapStateManager mapState( width, height );
|
||||
|
||||
auto mapBoundsCheck = [width, height]( int x, int y ) {
|
||||
x = std::clamp( x, 0, width - 1 );
|
||||
@@ -346,7 +342,7 @@ namespace Maps::Random_Generator
|
||||
|
||||
// Step 2. Determine region layout and placement.
|
||||
// Insert empty region that represents water and map edges
|
||||
std::vector<Region> mapRegions = { { 0, data.getNode( 0 ), neutralColorIndex, Ground::WATER, 1, 0, RegionType::NEUTRAL } };
|
||||
std::vector<Region> mapRegions = { { 0, mapState.getNodeToUpdate( 0 ), neutralColorIndex, Ground::WATER, 1, 0, RegionType::NEUTRAL } };
|
||||
|
||||
const int neutralRegionCount = std::max( 1, expectedRegionCount - config.playerCount );
|
||||
const int innerLayer = std::min( neutralRegionCount, config.playerCount );
|
||||
@@ -364,6 +360,7 @@ namespace Maps::Random_Generator
|
||||
const int regionCount = mapLayers[layer].first;
|
||||
const double startingAngle = Rand::GetWithGen( 0, 360, randomGenerator );
|
||||
const double offsetAngle = 360.0 / regionCount;
|
||||
const bool regionCountIsEven = ( regionCount % config.playerCount ) == 0;
|
||||
for ( int i = 0; i < regionCount; ++i ) {
|
||||
const double radians = ( startingAngle + offsetAngle * i ) * M_PI / 180;
|
||||
const double distance = mapLayers[layer].second;
|
||||
@@ -380,9 +377,9 @@ namespace Maps::Random_Generator
|
||||
const int32_t treasureLimit = isPlayerRegion ? regionConfiguration.treasureValueLimit : regionConfiguration.treasureValueLimit * 2;
|
||||
|
||||
const uint32_t regionID = static_cast<uint32_t>( mapRegions.size() );
|
||||
Node & centerNode = data.getNode( centerTile );
|
||||
const RegionType innerType = ( layer == 0 ) ? RegionType::NEUTRAL : RegionType::EXPANSION;
|
||||
const RegionType type = isPlayerRegion ? RegionType::STARTING : innerType;
|
||||
Node & centerNode = mapState.getNodeToUpdate( centerTile );
|
||||
const RegionType neutralType = ( layer == 1 && regionCountIsEven ) ? RegionType::EXPANSION : RegionType::NEUTRAL;
|
||||
const RegionType type = isPlayerRegion ? RegionType::STARTING : neutralType;
|
||||
mapRegions.emplace_back( regionID, centerNode, regionColor, groundType, regionSizeLimit * 6 / 5, treasureLimit, type );
|
||||
|
||||
if ( isPlayerRegion ) {
|
||||
@@ -404,7 +401,7 @@ namespace Maps::Random_Generator
|
||||
stillRoomToExpand = false;
|
||||
// Skip the first region which is the border region.
|
||||
for ( size_t regionID = 1; regionID < mapRegions.size(); ++regionID ) {
|
||||
if ( mapRegions[regionID].regionExpansion( data, randomGenerator ) ) {
|
||||
if ( mapRegions[regionID].regionExpansion( mapState, randomGenerator ) ) {
|
||||
stillRoomToExpand = true;
|
||||
}
|
||||
}
|
||||
@@ -429,10 +426,7 @@ namespace Maps::Random_Generator
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5. Castles and mines placement
|
||||
std::set<int32_t> startingLocations;
|
||||
std::set<int32_t> actionLocations;
|
||||
|
||||
// Step 5. Fix terrain transitions and place Castles
|
||||
MapEconomy mapEconomy;
|
||||
|
||||
for ( Region & region : mapRegions ) {
|
||||
@@ -453,7 +447,7 @@ namespace Maps::Random_Generator
|
||||
continue;
|
||||
}
|
||||
|
||||
const Node & adjacentNode = data.getNode( adjacentIndex );
|
||||
const Node & adjacentNode = mapState.getNode( adjacentIndex );
|
||||
if ( adjacentNode.region == 0 && adjacentNode.index == adjacentIndex ) {
|
||||
extraNodes.insert( adjacentIndex );
|
||||
}
|
||||
@@ -462,7 +456,7 @@ namespace Maps::Random_Generator
|
||||
}
|
||||
|
||||
for ( const int32_t extraNodeIndex : extraNodes ) {
|
||||
Node & extra = data.getNode( extraNodeIndex );
|
||||
Node & extra = mapState.getNodeToUpdate( extraNodeIndex );
|
||||
region.nodes.emplace_back( extra );
|
||||
extra.region = region.id;
|
||||
extra.type = NodeType::BORDER;
|
||||
@@ -470,37 +464,70 @@ namespace Maps::Random_Generator
|
||||
|
||||
if ( region.colorIndex != neutralColorIndex ) {
|
||||
const fheroes2::Point castlePos = region.adjustRegionToFitCastle( mapFormat );
|
||||
if ( !placeCastle( mapFormat, data, region, castlePos, true ) ) {
|
||||
if ( !placeCastle( mapFormat, mapState, region, castlePos, true ) ) {
|
||||
// Return early if we can't place a starting player castle.
|
||||
DEBUG_LOG( DBG_DEVEL, DBG_WARN, "Not able to place a starting player castle on tile " << castlePos.x << ", " << castlePos.y )
|
||||
return false;
|
||||
}
|
||||
startingLocations.insert( region.centerIndex );
|
||||
}
|
||||
else if ( static_cast<int32_t>( region.nodes.size() ) > regionSizeLimit ) {
|
||||
// Place non-mandatory castles in bigger neutral regions.
|
||||
const bool useNeutralCastles = ( config.resourceDensity == ResourceDensity::ABUNDANT );
|
||||
const fheroes2::Point castlePos = region.adjustRegionToFitCastle( mapFormat );
|
||||
placeCastle( mapFormat, data, region, castlePos, useNeutralCastles );
|
||||
placeCastle( mapFormat, mapState, region, castlePos, useNeutralCastles );
|
||||
}
|
||||
else {
|
||||
forceTempRoadOnTile( mapFormat, region.centerIndex );
|
||||
}
|
||||
}
|
||||
Maps::updatePlayerRelatedObjects( mapFormat );
|
||||
|
||||
// Step 6. Set up region connectors based on frequency settings and border length.
|
||||
for ( Region & region : mapRegions ) {
|
||||
if ( region.groundType == Ground::WATER ) {
|
||||
continue;
|
||||
}
|
||||
for ( Node & node : region.nodes ) {
|
||||
region.checkNodeForConnections( mapState, mapRegions, node );
|
||||
}
|
||||
}
|
||||
|
||||
for ( const Region & region : mapRegions ) {
|
||||
for ( const Node & node : region.nodes ) {
|
||||
if ( node.type == NodeType::BORDER ) {
|
||||
placeBorderObstacle( mapFormat, mapState, node, randomGenerator );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 7. Place mines.
|
||||
const auto tryToPlaceMine = [&]( const std::vector<int32_t> & ring, const int resource ) {
|
||||
for ( const int32_t tileIndex : ring ) {
|
||||
const auto & node = mapState.getNode( tileIndex );
|
||||
if ( placeMine( mapFormat, mapState, node, resource ) ) {
|
||||
mapEconomy.increaseMineCount( resource );
|
||||
|
||||
const int32_t mineValue = getObjectGoldValue( getFakeMP2MineType( resource ) );
|
||||
placeMonster( mapFormat, Maps::GetDirectionIndex( tileIndex, Direction::BOTTOM ), getMonstersByValue( config.monsterStrength, mineValue ) );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
for ( const Region & region : mapRegions ) {
|
||||
if ( region.groundType == Ground::WATER ) {
|
||||
continue;
|
||||
}
|
||||
for ( const auto & [regionId, tileIndex] : region.connections ) {
|
||||
for ( const auto & step : findPathToNearestRoad( mapState, width, region.id, tileIndex ) ) {
|
||||
mapState.getNodeToUpdate( step ).type = NodeType::PATH;
|
||||
forceTempRoadOnTile( mapFormat, step );
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<std::vector<int32_t>> tileRings = findOpenTilesSortedJittered( region, width, randomGenerator );
|
||||
|
||||
const auto tryToPlaceMine = [&]( const std::vector<int32_t> & ring, const int resource ) {
|
||||
for ( const int32_t tileIndex : ring ) {
|
||||
const auto & node = data.getNode( tileIndex );
|
||||
if ( placeMine( mapFormat, data, node, resource ) ) {
|
||||
mapEconomy.increaseMineCount( resource );
|
||||
actionLocations.insert( tileIndex );
|
||||
|
||||
const int32_t mineValue = getObjectGoldValue( getFakeMP2MineType( resource ) );
|
||||
placeMonster( mapFormat, Maps::GetDirectionIndex( tileIndex, Direction::BOTTOM ), getMonstersByValue( config.monsterStrength, mineValue ) );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if ( tileRings.size() < 4 ) {
|
||||
continue;
|
||||
}
|
||||
@@ -522,58 +549,11 @@ namespace Maps::Random_Generator
|
||||
}
|
||||
}
|
||||
}
|
||||
Maps::updatePlayerRelatedObjects( mapFormat );
|
||||
|
||||
// Step 6. Set up region connectors based on frequency settings and border length.
|
||||
for ( Region & region : mapRegions ) {
|
||||
if ( region.groundType == Ground::WATER ) {
|
||||
continue;
|
||||
}
|
||||
for ( Node & node : region.nodes ) {
|
||||
region.checkNodeForConnections( data, mapRegions, node );
|
||||
}
|
||||
}
|
||||
|
||||
for ( const Region & region : mapRegions ) {
|
||||
for ( const Node & node : region.nodes ) {
|
||||
if ( node.type == NodeType::BORDER ) {
|
||||
placeRandomObstacle( mapFormat, data, node, randomGenerator );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 7. Set up pathfinder and generate road network.
|
||||
AIWorldPathfinder pathfinder;
|
||||
pathfinder.reset();
|
||||
const PlayerColor testPlayer = PlayerColor::BLUE;
|
||||
|
||||
// Have to remove fog first otherwise pathfinder won't work
|
||||
for ( int idx = 0; idx < width * height; ++idx ) {
|
||||
world.getTile( idx ).removeFogForPlayers( static_cast<PlayerColorsSet>( testPlayer ) );
|
||||
}
|
||||
world.resetPathfinder();
|
||||
world.updatePassabilities();
|
||||
|
||||
// Set explicit paths
|
||||
for ( const Region & region : mapRegions ) {
|
||||
if ( region.groundType == Ground::WATER ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pathfinder.reEvaluateIfNeeded( region.centerIndex, testPlayer, 999999.9, Skill::Level::EXPERT );
|
||||
for ( const auto & [regionId, tileIndex] : region.connections ) {
|
||||
const auto & path = pathfinder.buildPath( tileIndex );
|
||||
for ( const auto & step : path ) {
|
||||
data.getNode( step.GetIndex() ).type = NodeType::PATH;
|
||||
Maps::updateRoadOnTile( mapFormat, step.GetIndex(), true );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 8: Place powerups and treasure clusters while avoiding the paths.
|
||||
for ( Region & region : mapRegions ) {
|
||||
placeObjectSet( mapFormat, data, region, powerupObjectSets, config.monsterStrength, regionConfiguration.powerUpsCount, randomGenerator );
|
||||
placeObjectSet( mapFormat, data, region, prefabObjectSets, config.monsterStrength, regionConfiguration.treasureCount, randomGenerator );
|
||||
placeObjectSet( mapFormat, mapState, region, powerupObjectSets, config.monsterStrength, regionConfiguration.powerUpsCount, randomGenerator );
|
||||
placeObjectSet( mapFormat, mapState, region, prefabObjectSets, config.monsterStrength, regionConfiguration.treasureCount, randomGenerator );
|
||||
}
|
||||
|
||||
// TODO: Step 9: Detect and fill empty areas with decorative/flavour objects.
|
||||
@@ -592,16 +572,7 @@ namespace Maps::Random_Generator
|
||||
}
|
||||
}
|
||||
|
||||
// Step 11: Validate that map is playable.
|
||||
for ( const int32_t start : startingLocations ) {
|
||||
pathfinder.reEvaluateIfNeeded( start, testPlayer, 999999.9, Skill::Level::EXPERT );
|
||||
for ( const int action : actionLocations ) {
|
||||
if ( pathfinder.getDistance( action ) == 0 ) {
|
||||
DEBUG_LOG( DBG_DEVEL, DBG_WARN, "Not able to find path from " << start << " to " << action )
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Maps::updateAllRoads( mapFormat );
|
||||
|
||||
// Visual debug
|
||||
for ( const Region & region : mapRegions ) {
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
|
||||
@@ -49,10 +50,18 @@
|
||||
|
||||
namespace
|
||||
{
|
||||
struct RoadBuilderNode final
|
||||
{
|
||||
int32_t _from{ -1 };
|
||||
uint32_t _cost{ 0 };
|
||||
int _direction{ Direction::UNKNOWN };
|
||||
};
|
||||
|
||||
constexpr int randomCastleIndex{ 12 };
|
||||
constexpr int randomTownIndex{ 13 };
|
||||
constexpr int randomHeroIndex{ 7 };
|
||||
constexpr int maxPlacementAttempts{ 30 };
|
||||
constexpr uint32_t roadBuilderMargin{ Maps::Ground::defaultGroundPenalty * 3 };
|
||||
|
||||
const std::map<int, std::vector<int>> obstaclesPerGround = {
|
||||
{ Maps::Ground::DESERT, { 24, 25, 26, 27, 28, 29 } }, { Maps::Ground::SNOW, { 30, 31, 32, 33, 34, 35 } }, { Maps::Ground::SWAMP, { 18, 19, 20, 21, 22, 23 } },
|
||||
@@ -216,10 +225,14 @@ namespace
|
||||
return true;
|
||||
}
|
||||
|
||||
void markNodeAsType( Maps::Random_Generator::NodeCache & data, const fheroes2::Point position, const Maps::Random_Generator::NodeType type )
|
||||
void markNodeAsType( Maps::Random_Generator::MapStateManager & data, const fheroes2::Point position, const Maps::Random_Generator::NodeType type )
|
||||
{
|
||||
if ( Maps::isValidAbsPoint( position.x, position.y ) ) {
|
||||
data.getNode( position ).type = type;
|
||||
auto & node = data.getNodeToUpdate( position );
|
||||
// Never override border types (no assert needed; can happen when placing overlapping obstacles)
|
||||
if ( node.type != Maps::Random_Generator::NodeType::BORDER ) {
|
||||
node.type = type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -326,6 +339,121 @@ namespace Maps::Random_Generator
|
||||
return objectIndex;
|
||||
}
|
||||
|
||||
std::vector<int32_t> findPathToNearestRoad( const Maps::Random_Generator::MapStateManager & nodes, const int32_t mapWidth, const uint32_t regionId,
|
||||
const int32_t start )
|
||||
{
|
||||
assert( start > 0 );
|
||||
assert( mapWidth > 0 );
|
||||
assert( start < mapWidth * mapWidth );
|
||||
|
||||
std::vector<RoadBuilderNode> cache( static_cast<size_t>( mapWidth ) * mapWidth );
|
||||
assert( start < static_cast<int32_t>( cache.size() ) );
|
||||
|
||||
std::vector<int32_t> result;
|
||||
std::vector<int32_t> nodesToExplore;
|
||||
nodesToExplore.push_back( start );
|
||||
cache[static_cast<size_t>( start )]._from = start;
|
||||
cache[static_cast<size_t>( start )]._cost = 0;
|
||||
|
||||
const bool fromActionTile = ( nodes.getNode( start ).type == Maps::Random_Generator::NodeType::ACTION );
|
||||
const Directions & directions = Direction::All();
|
||||
|
||||
int32_t bestRoadIndex = -1;
|
||||
uint32_t bestRoadCost = std::numeric_limits<uint32_t>::max();
|
||||
for ( size_t lastProcessedNode = 0; lastProcessedNode < nodesToExplore.size(); ++lastProcessedNode ) {
|
||||
const int32_t currentNodeIdx = nodesToExplore[lastProcessedNode];
|
||||
const RoadBuilderNode & currentNode = cache[currentNodeIdx];
|
||||
|
||||
// Continue to search until margin is hit to find a potential optimal route
|
||||
if ( bestRoadIndex != -1 && currentNode._cost > bestRoadCost + roadBuilderMargin ) {
|
||||
break;
|
||||
}
|
||||
|
||||
const bool comeFromStraight = !Direction::isDiagonal( currentNode._direction );
|
||||
|
||||
if ( comeFromStraight && currentNodeIdx != start && currentNode._cost < bestRoadCost && world.getTile( currentNodeIdx ).isRoad() ) {
|
||||
bestRoadIndex = currentNodeIdx;
|
||||
bestRoadCost = currentNode._cost;
|
||||
}
|
||||
|
||||
for ( const int direction : directions ) {
|
||||
if ( !Maps::isValidDirection( currentNodeIdx, direction ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool isDiagonal = Direction::isDiagonal( direction );
|
||||
// Edge case: force straight roads out of action tiles (e.g. mines)
|
||||
if ( fromActionTile && currentNodeIdx == start && isDiagonal ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Edge case: avoid tight road turns for a better visuals
|
||||
if ( isDiagonal && currentNode._from != -1 ) {
|
||||
const int previousDirection = cache[currentNode._from]._direction;
|
||||
|
||||
if ( previousDirection != direction && Direction::isDiagonal( previousDirection ) ) {
|
||||
continue;
|
||||
}
|
||||
if ( previousDirection == Direction::BOTTOM && ( currentNode._direction == Direction::LEFT || currentNode._direction == Direction::RIGHT ) ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const int newIndex = Maps::GetDirectionIndex( currentNodeIdx, direction );
|
||||
if ( newIndex == start ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Node & other = nodes.getNode( newIndex );
|
||||
|
||||
if ( other.region != regionId || ( other.type != Maps::Random_Generator::NodeType::OPEN && other.type != Maps::Random_Generator::NodeType::PATH ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
RoadBuilderNode & newNode = cache[newIndex];
|
||||
constexpr uint32_t diagonalMovePenalty = Ground::defaultGroundPenalty * 3 / 2;
|
||||
const uint32_t movementPenalty = isDiagonal ? diagonalMovePenalty : Ground::defaultGroundPenalty;
|
||||
const uint32_t movementCost = currentNode._cost + movementPenalty;
|
||||
|
||||
if ( newNode._from == -1 || newNode._cost > movementCost ) {
|
||||
newNode._from = currentNodeIdx;
|
||||
newNode._cost = movementCost;
|
||||
newNode._direction = direction;
|
||||
|
||||
nodesToExplore.push_back( newIndex );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( bestRoadIndex == -1 ) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for ( int32_t currentStep = bestRoadIndex;; ) {
|
||||
const RoadBuilderNode & rbNode = cache[static_cast<size_t>( currentStep )];
|
||||
|
||||
// Adding additional 0 cost road step to fix road transitions to compensate for missing sprites
|
||||
if ( result.size() == 1 ) {
|
||||
if ( rbNode._direction == Direction::BOTTOM_RIGHT ) {
|
||||
result.emplace_back( Maps::GetDirectionIndex( currentStep, Direction::LEFT ) );
|
||||
}
|
||||
else if ( rbNode._direction == Direction::BOTTOM_LEFT ) {
|
||||
result.emplace_back( Maps::GetDirectionIndex( currentStep, Direction::RIGHT ) );
|
||||
}
|
||||
}
|
||||
|
||||
result.emplace_back( currentStep );
|
||||
|
||||
if ( currentStep == rbNode._from ) {
|
||||
break;
|
||||
}
|
||||
|
||||
currentStep = rbNode._from;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::vector<int32_t>> findOpenTilesSortedJittered( const Region & region, int32_t mapWidth, Rand::PCG32 & randomGenerator )
|
||||
{
|
||||
if ( region.centerIndex < 0 || region.nodes.empty() || mapWidth <= 0 ) {
|
||||
@@ -365,7 +493,7 @@ namespace Maps::Random_Generator
|
||||
return buckets;
|
||||
}
|
||||
|
||||
bool canFitObject( const NodeCache & data, const ObjectInfo & info, const fheroes2::Point & mainTilePos, const bool skipBorders )
|
||||
bool canFitObject( const MapStateManager & data, const ObjectInfo & info, const fheroes2::Point & mainTilePos, const bool skipBorders )
|
||||
{
|
||||
bool invalid = false;
|
||||
fheroes2::Rect objectRect;
|
||||
@@ -377,7 +505,7 @@ namespace Maps::Random_Generator
|
||||
invalid = true;
|
||||
return;
|
||||
}
|
||||
if ( node.type != NodeType::OPEN && ( skipBorders || node.type != NodeType::BORDER ) ) {
|
||||
if ( node.type != NodeType::OPEN && ( skipBorders || ( node.type != NodeType::BORDER && node.type != NodeType::OBSTACLE ) ) ) {
|
||||
invalid = true;
|
||||
return;
|
||||
}
|
||||
@@ -407,7 +535,7 @@ namespace Maps::Random_Generator
|
||||
return true;
|
||||
}
|
||||
|
||||
bool canFitObjectSet( const NodeCache & data, const ObjectSet & set, const fheroes2::Point & mainTilePos )
|
||||
bool canFitObjectSet( const MapStateManager & data, const ObjectSet & set, const fheroes2::Point & mainTilePos )
|
||||
{
|
||||
for ( const fheroes2::Point offset : set.entranceCheck ) {
|
||||
const Node & node = data.getNode( mainTilePos + offset );
|
||||
@@ -447,35 +575,23 @@ namespace Maps::Random_Generator
|
||||
return true;
|
||||
}
|
||||
|
||||
void markObjectPlacement( NodeCache & data, const ObjectInfo & info, const fheroes2::Point & mainTilePos )
|
||||
void markObjectPlacement( MapStateManager & data, const ObjectInfo & info, const fheroes2::Point & mainTilePos )
|
||||
{
|
||||
fheroes2::Rect objectRect;
|
||||
|
||||
auto updateObjectArea = [&data, &mainTilePos, &objectRect]( const auto & partInfo ) {
|
||||
Node & node = data.getNode( mainTilePos + partInfo.tileOffset );
|
||||
objectRect.x = std::min( objectRect.x, partInfo.tileOffset.x );
|
||||
objectRect.width = std::max( objectRect.width, partInfo.tileOffset.x );
|
||||
|
||||
node.type = NodeType::OBSTACLE;
|
||||
};
|
||||
|
||||
iterateOverObjectParts( info, updateObjectArea );
|
||||
iterateOverObjectParts( info, [&data, &mainTilePos]( const auto & partInfo ) { markNodeAsType( data, mainTilePos + partInfo.tileOffset, NodeType::OBSTACLE ); } );
|
||||
|
||||
if ( !MP2::isOffGameActionObject( info.objectType ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
for ( int x = objectRect.x - 1; x <= objectRect.width + 1; ++x ) {
|
||||
markNodeAsType( data, mainTilePos + fheroes2::Point{ x, objectRect.height + 1 }, NodeType::PATH );
|
||||
}
|
||||
|
||||
// Mark extra nodes as path to avoid objects clumping together
|
||||
markNodeAsType( data, mainTilePos + fheroes2::Point{ objectRect.x - 1, 0 }, NodeType::PATH );
|
||||
markNodeAsType( data, mainTilePos + fheroes2::Point{ objectRect.width + 1, 0 }, NodeType::PATH );
|
||||
|
||||
markNodeAsType( data, mainTilePos, NodeType::ACTION );
|
||||
}
|
||||
|
||||
// Wouldn't render correctly but will speed up placement
|
||||
void forceTempRoadOnTile( Map_Format::MapFormat & mapFormat, const int32_t tileIndex )
|
||||
{
|
||||
Maps::writeRoadSpriteToTile( mapFormat.tiles[tileIndex], tileIndex, 0 );
|
||||
}
|
||||
|
||||
bool putObjectOnMap( Map_Format::MapFormat & mapFormat, Tile & tile, const ObjectGroup groupType, const int32_t objectIndex )
|
||||
{
|
||||
const auto & objectInfo = Maps::getObjectInfo( groupType, objectIndex );
|
||||
@@ -499,19 +615,35 @@ namespace Maps::Random_Generator
|
||||
return true;
|
||||
}
|
||||
|
||||
bool placeActionObject( Map_Format::MapFormat & mapFormat, NodeCache & data, Tile & tile, const ObjectGroup groupType, const int32_t type )
|
||||
bool placeActionObject( Map_Format::MapFormat & mapFormat, MapStateManager & data, Tile & tile, const ObjectGroup groupType, const int32_t type )
|
||||
{
|
||||
const fheroes2::Point tilePos = tile.GetCenter();
|
||||
const auto & objectInfo = Maps::getObjectInfo( groupType, type );
|
||||
if ( canFitObject( data, objectInfo, tilePos, true ) && putObjectOnMap( mapFormat, tile, groupType, type ) ) {
|
||||
|
||||
if ( canFitObject( data, objectInfo, tilePos, true ) ) {
|
||||
MapStateTransaction transaction( data );
|
||||
markObjectPlacement( data, objectInfo, tilePos );
|
||||
return true;
|
||||
|
||||
const int32_t tileIndex = tile.GetIndex();
|
||||
const auto & roadToObject = findPathToNearestRoad( data, mapFormat.width, data.getNode( tileIndex ).region, tileIndex );
|
||||
if ( roadToObject.empty() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( putObjectOnMap( mapFormat, tile, groupType, type ) ) {
|
||||
for ( const auto & step : roadToObject ) {
|
||||
data.getNodeToUpdate( step ).type = NodeType::PATH;
|
||||
forceTempRoadOnTile( mapFormat, step );
|
||||
}
|
||||
transaction.commit();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool placeCastle( Map_Format::MapFormat & mapFormat, NodeCache & data, const Region & region, const fheroes2::Point tilePos, const bool isCastle )
|
||||
bool placeCastle( Map_Format::MapFormat & mapFormat, MapStateManager & data, const Region & region, const fheroes2::Point tilePos, const bool isCastle )
|
||||
{
|
||||
auto & tile = world.getTile( tilePos.x, tilePos.y );
|
||||
if ( tile.isWater() ) {
|
||||
@@ -577,6 +709,7 @@ namespace Maps::Random_Generator
|
||||
// Force roads coming from the castle
|
||||
const int32_t nextIndex = Maps::GetDirectionIndex( bottomIndex, Direction::BOTTOM );
|
||||
if ( Maps::isValidAbsIndex( nextIndex ) ) {
|
||||
data.getNodeToUpdate( bottomIndex ).type = NodeType::PATH;
|
||||
Maps::updateRoadOnTile( mapFormat, bottomIndex, true );
|
||||
Maps::updateRoadOnTile( mapFormat, nextIndex, true );
|
||||
}
|
||||
@@ -584,14 +717,14 @@ namespace Maps::Random_Generator
|
||||
return true;
|
||||
}
|
||||
|
||||
bool placeMine( Map_Format::MapFormat & mapFormat, NodeCache & data, const Node & node, const int resource )
|
||||
bool placeMine( Map_Format::MapFormat & mapFormat, MapStateManager & data, const Node & node, const int resource )
|
||||
{
|
||||
Tile & mineTile = world.getTile( node.index );
|
||||
const int32_t mineType = fheroes2::getMineObjectInfoId( resource, mineTile.GetGround() );
|
||||
return placeActionObject( mapFormat, data, mineTile, ObjectGroup::ADVENTURE_MINES, mineType );
|
||||
}
|
||||
|
||||
bool placeRandomObstacle( Map_Format::MapFormat & mapFormat, const NodeCache & data, const Node & node, Rand::PCG32 & randomGenerator )
|
||||
bool placeBorderObstacle( Map_Format::MapFormat & mapFormat, MapStateManager & data, const Node & node, Rand::PCG32 & randomGenerator )
|
||||
{
|
||||
Tile & tile = world.getTile( node.index );
|
||||
const auto it = obstaclesPerGround.find( tile.GetGround() );
|
||||
@@ -606,6 +739,7 @@ namespace Maps::Random_Generator
|
||||
for ( const auto & obstacleId : obstacleList ) {
|
||||
const auto & objectInfo = Maps::getObjectInfo( ObjectGroup::LANDSCAPE_TREES, obstacleId );
|
||||
if ( canFitObject( data, objectInfo, tilePos, false ) && putObjectOnMap( mapFormat, tile, ObjectGroup::LANDSCAPE_TREES, obstacleId ) ) {
|
||||
markObjectPlacement( data, objectInfo, tilePos );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -632,7 +766,7 @@ namespace Maps::Random_Generator
|
||||
}
|
||||
}
|
||||
|
||||
bool placeSimpleObject( Map_Format::MapFormat & mapFormat, NodeCache & data, const Node & centerNode, const ObjectPlacement & placement )
|
||||
bool placeSimpleObject( Map_Format::MapFormat & mapFormat, MapStateManager & data, const Node & centerNode, const ObjectPlacement & placement )
|
||||
{
|
||||
const fheroes2::Point position = Maps::GetPoint( centerNode.index ) + placement.offset;
|
||||
if ( !Maps::isValidAbsPoint( position.x, position.y ) ) {
|
||||
@@ -649,8 +783,8 @@ namespace Maps::Random_Generator
|
||||
return false;
|
||||
}
|
||||
|
||||
void placeObjectSet( Map_Format::MapFormat & mapFormat, NodeCache & data, Region & region, std::vector<ObjectSet> objects, const MonsterStrength monsterStrength,
|
||||
const uint8_t expectedCount, Rand::PCG32 & randomGenerator )
|
||||
void placeObjectSet( Map_Format::MapFormat & mapFormat, MapStateManager & data, Region & region, std::vector<ObjectSet> objectSets,
|
||||
const MonsterStrength monsterStrength, const uint8_t expectedCount, Rand::PCG32 & randomGenerator )
|
||||
{
|
||||
int objectsPlaced = 0;
|
||||
for ( int attempt = 0; attempt < maxPlacementAttempts; ++attempt ) {
|
||||
@@ -660,12 +794,30 @@ namespace Maps::Random_Generator
|
||||
|
||||
const Node & node = Rand::GetWithGen( region.nodes, randomGenerator );
|
||||
|
||||
Rand::ShuffleWithGen( objects, randomGenerator );
|
||||
for ( const auto & prefab : objects ) {
|
||||
Rand::ShuffleWithGen( objectSets, randomGenerator );
|
||||
for ( const auto & prefab : objectSets ) {
|
||||
if ( !canFitObjectSet( data, prefab, Maps::GetPoint( node.index ) ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MapStateTransaction transaction( data );
|
||||
for ( const auto & obstacle : prefab.obstacles ) {
|
||||
const fheroes2::Point position = Maps::GetPoint( node.index ) + obstacle.offset;
|
||||
const auto & objectInfo = Maps::getObjectInfo( obstacle.groupType, obstacle.objectIndex );
|
||||
markObjectPlacement( data, objectInfo, position );
|
||||
}
|
||||
|
||||
const auto & routeToGroup = findPathToNearestRoad( data, mapFormat.width, region.id, node.index );
|
||||
if ( routeToGroup.empty() ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for ( const auto & step : routeToGroup ) {
|
||||
data.getNodeToUpdate( step ).type = NodeType::PATH;
|
||||
forceTempRoadOnTile( mapFormat, step );
|
||||
}
|
||||
transaction.commit();
|
||||
|
||||
for ( const auto & obstacle : prefab.obstacles ) {
|
||||
placeSimpleObject( mapFormat, data, node, obstacle );
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Maps
|
||||
|
||||
namespace Maps::Random_Generator
|
||||
{
|
||||
class NodeCache;
|
||||
class MapStateManager;
|
||||
struct Node;
|
||||
struct ObjectPlacement;
|
||||
struct ObjectSet;
|
||||
@@ -66,19 +66,22 @@ namespace Maps::Random_Generator
|
||||
MonsterSelection getMonstersByValue( const MonsterStrength monsterStrength, int32_t protectedObjectValue );
|
||||
std::pair<ObjectGroup, int32_t> getRandomTreasure( const int32_t goldValueLimit, Rand::PCG32 & randomGenerator );
|
||||
int32_t selectTerrainVariantForObject( const ObjectGroup groupType, const int32_t objectIndex, const int groundType );
|
||||
std::vector<int32_t> findPathToNearestRoad( const Maps::Random_Generator::MapStateManager & nodes, const int32_t mapWidth, const uint32_t regionId,
|
||||
const int32_t start );
|
||||
std::vector<std::vector<int32_t>> findOpenTilesSortedJittered( const Region & region, int32_t mapWidth, Rand::PCG32 & randomGenerator );
|
||||
|
||||
bool canFitObject( const NodeCache & data, const ObjectInfo & info, const fheroes2::Point & mainTilePos, const bool skipBorders );
|
||||
bool canFitObjectSet( const NodeCache & data, const ObjectSet & set, const fheroes2::Point & mainTilePos );
|
||||
void markObjectPlacement( NodeCache & data, const ObjectInfo & info, const fheroes2::Point & mainTilePos );
|
||||
bool canFitObject( const MapStateManager & data, const ObjectInfo & info, const fheroes2::Point & mainTilePos, const bool skipBorders );
|
||||
bool canFitObjectSet( const MapStateManager & data, const ObjectSet & set, const fheroes2::Point & mainTilePos );
|
||||
void markObjectPlacement( MapStateManager & data, const ObjectInfo & info, const fheroes2::Point & mainTilePos );
|
||||
void forceTempRoadOnTile( Map_Format::MapFormat & mapFormat, const int32_t tileIndex );
|
||||
|
||||
bool putObjectOnMap( Map_Format::MapFormat & mapFormat, Tile & tile, const ObjectGroup groupType, const int32_t objectIndex );
|
||||
bool placeActionObject( Map_Format::MapFormat & mapFormat, NodeCache & data, Tile & tile, const ObjectGroup groupType, const int32_t type );
|
||||
bool placeCastle( Map_Format::MapFormat & mapFormat, NodeCache & data, const Region & region, const fheroes2::Point tilePos, const bool isCastle );
|
||||
bool placeMine( Map_Format::MapFormat & mapFormat, NodeCache & data, const Node & node, const int resource );
|
||||
bool placeRandomObstacle( Map_Format::MapFormat & mapFormat, const NodeCache & data, const Node & node, Rand::PCG32 & randomGenerator );
|
||||
bool placeActionObject( Map_Format::MapFormat & mapFormat, MapStateManager & data, Tile & tile, const ObjectGroup groupType, const int32_t type );
|
||||
bool placeCastle( Map_Format::MapFormat & mapFormat, MapStateManager & data, const Region & region, const fheroes2::Point tilePos, const bool isCastle );
|
||||
bool placeMine( Map_Format::MapFormat & mapFormat, MapStateManager & data, const Node & node, const int resource );
|
||||
bool placeBorderObstacle( Map_Format::MapFormat & mapFormat, MapStateManager & data, const Node & node, Rand::PCG32 & randomGenerator );
|
||||
void placeMonster( Map_Format::MapFormat & mapFormat, const int32_t index, const MonsterSelection & monster );
|
||||
bool placeSimpleObject( Map_Format::MapFormat & mapFormat, NodeCache & data, const Node & centerNode, const ObjectPlacement & placement );
|
||||
void placeObjectSet( Map_Format::MapFormat & mapFormat, NodeCache & data, Region & region, std::vector<ObjectSet> objects, const MonsterStrength monsterStrength,
|
||||
const uint8_t expectedCount, Rand::PCG32 & randomGenerator );
|
||||
bool placeSimpleObject( Map_Format::MapFormat & mapFormat, MapStateManager & data, const Node & centerNode, const ObjectPlacement & placement );
|
||||
void placeObjectSet( Map_Format::MapFormat & mapFormat, MapStateManager & data, Region & region, std::vector<ObjectSet> objects,
|
||||
const MonsterStrength monsterStrength, const uint8_t expectedCount, Rand::PCG32 & randomGenerator );
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace
|
||||
|
||||
namespace Maps::Random_Generator
|
||||
{
|
||||
NodeCache::NodeCache( const int32_t width, const int32_t height )
|
||||
MapStateManager::MapStateManager( const int32_t width, const int32_t height )
|
||||
: _mapSize( width )
|
||||
, _data( static_cast<size_t>( width ) * height )
|
||||
{
|
||||
@@ -51,6 +51,44 @@ namespace Maps::Random_Generator
|
||||
}
|
||||
}
|
||||
|
||||
void MapStateManager::commitTransaction( const size_t record )
|
||||
{
|
||||
assert( !_transactionRecords.empty() );
|
||||
assert( _transactionRecords.back() == record );
|
||||
|
||||
_transactionRecords.pop_back();
|
||||
|
||||
if ( _transactionRecords.empty() ) {
|
||||
_history.clear();
|
||||
}
|
||||
else {
|
||||
_history.resize( record );
|
||||
}
|
||||
}
|
||||
|
||||
void MapStateManager::rollbackTransaction( const size_t record )
|
||||
{
|
||||
assert( !_transactionRecords.empty() );
|
||||
assert( _transactionRecords.back() == record );
|
||||
_transactionRecords.pop_back();
|
||||
|
||||
if ( _history.empty() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
for ( size_t index = _history.size() - 1; index > record; --index ) {
|
||||
const StateChange & change = _history[index];
|
||||
_data[static_cast<size_t>( change.index )] = change.state;
|
||||
}
|
||||
|
||||
if ( _transactionRecords.empty() ) {
|
||||
_history.clear();
|
||||
}
|
||||
else {
|
||||
_history.resize( record );
|
||||
}
|
||||
}
|
||||
|
||||
void MapEconomy::increaseMineCount( const int resourceType )
|
||||
{
|
||||
const auto it = _minesCount.find( resourceType );
|
||||
@@ -67,7 +105,7 @@ namespace Maps::Random_Generator
|
||||
return *it;
|
||||
}
|
||||
|
||||
void Region::checkAdjacentTiles( NodeCache & rawData, const double distanceLimit, Rand::PCG32 & randomGenerator )
|
||||
void Region::checkAdjacentTiles( MapStateManager & rawData, const double distanceLimit, Rand::PCG32 & randomGenerator )
|
||||
{
|
||||
Node & previousNode = nodes[lastProcessedNode];
|
||||
const int nodeIndex = previousNode.index;
|
||||
@@ -85,7 +123,7 @@ namespace Maps::Random_Generator
|
||||
continue;
|
||||
}
|
||||
|
||||
Node & newTile = rawData.getNode( newPosition );
|
||||
Node & newTile = rawData.getNodeToUpdate( newPosition );
|
||||
|
||||
if ( Maps::GetApproximateDistance( centerIndex, newTile.index ) > distanceLimit ) {
|
||||
previousNode.type = NodeType::BORDER;
|
||||
@@ -109,7 +147,7 @@ namespace Maps::Random_Generator
|
||||
}
|
||||
}
|
||||
|
||||
bool Region::regionExpansion( NodeCache & rawData, Rand::PCG32 & randomGenerator )
|
||||
bool Region::regionExpansion( MapStateManager & rawData, Rand::PCG32 & randomGenerator )
|
||||
{
|
||||
// Process only "open" nodes that exist at the start of the loop and ignore what's added.
|
||||
const size_t nodesEnd = nodes.size();
|
||||
@@ -122,7 +160,7 @@ namespace Maps::Random_Generator
|
||||
return lastProcessedNode != nodes.size();
|
||||
}
|
||||
|
||||
bool Region::checkNodeForConnections( NodeCache & data, std::vector<Region> & mapRegions, Node & node )
|
||||
bool Region::checkNodeForConnections( MapStateManager & data, std::vector<Region> & mapRegions, Node & node )
|
||||
{
|
||||
if ( node.type != NodeType::BORDER ) {
|
||||
return false;
|
||||
@@ -150,7 +188,7 @@ namespace Maps::Random_Generator
|
||||
}
|
||||
|
||||
for ( uint8_t direction = 0; direction < 4; ++direction ) {
|
||||
Node & adjacent = data.getNode( position + directionOffsets[direction] );
|
||||
Node & adjacent = data.getNodeToUpdate( position + directionOffsets[direction] );
|
||||
|
||||
if ( adjacent.index == -1 || adjacent.region == id ) {
|
||||
continue;
|
||||
@@ -161,7 +199,7 @@ namespace Maps::Random_Generator
|
||||
break;
|
||||
}
|
||||
|
||||
Node & twoAway = data.getNode( position + directionOffsets[direction] + directionOffsets[direction] );
|
||||
Node & twoAway = data.getNodeToUpdate( position + directionOffsets[direction] + directionOffsets[direction] );
|
||||
if ( twoAway.index == -1 || twoAway.type != NodeType::OPEN ) {
|
||||
continue;
|
||||
}
|
||||
@@ -174,7 +212,7 @@ namespace Maps::Random_Generator
|
||||
adjacent.type = NodeType::PATH;
|
||||
twoAway.type = NodeType::PATH;
|
||||
|
||||
Node & stepBack = data.getNode( position - directionOffsets[direction] );
|
||||
Node & stepBack = data.getNodeToUpdate( position - directionOffsets[direction] );
|
||||
if ( stepBack.index != -1 ) {
|
||||
stepBack.type = NodeType::PATH;
|
||||
}
|
||||
|
||||
@@ -84,12 +84,12 @@ namespace Maps::Random_Generator
|
||||
}
|
||||
};
|
||||
|
||||
class NodeCache final
|
||||
class MapStateManager final
|
||||
{
|
||||
public:
|
||||
NodeCache( const int32_t width, const int32_t height );
|
||||
MapStateManager( const int32_t width, const int32_t height );
|
||||
|
||||
Node & getNode( const fheroes2::Point position )
|
||||
Node & getNodeToUpdate( const fheroes2::Point position )
|
||||
{
|
||||
if ( position.x < 0 || position.x >= _mapSize || position.y < 0 || position.y >= _mapSize ) {
|
||||
// We shouldn't try to get a tile with an invalid index.
|
||||
@@ -100,7 +100,12 @@ namespace Maps::Random_Generator
|
||||
return _outOfBounds;
|
||||
}
|
||||
|
||||
return _data[position.y * _mapSize + position.x];
|
||||
const int32_t index = position.y * _mapSize + position.x;
|
||||
if ( !_transactionRecords.empty() ) {
|
||||
recordStateChange( index, _data[index] );
|
||||
}
|
||||
|
||||
return _data[index];
|
||||
}
|
||||
|
||||
const Node & getNode( const fheroes2::Point position ) const
|
||||
@@ -117,7 +122,26 @@ namespace Maps::Random_Generator
|
||||
return _data[position.y * _mapSize + position.x];
|
||||
}
|
||||
|
||||
Node & getNode( const int32_t index )
|
||||
Node & getNodeToUpdate( const int32_t index )
|
||||
{
|
||||
if ( index < 0 || index >= _mapSize * _mapSize ) {
|
||||
// We shouldn't try to get a tile with an invalid index.
|
||||
assert( 0 );
|
||||
|
||||
assert( _outOfBounds.type == NodeType::BORDER );
|
||||
assert( _outOfBounds.index == -1 );
|
||||
|
||||
return _outOfBounds;
|
||||
}
|
||||
|
||||
if ( !_transactionRecords.empty() ) {
|
||||
recordStateChange( index, _data[index] );
|
||||
}
|
||||
|
||||
return _data[index];
|
||||
}
|
||||
|
||||
const Node & getNode( const int32_t index ) const
|
||||
{
|
||||
if ( index < 0 || index >= _mapSize * _mapSize ) {
|
||||
// We shouldn't try to get a tile with an invalid index.
|
||||
@@ -133,9 +157,77 @@ namespace Maps::Random_Generator
|
||||
}
|
||||
|
||||
private:
|
||||
friend class MapStateTransaction;
|
||||
|
||||
struct StateChange final
|
||||
{
|
||||
int32_t index{ -1 };
|
||||
Node state;
|
||||
|
||||
StateChange() = default;
|
||||
explicit StateChange( const int32_t index_, const Node & current )
|
||||
: index( index_ )
|
||||
, state( current )
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
};
|
||||
|
||||
void recordStateChange( const int32_t index, const Node & current )
|
||||
{
|
||||
_history.emplace_back( index, current );
|
||||
}
|
||||
|
||||
size_t startTransaction()
|
||||
{
|
||||
const size_t record = _history.size();
|
||||
_transactionRecords.push_back( record );
|
||||
return record;
|
||||
}
|
||||
|
||||
void commitTransaction( const size_t record );
|
||||
void rollbackTransaction( const size_t record );
|
||||
|
||||
const int32_t _mapSize{ 0 };
|
||||
Node _outOfBounds{ -1, 0, NodeType::BORDER };
|
||||
std::vector<Node> _data;
|
||||
|
||||
std::vector<StateChange> _history;
|
||||
std::vector<size_t> _transactionRecords;
|
||||
};
|
||||
|
||||
class MapStateTransaction final
|
||||
{
|
||||
public:
|
||||
explicit MapStateTransaction( MapStateManager & manager )
|
||||
: _manager( manager )
|
||||
, _record( _manager.startTransaction() )
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
MapStateTransaction( const MapStateTransaction & ) = delete;
|
||||
MapStateTransaction & operator=( const MapStateTransaction & ) = delete;
|
||||
|
||||
~MapStateTransaction()
|
||||
{
|
||||
if ( !_committed ) {
|
||||
_manager.rollbackTransaction( _record );
|
||||
}
|
||||
}
|
||||
|
||||
void commit()
|
||||
{
|
||||
assert( !_committed );
|
||||
|
||||
_manager.commitTransaction( _record );
|
||||
_committed = true;
|
||||
}
|
||||
|
||||
private:
|
||||
MapStateManager & _manager;
|
||||
size_t _record{ 0 };
|
||||
bool _committed{ false };
|
||||
};
|
||||
|
||||
enum class RegionType : uint8_t
|
||||
@@ -178,9 +270,9 @@ namespace Maps::Random_Generator
|
||||
nodes.emplace_back( centerNode );
|
||||
}
|
||||
|
||||
void checkAdjacentTiles( NodeCache & rawData, const double distanceLimit, Rand::PCG32 & randomGenerator );
|
||||
bool regionExpansion( NodeCache & rawData, Rand::PCG32 & randomGenerator );
|
||||
bool checkNodeForConnections( NodeCache & data, std::vector<Region> & mapRegions, Node & node );
|
||||
void checkAdjacentTiles( MapStateManager & rawData, const double distanceLimit, Rand::PCG32 & randomGenerator );
|
||||
bool regionExpansion( MapStateManager & rawData, Rand::PCG32 & randomGenerator );
|
||||
bool checkNodeForConnections( MapStateManager & data, std::vector<Region> & mapRegions, Node & node );
|
||||
fheroes2::Point adjustRegionToFitCastle( const Map_Format::MapFormat & mapFormat );
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user