mirror of
https://github.com/ihhub/fheroes2.git
synced 2026-10-12 02:16:11 +00:00
Split random map generator into multiple implementation files (#10354)
This commit is contained in:
@@ -209,6 +209,8 @@
|
||||
<ClCompile Include="src\fheroes2\maps\map_format_info.cpp" />
|
||||
<ClCompile Include="src\fheroes2\maps\map_object_info.cpp" />
|
||||
<ClCompile Include="src\fheroes2\maps\map_random_generator.cpp" />
|
||||
<ClCompile Include="src\fheroes2\maps\map_random_generator_helper.cpp" />
|
||||
<ClCompile Include="src\fheroes2\maps\map_random_generator_info.cpp" />
|
||||
<ClCompile Include="src\fheroes2\maps\maps.cpp" />
|
||||
<ClCompile Include="src\fheroes2\maps\maps_fileinfo.cpp" />
|
||||
<ClCompile Include="src\fheroes2\maps\maps_objects.cpp" />
|
||||
@@ -416,6 +418,8 @@
|
||||
<ClInclude Include="src\fheroes2\maps\map_format_info.h" />
|
||||
<ClInclude Include="src\fheroes2\maps\map_object_info.h" />
|
||||
<ClInclude Include="src\fheroes2\maps\map_random_generator.h" />
|
||||
<ClInclude Include="src\fheroes2\maps\map_random_generator_helper.h" />
|
||||
<ClInclude Include="src\fheroes2\maps\map_random_generator_info.h" />
|
||||
<ClInclude Include="src\fheroes2\maps\maps.h" />
|
||||
<ClInclude Include="src\fheroes2\maps\maps_fileinfo.h" />
|
||||
<ClInclude Include="src\fheroes2\maps\maps_objects.h" />
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,569 @@
|
||||
/***************************************************************************
|
||||
* fheroes2: https://github.com/ihhub/fheroes2 *
|
||||
* Copyright (C) 2025 *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#include "map_random_generator_helper.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
|
||||
#include "color.h"
|
||||
#include "direction.h"
|
||||
#include "ground.h"
|
||||
#include "map_format_helper.h"
|
||||
#include "map_format_info.h"
|
||||
#include "map_object_info.h"
|
||||
#include "map_random_generator_info.h"
|
||||
#include "maps.h"
|
||||
#include "maps_tiles.h"
|
||||
#include "maps_tiles_helper.h"
|
||||
#include "mp2.h"
|
||||
#include "race.h"
|
||||
#include "rand.h"
|
||||
#include "ui_map_object.h"
|
||||
#include "world.h"
|
||||
#include "world_object_uid.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr int randomCastleIndex{ 12 };
|
||||
constexpr int randomTownIndex{ 13 };
|
||||
constexpr int maxPlacementAttempts{ 30 };
|
||||
constexpr std::array<std::pair<int32_t, int32_t>, 4> randomMonsterThresholds = { { { 67, 0 }, { 68, 1250 }, { 69, 3250 }, { 70, 8250 } } };
|
||||
|
||||
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 } },
|
||||
{ Maps::Ground::WASTELAND, { 12, 13, 14, 15, 16, 17 } }, { Maps::Ground::BEACH, { 24, 25, 26, 27, 28, 29 } }, { Maps::Ground::LAVA, { 6, 7, 8, 9, 10, 11 } },
|
||||
{ Maps::Ground::DIRT, { 18, 19, 20, 21, 22, 23 } }, { Maps::Ground::GRASS, { 0, 1, 2, 3, 4, 5 } }, { Maps::Ground::WATER, {} },
|
||||
};
|
||||
|
||||
// Evaluating all treasure and power-ups using gold equivalent
|
||||
// Benchmark is 1500 gold = 1000 experience; 1 primary attribute point worth 2000 gold
|
||||
// Artifacts: treasure is 1 attribute, minor is 2, major is 4 with a bonus for rarity
|
||||
const std::map<MP2::MapObjectType, int32_t> objectGoldValue = {
|
||||
{ MP2::OBJ_AIR_ALTAR, 4000 },
|
||||
{ MP2::OBJ_ARCHER_HOUSE, 2000 },
|
||||
{ MP2::OBJ_CAMPFIRE, 1000 },
|
||||
{ MP2::OBJ_CAVE, 1500 },
|
||||
{ MP2::OBJ_DESERT_TENT, 3000 },
|
||||
{ MP2::OBJ_DWARF_COTTAGE, 2000 },
|
||||
{ MP2::OBJ_EARTH_ALTAR, 4000 },
|
||||
{ MP2::OBJ_EXCAVATION, 1500 },
|
||||
{ MP2::OBJ_FIRE_ALTAR, 4000 },
|
||||
{ MP2::OBJ_FORT, 2000 },
|
||||
{ MP2::OBJ_GAZEBO, 1500 },
|
||||
{ MP2::OBJ_GENIE_LAMP, 6000 },
|
||||
{ MP2::OBJ_GOBLIN_HUT, 1500 },
|
||||
{ MP2::OBJ_HALFLING_HOLE, 1500 },
|
||||
{ MP2::OBJ_MERCENARY_CAMP, 2000 },
|
||||
{ MP2::OBJ_PEASANT_HUT, 250 },
|
||||
{ MP2::OBJ_RANDOM_ARTIFACT_MAJOR, 10000 },
|
||||
{ MP2::OBJ_RANDOM_ARTIFACT_MINOR, 4000 },
|
||||
{ MP2::OBJ_RANDOM_ARTIFACT_TREASURE, 2000 },
|
||||
{ MP2::OBJ_RANDOM_RESOURCE, 750 },
|
||||
{ MP2::OBJ_RESOURCE, 750 },
|
||||
{ MP2::OBJ_RUINS, 4000 },
|
||||
{ MP2::OBJ_SHRINE_FIRST_CIRCLE, 1000 },
|
||||
{ MP2::OBJ_SHRINE_SECOND_CIRCLE, 2000 },
|
||||
{ MP2::OBJ_SHRINE_THIRD_CIRCLE, 3000 },
|
||||
{ MP2::OBJ_STANDING_STONES, 2000 },
|
||||
{ MP2::OBJ_TREASURE_CHEST, 1500 },
|
||||
{ MP2::OBJ_TREE_CITY, 1500 },
|
||||
{ MP2::OBJ_TREE_HOUSE, 1500 },
|
||||
{ MP2::OBJ_TREE_OF_KNOWLEDGE, 3000 },
|
||||
{ MP2::OBJ_WAGON_CAMP, 2000 },
|
||||
{ MP2::OBJ_WATCH_TOWER, 2000 },
|
||||
{ MP2::OBJ_WATER_ALTAR, 4000 },
|
||||
{ MP2::OBJ_WITCH_DOCTORS_HUT, 2000 },
|
||||
{ MP2::OBJ_XANADU, 8000 },
|
||||
};
|
||||
|
||||
constexpr int32_t treeTypeFromGroundType( const int groundType )
|
||||
{
|
||||
switch ( groundType ) {
|
||||
case Maps::Ground::WATER:
|
||||
assert( 0 );
|
||||
return 0;
|
||||
case Maps::Ground::GRASS:
|
||||
return 0;
|
||||
case Maps::Ground::SNOW:
|
||||
return 30;
|
||||
case Maps::Ground::SWAMP:
|
||||
return 0;
|
||||
case Maps::Ground::LAVA:
|
||||
return 6;
|
||||
case Maps::Ground::DESERT:
|
||||
return 24;
|
||||
case Maps::Ground::DIRT:
|
||||
return 18;
|
||||
case Maps::Ground::WASTELAND:
|
||||
return 12;
|
||||
case Maps::Ground::BEACH:
|
||||
return 24;
|
||||
default:
|
||||
// Have you added a new ground? Add the logic above!
|
||||
assert( 0 );
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
constexpr int32_t mountainTypeFromGroundType( const int groundType )
|
||||
{
|
||||
switch ( groundType ) {
|
||||
case Maps::Ground::WATER:
|
||||
assert( 0 );
|
||||
return 0;
|
||||
case Maps::Ground::GRASS:
|
||||
return 6;
|
||||
case Maps::Ground::SNOW:
|
||||
return 12;
|
||||
case Maps::Ground::SWAMP:
|
||||
return 18;
|
||||
case Maps::Ground::LAVA:
|
||||
return 24;
|
||||
case Maps::Ground::DESERT:
|
||||
return 30;
|
||||
case Maps::Ground::DIRT:
|
||||
return 36;
|
||||
case Maps::Ground::WASTELAND:
|
||||
return 44;
|
||||
case Maps::Ground::BEACH:
|
||||
return 0;
|
||||
default:
|
||||
// Have you added a new ground? Add the logic above!
|
||||
assert( 0 );
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool iterateOverObjectParts( const Maps::ObjectInfo & info, const std::function<void( const Maps::ObjectPartInfo & )> & lambda )
|
||||
{
|
||||
for ( const auto & objectPart : info.groundLevelParts ) {
|
||||
if ( objectPart.layerType == Maps::SHADOW_LAYER || objectPart.layerType == Maps::TERRAIN_LAYER ) {
|
||||
// Shadow and terrain layer parts are ignored.
|
||||
continue;
|
||||
}
|
||||
|
||||
lambda( objectPart );
|
||||
}
|
||||
for ( const auto & objectPart : info.topLevelParts ) {
|
||||
lambda( objectPart );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Maps::Random_Generator
|
||||
{
|
||||
int32_t getObjectGoldValue( const Maps::ObjectGroup group, const int32_t objectIndex )
|
||||
{
|
||||
const auto & objectInfo = Maps::getObjectInfo( group, objectIndex );
|
||||
|
||||
const auto it = objectGoldValue.find( objectInfo.objectType );
|
||||
if ( it == objectGoldValue.end() ) {
|
||||
// No valuation for the object? Add it!
|
||||
assert( 0 );
|
||||
return 0;
|
||||
}
|
||||
|
||||
return it->second;
|
||||
}
|
||||
|
||||
int32_t pickMonsterByValue( const int32_t protectedObjectValue )
|
||||
{
|
||||
assert( protectedObjectValue >= 0 );
|
||||
|
||||
int32_t bestMonster = 0;
|
||||
for ( const auto & [monsterIndex, threshold] : randomMonsterThresholds ) {
|
||||
if ( protectedObjectValue >= threshold ) {
|
||||
bestMonster = monsterIndex;
|
||||
}
|
||||
}
|
||||
return bestMonster;
|
||||
}
|
||||
|
||||
int32_t selectTerrainVariantForObject( const ObjectGroup groupType, const int32_t objectIndex, const int groundType )
|
||||
{
|
||||
if ( groupType == ObjectGroup::LANDSCAPE_TREES ) {
|
||||
assert( objectIndex < 6 );
|
||||
return treeTypeFromGroundType( groundType ) + objectIndex;
|
||||
}
|
||||
|
||||
if ( groupType == ObjectGroup::LANDSCAPE_MOUNTAINS ) {
|
||||
assert( objectIndex < 8 );
|
||||
return mountainTypeFromGroundType( groundType ) + objectIndex;
|
||||
}
|
||||
return objectIndex;
|
||||
}
|
||||
|
||||
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 ) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::vector<int32_t>> buckets;
|
||||
|
||||
const int centerX = region.centerIndex % mapWidth;
|
||||
const int centerY = region.centerIndex / mapWidth;
|
||||
|
||||
for ( const Node & node : region.nodes ) {
|
||||
if ( node.type != NodeType::OPEN || node.index < 0 ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int dx = ( node.index % mapWidth ) - centerX;
|
||||
const int dy = ( node.index / mapWidth ) - centerY;
|
||||
|
||||
const double distance = std::sqrt( static_cast<double>( dx * dx + dy * dy ) );
|
||||
const uint32_t noise = static_cast<int>( Rand::GetWithGen( 0, 2, randomGenerator ) );
|
||||
const size_t ring = static_cast<size_t>( std::floor( distance ) + noise );
|
||||
if ( ring >= buckets.size() ) {
|
||||
buckets.resize( ring + 1 );
|
||||
}
|
||||
|
||||
buckets[ring].push_back( node.index );
|
||||
}
|
||||
|
||||
for ( auto & bucket : buckets ) {
|
||||
if ( bucket.empty() ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Rand::ShuffleWithGen( bucket, randomGenerator );
|
||||
}
|
||||
return buckets;
|
||||
}
|
||||
|
||||
bool canFitObject( const NodeCache & data, const ObjectInfo & info, const fheroes2::Point & mainTilePos, const bool skipBorders )
|
||||
{
|
||||
bool invalid = false;
|
||||
fheroes2::Rect objectRect;
|
||||
|
||||
auto updateObjectArea = [&data, &mainTilePos, &objectRect, skipBorders, &invalid]( const auto & partInfo ) {
|
||||
const Node & node = data.getNode( mainTilePos + partInfo.tileOffset );
|
||||
|
||||
if ( node.index == -1 || node.region == 0 ) {
|
||||
invalid = true;
|
||||
return;
|
||||
}
|
||||
if ( node.type != NodeType::OPEN && ( skipBorders || node.type != NodeType::BORDER ) ) {
|
||||
invalid = true;
|
||||
return;
|
||||
}
|
||||
|
||||
objectRect.x = std::min( objectRect.x, partInfo.tileOffset.x );
|
||||
objectRect.width = std::max( objectRect.width, partInfo.tileOffset.x );
|
||||
};
|
||||
|
||||
iterateOverObjectParts( info, updateObjectArea );
|
||||
|
||||
if ( invalid ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( MP2::isOffGameActionObject( info.objectType ) ) {
|
||||
for ( int x = objectRect.x - 1; x <= objectRect.width + 1; ++x ) {
|
||||
const Node & pathNode = data.getNode( mainTilePos + fheroes2::Point{ x, 1 } );
|
||||
if ( pathNode.index == -1 || pathNode.type == NodeType::OBSTACLE ) {
|
||||
return false;
|
||||
}
|
||||
if ( pathNode.type == NodeType::BORDER ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool canFitObjectSet( const NodeCache & data, const ObjectSet & set, const fheroes2::Point & mainTilePos )
|
||||
{
|
||||
for ( const fheroes2::Point offset : set.entranceCheck ) {
|
||||
const Node & node = data.getNode( mainTilePos + offset );
|
||||
if ( node.index == -1 || node.type != NodeType::OPEN ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for ( const auto * placements : { &set.obstacles, &set.valuables, &set.monsters } ) {
|
||||
for ( const auto & placement : *placements ) {
|
||||
const auto & info = Maps::getObjectInfo( placement.groupType, placement.objectIndex );
|
||||
const bool isAction = MP2::isInGameActionObject( info.objectType );
|
||||
const fheroes2::Point position = mainTilePos + placement.offset;
|
||||
bool invalid = false;
|
||||
|
||||
auto isValidObjectPart = [&data, &position, isAction, &invalid]( const auto & partInfo ) {
|
||||
const Node & node = data.getNode( position + partInfo.tileOffset );
|
||||
|
||||
if ( node.index == -1 || node.region == 0 ) {
|
||||
invalid = true;
|
||||
return;
|
||||
}
|
||||
if ( node.type != NodeType::OPEN && ( isAction || node.type != NodeType::BORDER ) ) {
|
||||
invalid = true;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
iterateOverObjectParts( info, isValidObjectPart );
|
||||
|
||||
if ( invalid ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void markObjectPlacement( NodeCache & 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 );
|
||||
|
||||
if ( !MP2::isOffGameActionObject( info.objectType ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
for ( int x = objectRect.x - 1; x <= objectRect.width + 1; ++x ) {
|
||||
data.getNode( mainTilePos + fheroes2::Point{ x, objectRect.height + 1 } ).type = NodeType::PATH;
|
||||
}
|
||||
|
||||
// Mark extra nodes as path to avoid objects clumping together
|
||||
data.getNode( mainTilePos + fheroes2::Point{ objectRect.x - 1, 0 } ).type = NodeType::PATH;
|
||||
data.getNode( mainTilePos + fheroes2::Point{ objectRect.width + 1, 0 } ).type = NodeType::PATH;
|
||||
|
||||
data.getNode( mainTilePos ).type = NodeType::ACTION;
|
||||
}
|
||||
|
||||
bool putObjectOnMap( Map_Format::MapFormat & mapFormat, Tile & tile, const ObjectGroup groupType, const int32_t objectIndex )
|
||||
{
|
||||
const auto & objectInfo = Maps::getObjectInfo( groupType, objectIndex );
|
||||
if ( objectInfo.empty() ) {
|
||||
assert( 0 );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Maps::setObjectOnTile isn't idempotent, check if object was already placed
|
||||
if ( MP2::isInGameActionObject( objectInfo.objectType ) && tile.getMainObjectType() == objectInfo.objectType ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do not update passabilities after every object placement.
|
||||
if ( !Maps::setObjectOnTile( tile, objectInfo, false ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Maps::addObjectToMap( mapFormat, tile.GetIndex(), groupType, static_cast<uint32_t>( objectIndex ) );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool placeActionObject( Map_Format::MapFormat & mapFormat, NodeCache & 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 ) ) {
|
||||
markObjectPlacement( data, objectInfo, tilePos );
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool placeCastle( Map_Format::MapFormat & mapFormat, NodeCache & data, const Region & region, const fheroes2::Point tilePos, const bool isCastle )
|
||||
{
|
||||
auto & tile = world.getTile( tilePos.x, tilePos.y );
|
||||
if ( tile.isWater() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int32_t basementId = fheroes2::getTownBasementId( tile.GetGround() );
|
||||
const int32_t castleObjectId = isCastle ? randomCastleIndex : randomTownIndex;
|
||||
|
||||
const auto & basementInfo = Maps::getObjectInfo( ObjectGroup::LANDSCAPE_TOWN_BASEMENTS, basementId );
|
||||
const auto & castleInfo = Maps::getObjectInfo( ObjectGroup::KINGDOM_TOWNS, castleObjectId );
|
||||
|
||||
if ( !canFitObject( data, basementInfo, tilePos, true ) || !canFitObject( data, castleInfo, tilePos, true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( !putObjectOnMap( mapFormat, tile, ObjectGroup::LANDSCAPE_TOWN_BASEMENTS, basementId ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Since the whole object consists of multiple "objects" we have to put the same ID for all of them.
|
||||
// Every time an object is being placed on a map the counter is going to be increased by 1.
|
||||
// Therefore, we set the counter by 1 less for each object to match object UID for all of them.
|
||||
assert( Maps::getLastObjectUID() > 0 );
|
||||
const uint32_t objectId = Maps::getLastObjectUID() - 1;
|
||||
|
||||
Maps::setLastObjectUID( objectId );
|
||||
|
||||
if ( !putObjectOnMap( mapFormat, tile, ObjectGroup::KINGDOM_TOWNS, castleObjectId ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int32_t bottomIndex = Maps::GetDirectionIndex( tile.GetIndex(), Direction::BOTTOM );
|
||||
|
||||
if ( Maps::isValidAbsIndex( bottomIndex ) && Maps::doesContainRoads( mapFormat.tiles[bottomIndex] ) ) {
|
||||
// Update road if there is one in front of the town/castle entrance.
|
||||
Maps::updateRoadSpriteOnTile( mapFormat, bottomIndex, false );
|
||||
}
|
||||
|
||||
// By default use random (default) army for the neutral race town/castle.
|
||||
const PlayerColor color = Color::IndexToColor( region.colorIndex );
|
||||
if ( color == PlayerColor::NONE ) {
|
||||
Maps::setDefaultCastleDefenderArmy( mapFormat.castleMetadata[Maps::getLastObjectUID()] );
|
||||
}
|
||||
|
||||
// Add flags.
|
||||
assert( tile.GetIndex() > 0 && tile.GetIndex() < world.w() * world.h() - 1 );
|
||||
Maps::setLastObjectUID( objectId );
|
||||
|
||||
if ( !putObjectOnMap( mapFormat, world.getTile( tile.GetIndex() - 1 ), ObjectGroup::LANDSCAPE_FLAGS, Color::GetIndex( color ) * 2 ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Maps::setLastObjectUID( objectId );
|
||||
|
||||
if ( !putObjectOnMap( mapFormat, world.getTile( tile.GetIndex() + 1 ), ObjectGroup::LANDSCAPE_FLAGS, Color::GetIndex( color ) * 2 + 1 ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ObjectInfo & townObjectInfo = Maps::getObjectInfo( ObjectGroup::KINGDOM_TOWNS, castleObjectId );
|
||||
const uint8_t race = Race::IndexToRace( static_cast<int>( townObjectInfo.metadata[0] ) );
|
||||
|
||||
world.addCastle( tile.GetIndex(), race, color );
|
||||
|
||||
markObjectPlacement( data, basementInfo, tilePos );
|
||||
markObjectPlacement( data, castleInfo, tilePos );
|
||||
|
||||
// Force roads coming from the castle
|
||||
const int32_t nextIndex = Maps::GetDirectionIndex( bottomIndex, Direction::BOTTOM );
|
||||
if ( Maps::isValidAbsIndex( nextIndex ) ) {
|
||||
Maps::updateRoadOnTile( mapFormat, bottomIndex, true );
|
||||
Maps::updateRoadOnTile( mapFormat, nextIndex, true );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool placeMine( Map_Format::MapFormat & mapFormat, NodeCache & 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 placeObstacle( Map_Format::MapFormat & mapFormat, const NodeCache & data, const Node & node, Rand::PCG32 & randomGenerator )
|
||||
{
|
||||
Tile & tile = world.getTile( node.index );
|
||||
const auto it = obstaclesPerGround.find( tile.GetGround() );
|
||||
if ( it == obstaclesPerGround.end() || it->second.empty() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<int> obstacleList = it->second;
|
||||
Rand::ShuffleWithGen( obstacleList, randomGenerator );
|
||||
|
||||
const fheroes2::Point tilePos = tile.GetCenter();
|
||||
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 ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool placeSimpleObject( Map_Format::MapFormat & mapFormat, NodeCache & data, const Node & centerNode, const ObjectPlacement & placement )
|
||||
{
|
||||
const fheroes2::Point position = Maps::GetPoint( centerNode.index ) + placement.offset;
|
||||
if ( !Maps::isValidAbsPoint( position.x, position.y ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Tile & tile = world.getTile( position.x, position.y );
|
||||
const int32_t objectIndex = selectTerrainVariantForObject( placement.groupType, placement.objectIndex, tile.GetGround() );
|
||||
const auto & objectInfo = Maps::getObjectInfo( placement.groupType, objectIndex );
|
||||
if ( putObjectOnMap( mapFormat, tile, placement.groupType, objectIndex ) ) {
|
||||
markObjectPlacement( data, objectInfo, position );
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void placeObjectSet( Map_Format::MapFormat & mapFormat, NodeCache & data, const Region & region, std::vector<ObjectSet> objects, const uint8_t expectedCount,
|
||||
Rand::PCG32 & randomGenerator )
|
||||
{
|
||||
int objectsPlaced = 0;
|
||||
for ( int attempt = 0; attempt < maxPlacementAttempts; ++attempt ) {
|
||||
if ( objectsPlaced == expectedCount ) {
|
||||
break;
|
||||
}
|
||||
|
||||
const Node & node = Rand::GetWithGen( region.nodes, randomGenerator );
|
||||
|
||||
Rand::ShuffleWithGen( objects, randomGenerator );
|
||||
for ( const auto & prefab : objects ) {
|
||||
if ( !canFitObjectSet( data, prefab, Maps::GetPoint( node.index ) ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for ( const auto & obstacle : prefab.obstacles ) {
|
||||
placeSimpleObject( mapFormat, data, node, obstacle );
|
||||
}
|
||||
|
||||
int32_t groupValue = 0;
|
||||
for ( const auto & treasure : prefab.valuables ) {
|
||||
placeSimpleObject( mapFormat, data, node, treasure );
|
||||
groupValue += getObjectGoldValue( treasure.groupType, treasure.objectIndex );
|
||||
}
|
||||
|
||||
const int32_t monsterIndex = pickMonsterByValue( groupValue );
|
||||
for ( const auto & monster : prefab.monsters ) {
|
||||
const fheroes2::Point position = Maps::GetPoint( node.index ) + monster.offset;
|
||||
putObjectOnMap( mapFormat, world.getTile( position.x, position.y ), ObjectGroup::MONSTERS, monsterIndex );
|
||||
}
|
||||
++objectsPlaced;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/***************************************************************************
|
||||
* fheroes2: https://github.com/ihhub/fheroes2 *
|
||||
* Copyright (C) 2025 *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "math_base.h"
|
||||
|
||||
namespace Maps
|
||||
{
|
||||
class Tile;
|
||||
struct ObjectInfo;
|
||||
enum class ObjectGroup : uint8_t;
|
||||
}
|
||||
|
||||
namespace Maps::Random_Generator
|
||||
{
|
||||
class NodeCache;
|
||||
struct Node;
|
||||
struct ObjectPlacement;
|
||||
struct ObjectSet;
|
||||
struct Region;
|
||||
}
|
||||
|
||||
namespace Maps::Map_Format
|
||||
{
|
||||
struct MapFormat;
|
||||
}
|
||||
|
||||
namespace Rand
|
||||
{
|
||||
class PCG32;
|
||||
}
|
||||
|
||||
namespace Maps::Random_Generator
|
||||
{
|
||||
int32_t getObjectGoldValue( const ObjectGroup group, const int32_t objectIndex );
|
||||
int32_t pickMonsterByValue( const int32_t protectedObjectValue );
|
||||
int32_t selectTerrainVariantForObject( const ObjectGroup groupType, const int32_t objectIndex, const int groundType );
|
||||
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 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 placeObstacle( Map_Format::MapFormat & mapFormat, const NodeCache & data, const Node & node, Rand::PCG32 & randomGenerator );
|
||||
bool placeSimpleObject( Map_Format::MapFormat & mapFormat, NodeCache & data, const Node & centerNode, const ObjectPlacement & placement );
|
||||
void placeObjectSet( Map_Format::MapFormat & mapFormat, NodeCache & data, const Region & region, std::vector<ObjectSet> objects, const uint8_t expectedCount,
|
||||
Rand::PCG32 & randomGenerator );
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/***************************************************************************
|
||||
* fheroes2: https://github.com/ihhub/fheroes2 *
|
||||
* Copyright (C) 2025 *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#include "map_random_generator_info.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <ostream>
|
||||
#include <utility>
|
||||
|
||||
#include "logging.h"
|
||||
#include "map_format_info.h"
|
||||
#include "maps.h"
|
||||
#include "rand.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr uint8_t directionCount{ 8 };
|
||||
const std::array<fheroes2::Point, directionCount> directionOffsets{ { { 0, -1 }, { 1, 0 }, { 0, 1 }, { -1, 0 }, { -1, -1 }, { 1, -1 }, { 1, 1 }, { -1, 1 } } };
|
||||
}
|
||||
|
||||
namespace Maps::Random_Generator
|
||||
{
|
||||
NodeCache::NodeCache( const int32_t width, const int32_t height )
|
||||
: _mapSize( width )
|
||||
, _data( static_cast<size_t>( width ) * height )
|
||||
{
|
||||
assert( width > 0 && height > 0 );
|
||||
assert( width == height );
|
||||
|
||||
for ( size_t i = 0; i < _data.size(); ++i ) {
|
||||
_data[i].index = static_cast<int>( i );
|
||||
}
|
||||
}
|
||||
|
||||
void MapEconomy::increaseMineCount( const int resourceType )
|
||||
{
|
||||
const auto it = _minesCount.find( resourceType );
|
||||
assert( it != _minesCount.end() );
|
||||
++it->second;
|
||||
}
|
||||
|
||||
int MapEconomy::pickNextMineResource()
|
||||
{
|
||||
const auto it = std::min_element( secondaryResources.begin(), secondaryResources.end(),
|
||||
[this]( const auto & a, const auto & b ) { return _minesCount.at( a ) < _minesCount.at( b ); } );
|
||||
assert( it != secondaryResources.end() );
|
||||
|
||||
return *it;
|
||||
}
|
||||
|
||||
void Region::checkAdjacentTiles( NodeCache & rawData, Rand::PCG32 & randomGenerator )
|
||||
{
|
||||
Node & previousNode = nodes[lastProcessedNode];
|
||||
const int nodeIndex = previousNode.index;
|
||||
|
||||
for ( uint8_t direction = 0; direction < directionCount; ++direction ) {
|
||||
if ( nodes.size() > sizeLimit ) {
|
||||
previousNode.type = NodeType::BORDER;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check diagonal direction only 50% of the time to get more circular distribution.
|
||||
// It gives randomness for uneven edges.
|
||||
if ( direction > 3 && Rand::GetWithGen( 0, 1, randomGenerator ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: use node index and pre-calculate offsets in advance.
|
||||
// This will speed up the below calculations.
|
||||
const fheroes2::Point newPosition = Maps::GetPoint( nodeIndex ) + directionOffsets[direction];
|
||||
if ( !Maps::isValidAbsPoint( newPosition.x, newPosition.y ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Node & newTile = rawData.getNode( newPosition );
|
||||
if ( newTile.region == 0 && newTile.type == NodeType::OPEN ) {
|
||||
newTile.region = id;
|
||||
nodes.emplace_back( newTile );
|
||||
}
|
||||
else if ( newTile.region != id ) {
|
||||
previousNode.type = NodeType::BORDER;
|
||||
neighbours.insert( newTile.region );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Region::regionExpansion( NodeCache & 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();
|
||||
|
||||
while ( lastProcessedNode < nodesEnd ) {
|
||||
checkAdjacentTiles( rawData, randomGenerator );
|
||||
++lastProcessedNode;
|
||||
}
|
||||
return lastProcessedNode != nodes.size();
|
||||
}
|
||||
|
||||
bool Region::checkNodeForConnections( NodeCache & data, std::vector<Region> & mapRegions, Node & node )
|
||||
{
|
||||
if ( node.type != NodeType::BORDER ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const fheroes2::Point position = Maps::GetPoint( node.index );
|
||||
|
||||
int distinctNeighbours = 0;
|
||||
uint32_t seen = node.region;
|
||||
for ( uint8_t direction = 0; direction < directionCount; ++direction ) {
|
||||
const Node & newTile = data.getNode( position + directionOffsets[direction] );
|
||||
|
||||
if ( newTile.index == -1 || newTile.region == id ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( seen != newTile.region ) {
|
||||
seen = newTile.region;
|
||||
|
||||
++distinctNeighbours;
|
||||
if ( distinctNeighbours > 1 ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ( uint8_t direction = 0; direction < 4; ++direction ) {
|
||||
Node & adjacent = data.getNode( position + directionOffsets[direction] );
|
||||
|
||||
if ( adjacent.index == -1 || adjacent.region == id || mapRegions[adjacent.region].groundType == Ground::WATER ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Node & twoAway = data.getNode( position + directionOffsets[direction] + directionOffsets[direction] );
|
||||
if ( twoAway.index == -1 || twoAway.type != NodeType::OPEN ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( connections.find( adjacent.region ) == connections.end() ) {
|
||||
DEBUG_LOG( DBG_DEVEL, DBG_TRACE, "Found a connection between " << id << " and " << adjacent.region << ", via " << node.index )
|
||||
connections.emplace( adjacent.region, node.index );
|
||||
mapRegions[adjacent.region].connections.emplace( id, node.index );
|
||||
node.type = NodeType::CONNECTOR;
|
||||
adjacent.type = NodeType::PATH;
|
||||
twoAway.type = NodeType::PATH;
|
||||
|
||||
Node & stepBack = data.getNode( position - directionOffsets[direction] );
|
||||
if ( stepBack.index != -1 ) {
|
||||
stepBack.type = NodeType::PATH;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return node.type == NodeType::CONNECTOR;
|
||||
}
|
||||
|
||||
fheroes2::Point Region::adjustRegionToFitCastle( const Map_Format::MapFormat & mapFormat )
|
||||
{
|
||||
const fheroes2::Point startingLocation = Maps::GetPoint( centerIndex );
|
||||
const int32_t castleX = std::min( std::max( startingLocation.x, 4 ), mapFormat.width - 4 );
|
||||
const int32_t castleY = std::min( std::max( startingLocation.y, 4 ), mapFormat.width - 4 );
|
||||
centerIndex = Maps::GetIndexFromAbsPoint( castleX, castleY + 2 );
|
||||
return { castleX, castleY };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/***************************************************************************
|
||||
* fheroes2: https://github.com/ihhub/fheroes2 *
|
||||
* Copyright (C) 2025 *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "color.h"
|
||||
#include "ground.h"
|
||||
#include "map_object_info.h"
|
||||
#include "math_base.h"
|
||||
#include "resource.h"
|
||||
|
||||
namespace Maps::Map_Format
|
||||
{
|
||||
struct MapFormat;
|
||||
}
|
||||
|
||||
namespace Rand
|
||||
{
|
||||
class PCG32;
|
||||
}
|
||||
|
||||
namespace Maps::Random_Generator
|
||||
{
|
||||
inline int neutralColorIndex{ Color::GetIndex( PlayerColor::UNUSED ) };
|
||||
constexpr std::array<int, 4> secondaryResources = { Resource::CRYSTAL, Resource::SULFUR, Resource::GEMS, Resource::MERCURY };
|
||||
|
||||
enum class NodeType : uint8_t
|
||||
{
|
||||
OPEN,
|
||||
BORDER,
|
||||
ACTION,
|
||||
OBSTACLE,
|
||||
CONNECTOR,
|
||||
PATH
|
||||
};
|
||||
|
||||
struct Node final
|
||||
{
|
||||
int index{ -1 };
|
||||
uint32_t region{ 0 };
|
||||
NodeType type{ NodeType::OPEN };
|
||||
|
||||
Node() = default;
|
||||
|
||||
explicit Node( const int index_ )
|
||||
: index( index_ )
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
explicit Node( const int index_, const uint32_t region_, const NodeType type_ )
|
||||
: index( index_ )
|
||||
, region( region_ )
|
||||
, type( type_ )
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
};
|
||||
|
||||
class NodeCache final
|
||||
{
|
||||
public:
|
||||
NodeCache( const int32_t width, const int32_t height );
|
||||
|
||||
Node & getNode( 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.
|
||||
// TODO: here we must add an assertion and make sure that we never reach this place.
|
||||
assert( _outOfBounds.type == NodeType::BORDER );
|
||||
assert( _outOfBounds.index == -1 );
|
||||
|
||||
return _outOfBounds;
|
||||
}
|
||||
|
||||
return _data[position.y * _mapSize + position.x];
|
||||
}
|
||||
|
||||
const Node & getNode( const fheroes2::Point position ) const
|
||||
{
|
||||
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.
|
||||
// However, since we are accessing const value it is fine to get an invalid item.
|
||||
assert( _outOfBounds.type == NodeType::BORDER );
|
||||
assert( _outOfBounds.index == -1 );
|
||||
|
||||
return _outOfBounds;
|
||||
}
|
||||
|
||||
return _data[position.y * _mapSize + position.x];
|
||||
}
|
||||
|
||||
Node & getNode( 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;
|
||||
}
|
||||
|
||||
return _data[index];
|
||||
}
|
||||
|
||||
private:
|
||||
const int32_t _mapSize{ 0 };
|
||||
Node _outOfBounds{ -1, 0, NodeType::BORDER };
|
||||
std::vector<Node> _data;
|
||||
};
|
||||
|
||||
struct Region final
|
||||
{
|
||||
uint32_t id{ 0 };
|
||||
int32_t centerIndex{ -1 };
|
||||
std::set<uint32_t> neighbours;
|
||||
std::vector<std::reference_wrapper<Node>> nodes;
|
||||
std::map<uint32_t, int32_t> connections;
|
||||
size_t sizeLimit{ 0 };
|
||||
size_t lastProcessedNode{ 0 };
|
||||
int colorIndex{ neutralColorIndex };
|
||||
int groundType{ Ground::GRASS };
|
||||
|
||||
Region() = default;
|
||||
|
||||
Region( const uint32_t regionIndex, Node & centerNode, const int playerColor, const int ground, const size_t expectedSize )
|
||||
: id( regionIndex )
|
||||
, centerIndex( centerNode.index )
|
||||
, sizeLimit( expectedSize )
|
||||
, colorIndex( playerColor )
|
||||
, groundType( ground )
|
||||
{
|
||||
assert( expectedSize > 0 );
|
||||
|
||||
centerNode.region = regionIndex;
|
||||
nodes.reserve( expectedSize );
|
||||
nodes.emplace_back( centerNode );
|
||||
}
|
||||
|
||||
void checkAdjacentTiles( NodeCache & rawData, Rand::PCG32 & randomGenerator );
|
||||
bool regionExpansion( NodeCache & rawData, Rand::PCG32 & randomGenerator );
|
||||
bool checkNodeForConnections( NodeCache & data, std::vector<Region> & mapRegions, Node & node );
|
||||
fheroes2::Point adjustRegionToFitCastle( const Map_Format::MapFormat & mapFormat );
|
||||
};
|
||||
|
||||
class MapEconomy final
|
||||
{
|
||||
public:
|
||||
void increaseMineCount( const int resourceType );
|
||||
int pickNextMineResource();
|
||||
|
||||
private:
|
||||
std::map<int, uint32_t> _minesCount{ { Resource::WOOD, 0 }, { Resource::ORE, 0 }, { Resource::CRYSTAL, 0 }, { Resource::SULFUR, 0 },
|
||||
{ Resource::GEMS, 0 }, { Resource::MERCURY, 0 }, { Resource::GOLD, 0 } };
|
||||
};
|
||||
|
||||
struct RegionalObjects final
|
||||
{
|
||||
uint8_t castleCount{ 0 };
|
||||
uint8_t mineCount{ 0 };
|
||||
uint8_t objectCount{ 0 };
|
||||
uint8_t powerUpsCount{ 0 };
|
||||
uint8_t treasureCount{ 0 };
|
||||
};
|
||||
|
||||
struct ObjectPlacement final
|
||||
{
|
||||
fheroes2::Point offset;
|
||||
ObjectGroup groupType{ ObjectGroup::NONE };
|
||||
int32_t objectIndex{ 0 };
|
||||
};
|
||||
|
||||
struct ObjectSet final
|
||||
{
|
||||
std::vector<ObjectPlacement> obstacles;
|
||||
std::vector<ObjectPlacement> valuables;
|
||||
std::vector<ObjectPlacement> monsters;
|
||||
std::vector<fheroes2::Point> entranceCheck;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user