2013-08-17 15:46:48 +03:00
/*
* CRmgTemplateZone . 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 "CRmgTemplateZone.h"
2014-05-22 20:25:17 +03:00
# include "../mapping/CMapEditManager.h"
# include "../mapping/CMap.h"
2013-08-17 15:46:48 +03:00
# include "../VCMI_Lib.h"
# include "../CTownHandler.h"
2014-06-01 15:10:44 +03:00
# include "../CCreatureHandler.h"
2015-02-02 10:25:26 +02:00
# include "../spells/CSpellHandler.h" //for choosing random spells
2013-08-17 15:46:48 +03:00
2014-07-06 23:14:37 +03:00
# include "../mapObjects/CommonConstructors.h"
# include "../mapObjects/MapObjects.h" //needed to resolve templates for CommonConstructors.h
2014-06-22 17:26:08 +03:00
# include "../mapObjects/CGPandoraBox.h"
# include "../mapObjects/CRewardableObject.h"
2014-06-05 19:52:14 +03:00
2014-05-22 20:25:17 +03:00
class CMap ;
class CMapEditManager ;
2014-07-06 23:14:37 +03:00
//class CGObjectInstance;
2014-05-22 20:25:17 +03:00
2018-03-05 16:05:17 +02:00
using namespace rmg ; //TODO: move all to namespace
2013-08-17 15:46:48 +03:00
2015-01-18 15:19:00 +02:00
void CRmgTemplateZone : : addRoadNode ( const int3 & node )
{
roadNodes . insert ( node ) ;
}
2022-06-20 16:39:50 +02:00
CTileInfo : : CTileInfo ( ) : nearestObjectDistance ( float ( INT_MAX ) ) , terrain ( )
2014-05-22 20:25:17 +03:00
{
2014-05-30 17:50:06 +03:00
occupied = ETileType : : POSSIBLE ; //all tiles are initially possible to place objects or passages
2014-05-22 20:25:17 +03:00
}
2014-07-25 18:10:16 +03:00
float CTileInfo : : getNearestObjectDistance ( ) const
2014-05-22 20:25:17 +03:00
{
return nearestObjectDistance ;
}
2014-07-25 18:10:16 +03:00
void CTileInfo : : setNearestObjectDistance ( float value )
2014-05-22 20:25:17 +03:00
{
2014-07-25 18:10:16 +03:00
nearestObjectDistance = std : : max < float > ( 0 , value ) ; //never negative (or unitialized)
2014-05-22 20:25:17 +03:00
}
2014-05-30 17:50:06 +03:00
bool CTileInfo : : shouldBeBlocked ( ) const
2014-05-22 20:25:17 +03:00
{
2014-05-30 17:50:06 +03:00
return occupied = = ETileType : : BLOCKED ;
2014-05-22 20:25:17 +03:00
}
2014-05-30 17:50:06 +03:00
bool CTileInfo : : isBlocked ( ) const
2014-05-22 20:25:17 +03:00
{
2014-05-30 17:50:06 +03:00
return occupied = = ETileType : : BLOCKED | | occupied = = ETileType : : USED ;
2014-05-22 20:25:17 +03:00
}
2014-05-30 17:50:06 +03:00
bool CTileInfo : : isPossible ( ) const
2014-05-22 20:25:17 +03:00
{
2014-05-30 17:50:06 +03:00
return occupied = = ETileType : : POSSIBLE ;
2014-05-22 20:25:17 +03:00
}
2014-05-30 17:50:06 +03:00
bool CTileInfo : : isFree ( ) const
{
return occupied = = ETileType : : FREE ;
}
2015-01-18 15:19:00 +02:00
bool CTileInfo : : isRoad ( ) const
{
2022-06-20 16:39:50 +02:00
return roadType ! = ROAD_NAMES [ 0 ] ;
2015-01-18 15:19:00 +02:00
}
2014-07-04 00:11:24 +03:00
bool CTileInfo : : isUsed ( ) const
{
return occupied = = ETileType : : USED ;
}
2014-05-30 17:50:06 +03:00
void CTileInfo : : setOccupied ( ETileType : : ETileType value )
2014-05-22 20:25:17 +03:00
{
occupied = value ;
}
2015-01-16 10:40:11 +02:00
ETileType : : ETileType CTileInfo : : getTileType ( ) const
{
return occupied ;
}
2022-06-20 16:39:50 +02:00
Terrain CTileInfo : : getTerrainType ( ) const
2014-05-22 20:25:17 +03:00
{
return terrain ;
}
2022-06-20 16:39:50 +02:00
void CTileInfo : : setTerrainType ( Terrain value )
2014-05-22 20:25:17 +03:00
{
terrain = value ;
}
2022-06-20 16:39:50 +02:00
void CTileInfo : : setRoadType ( const std : : string & value )
2015-01-03 05:59:51 +02:00
{
roadType = value ;
// setOccupied(ETileType::FREE);
}
2022-05-28 15:03:50 +02:00
CRmgTemplateZone : : CRmgTemplateZone ( CMapGenerator * Gen )
2018-03-05 16:05:17 +02:00
: ZoneOptions ( ) ,
2014-10-30 14:03:53 +02:00
townType ( ETownType : : NEUTRAL ) ,
2022-06-20 16:39:50 +02:00
terrainType ( Terrain ( " grass " ) ) ,
2015-03-01 11:20:49 +02:00
minGuardedValue ( 0 ) ,
2018-03-09 20:11:20 +02:00
questArtZone ( ) ,
2022-05-28 15:03:50 +02:00
gen ( Gen )
2013-08-17 15:46:48 +03:00
{
}
2022-05-28 15:03:50 +02:00
bool CRmgTemplateZone : : isUnderground ( ) const
2013-08-17 15:46:48 +03:00
{
2022-05-28 15:03:50 +02:00
return getPos ( ) . z ;
2013-08-17 15:46:48 +03:00
}
2022-05-31 11:25:39 +02:00
void CRmgTemplateZone : : setOptions ( const ZoneOptions & options )
2014-05-24 13:42:06 +03:00
{
2022-05-31 11:25:39 +02:00
ZoneOptions : : operator = ( options ) ;
2014-05-24 13:42:06 +03:00
}
2018-03-09 20:11:20 +02:00
void CRmgTemplateZone : : setQuestArtZone ( std : : shared_ptr < CRmgTemplateZone > otherZone )
2015-03-01 11:20:49 +02:00
{
questArtZone = otherZone ;
}
2014-06-26 21:12:37 +03:00
std : : set < int3 > * CRmgTemplateZone : : getFreePaths ( )
{
return & freePaths ;
}
2022-05-31 11:25:39 +02:00
void CRmgTemplateZone : : addFreePath ( const int3 & p )
{
gen - > setOccupied ( p , ETileType : : FREE ) ;
freePaths . insert ( p ) ;
}
2014-05-24 13:42:06 +03:00
float3 CRmgTemplateZone : : getCenter ( ) const
{
return center ;
}
2014-05-30 17:50:06 +03:00
void CRmgTemplateZone : : setCenter ( const float3 & f )
2014-05-24 13:42:06 +03:00
{
//limit boundaries to (0,1) square
2016-02-23 13:11:25 +02:00
//alternate solution - wrap zone around unitary square. If it doesn't fit on one side, will come out on the opposite side
center = f ;
2016-07-10 14:32:45 +02:00
2020-10-01 10:38:06 +02:00
center . x = static_cast < float > ( std : : fmod ( center . x , 1 ) ) ;
center . y = static_cast < float > ( std : : fmod ( center . y , 1 ) ) ;
2016-07-10 14:32:45 +02:00
2022-05-31 11:25:39 +02:00
if ( center . x < 0 ) //fmod seems to work only for positive numbers? we want to stay positive
2016-07-10 14:32:45 +02:00
center . x = 1 - std : : abs ( center . x ) ;
2022-05-31 11:25:39 +02:00
if ( center . y < 0 )
2016-07-10 14:32:45 +02:00
center . y = 1 - std : : abs ( center . y ) ;
2014-05-24 13:42:06 +03:00
}
2014-05-22 20:25:17 +03:00
bool CRmgTemplateZone : : pointIsIn ( int x , int y )
{
2014-05-24 15:06:08 +03:00
return true ;
2014-05-22 20:25:17 +03:00
}
2014-05-30 17:50:06 +03:00
int3 CRmgTemplateZone : : getPos ( ) const
2014-05-22 20:25:17 +03:00
{
2014-05-24 13:42:06 +03:00
return pos ;
}
2014-05-25 12:02:15 +03:00
void CRmgTemplateZone : : setPos ( const int3 & Pos )
2014-05-24 13:42:06 +03:00
{
pos = Pos ;
2014-05-22 20:25:17 +03:00
}
2022-05-31 11:25:39 +02:00
void CRmgTemplateZone : : addTile ( const int3 & Pos )
{
tileinfo . insert ( Pos ) ;
}
void CRmgTemplateZone : : removeTile ( const int3 & Pos )
2014-05-24 15:06:08 +03:00
{
2022-05-31 11:25:39 +02:00
tileinfo . erase ( Pos ) ;
possibleTiles . erase ( Pos ) ;
2014-05-30 22:23:41 +03:00
}
2014-05-31 11:56:14 +03:00
std : : set < int3 > CRmgTemplateZone : : getTileInfo ( ) const
{
return tileinfo ;
}
2016-08-13 19:48:44 +02:00
std : : set < int3 > CRmgTemplateZone : : getPossibleTiles ( ) const
{
return possibleTiles ;
}
2014-05-31 11:56:14 +03:00
2022-05-31 11:25:39 +02:00
std : : set < int3 > CRmgTemplateZone : : collectDistantTiles ( float distance ) const
2014-07-07 19:01:15 +03:00
{
//TODO: mark tiles beyond zone as unavailable, but allow to connect with adjacent zones
//for (auto tile : tileinfo)
//{
// if (tile.dist2d(this->pos) > distance)
// {
// gen->setOccupied(tile, ETileType::USED);
// //gen->setOccupied(tile, ETileType::BLOCKED); //fixme: crash at rendering?
// }
//}
2022-05-31 11:25:39 +02:00
std : : set < int3 > discardedTiles ;
for ( auto & tile : tileinfo )
2014-07-03 13:28:51 +03:00
{
2022-05-31 11:25:39 +02:00
if ( tile . dist2d ( this - > pos ) > distance )
{
discardedTiles . insert ( tile ) ;
}
} ;
return discardedTiles ;
2014-07-03 13:28:51 +03:00
}
2015-01-16 20:28:27 +02:00
void CRmgTemplateZone : : clearTiles ( )
{
tileinfo . clear ( ) ;
}
2017-11-03 22:03:51 +02:00
void CRmgTemplateZone : : initFreeTiles ( )
2014-07-24 20:16:49 +03:00
{
2022-05-31 11:25:39 +02:00
vstd : : copy_if ( tileinfo , vstd : : set_inserter ( possibleTiles ) , [ this ] ( const int3 & tile ) - > bool
2014-07-24 20:16:49 +03:00
{
return gen - > isPossible ( tile ) ;
} ) ;
2022-05-31 11:25:39 +02:00
if ( freePaths . empty ( ) )
2016-08-15 21:37:38 +02:00
{
2022-05-31 11:25:39 +02:00
addFreePath ( pos ) ; //zone must have at least one free tile where other paths go - for instance in the center
2016-08-15 21:37:38 +02:00
}
2014-07-24 20:16:49 +03:00
}
2017-11-03 22:03:51 +02:00
void CRmgTemplateZone : : createBorder ( )
2014-05-30 22:23:41 +03:00
{
2022-05-31 11:25:39 +02:00
for ( auto tile : tileinfo )
2014-05-30 22:23:41 +03:00
{
2016-08-09 10:12:13 +02:00
bool edge = false ;
2017-11-03 22:03:51 +02:00
gen - > foreach_neighbour ( tile , [ this , & edge ] ( int3 & pos )
2014-05-30 22:23:41 +03:00
{
2016-08-09 10:12:13 +02:00
if ( edge )
return ; //optimization - do it only once
if ( gen - > getZoneID ( pos ) ! = id ) //optimization - better than set search
2014-05-31 11:56:14 +03:00
{
2022-05-31 11:25:39 +02:00
//bugfix with missing pos
if ( gen - > isPossible ( pos ) )
gen - > setOccupied ( pos , ETileType : : BLOCKED ) ;
2016-08-09 10:12:13 +02:00
//we are edge if at least one tile does not belong to zone
//mark all nearby tiles blocked and we're done
2017-11-03 22:03:51 +02:00
gen - > foreach_neighbour ( pos , [ this ] ( int3 & nearbyPos )
2014-05-31 11:56:14 +03:00
{
2016-08-09 10:12:13 +02:00
if ( gen - > isPossible ( nearbyPos ) )
gen - > setOccupied ( nearbyPos , ETileType : : BLOCKED ) ;
2014-05-31 11:56:14 +03:00
} ) ;
2016-09-08 13:24:28 +02:00
edge = true ;
2014-05-31 11:56:14 +03:00
}
} ) ;
}
}
2022-05-31 11:25:39 +02:00
void CRmgTemplateZone : : createWater ( EWaterContent : : EWaterContent waterContent , bool debug )
{
if ( waterContent = = EWaterContent : : NONE | | isUnderground ( ) )
return ; //do nothing
std : : set < int3 > waterTiles = collectDistantTiles ( ( float ) ( getSize ( ) + 1 ) ) ;
//add border tiles as water for ISLANDS
if ( waterContent = = EWaterContent : : ISLANDS )
{
for ( auto & tile : tileinfo )
{
if ( gen - > shouldBeBlocked ( tile ) )
{
waterTiles . insert ( tile ) ;
}
}
}
std : : list < int3 > tilesQueue ( waterTiles . begin ( ) , waterTiles . end ( ) ) ; //tiles need to be processed
std : : set < int3 > tilesChecked = waterTiles ; //tiles already processed
std : : map < int , std : : set < int3 > > coastTilesMap ; //key: distance to water; value: tiles with that distance
std : : map < int3 , int > tilesDist ; //key: tile; value: distance to water
//optimization: prefill distance for all tiles marked for water with 0
for ( auto & tile : waterTiles )
{
tilesDist [ tile ] = 0 ;
}
//fills the distance-to-water map
while ( ! tilesQueue . empty ( ) )
{
int3 src = tilesQueue . front ( ) ;
tilesQueue . pop_front ( ) ;
gen - > foreachDirectNeighbour ( src , [ this , & src , & tilesDist , & tilesChecked , & coastTilesMap , & tilesQueue ] ( const int3 & dst )
{
if ( tilesChecked . find ( dst ) ! = tilesChecked . end ( ) )
return ;
if ( tileinfo . find ( dst ) ! = tileinfo . end ( ) )
{
tilesDist [ dst ] = tilesDist [ src ] + 1 ;
coastTilesMap [ tilesDist [ dst ] ] . insert ( dst ) ;
tilesChecked . insert ( dst ) ;
tilesQueue . push_back ( dst ) ;
}
} ) ;
}
//generating some irregularity of coast
int coastIdMax = fmin ( sqrt ( coastTilesMap . size ( ) ) , 7.f ) ; //size of coastTilesMap shows the most distant tile from water
assert ( coastIdMax > 0 ) ;
tilesChecked . clear ( ) ;
for ( int coastId = coastIdMax ; coastId > = 1 ; - - coastId )
{
//amount of iterations shall be proportion of coast perimeter
const int coastLength = coastTilesMap [ coastId ] . size ( ) / ( coastId + 3 ) ;
for ( int coastIter = 0 ; coastIter < coastLength ; + + coastIter )
{
int3 tile = * RandomGeneratorUtil : : nextItem ( coastTilesMap [ coastId ] , gen - > rand ) ;
if ( tilesChecked . find ( tile ) ! = tilesChecked . end ( ) )
continue ;
if ( gen - > isUsed ( tile ) | | gen - > isFree ( tile ) ) //prevent placing water nearby town
continue ;
tilesQueue . push_back ( tile ) ;
tilesChecked . insert ( tile ) ;
}
}
//if tile is marked as water - connect it with "big" water
while ( ! tilesQueue . empty ( ) )
{
int3 src = tilesQueue . front ( ) ;
tilesQueue . pop_front ( ) ;
if ( waterTiles . find ( src ) ! = waterTiles . end ( ) )
continue ;
waterTiles . insert ( src ) ;
gen - > foreach_neighbour ( src , [ & src , & tilesDist , & tilesChecked , & tilesQueue ] ( const int3 & dst )
{
if ( tilesChecked . find ( dst ) ! = tilesChecked . end ( ) )
return ;
if ( tilesDist [ dst ] > 0 & & tilesDist [ src ] - tilesDist [ dst ] = = 1 )
{
tilesQueue . push_back ( dst ) ;
tilesChecked . insert ( dst ) ;
}
} ) ;
}
//start filtering of narrow places and coast atrifacts
std : : vector < int3 > waterAdd ;
for ( int coastId = 1 ; coastId < = coastIdMax ; + + coastId )
{
for ( auto & tile : coastTilesMap [ coastId ] )
{
//collect neighbout water tiles
auto collectionLambda = [ & waterTiles , & coastTilesMap ] ( const int3 & t , std : : set < int3 > & outCollection )
{
if ( waterTiles . find ( t ) ! = waterTiles . end ( ) )
{
coastTilesMap [ 0 ] . insert ( t ) ;
outCollection . insert ( t ) ;
}
} ;
std : : set < int3 > waterCoastDirect , waterCoastDiag ;
gen - > foreachDirectNeighbour ( tile , std : : bind ( collectionLambda , std : : placeholders : : _1 , std : : ref ( waterCoastDirect ) ) ) ;
gen - > foreachDiagonalNeighbour ( tile , std : : bind ( collectionLambda , std : : placeholders : : _1 , std : : ref ( waterCoastDiag ) ) ) ;
int waterCoastDirectNum = waterCoastDirect . size ( ) ;
int waterCoastDiagNum = waterCoastDiag . size ( ) ;
//remove tiles which are mostly covered by water
if ( waterCoastDirectNum > = 3 )
{
waterAdd . push_back ( tile ) ;
continue ;
}
if ( waterCoastDiagNum = = 4 & & waterCoastDirectNum = = 2 )
{
waterAdd . push_back ( tile ) ;
continue ;
}
if ( waterCoastDirectNum = = 2 & & waterCoastDiagNum > = 2 )
{
int3 diagSum , dirSum ;
for ( auto & i : waterCoastDiag )
diagSum + = i - tile ;
for ( auto & i : waterCoastDirect )
dirSum + = i - tile ;
if ( diagSum = = int3 ( ) | | dirSum = = int3 ( ) )
{
waterAdd . push_back ( tile ) ;
continue ;
}
if ( waterCoastDiagNum = = 3 & & diagSum ! = dirSum )
{
waterAdd . push_back ( tile ) ;
continue ;
}
}
}
}
for ( auto & i : waterAdd )
waterTiles . insert ( i ) ;
//filtering tiny "lakes"
for ( auto & tile : coastTilesMap [ 0 ] ) //now it's only coast-water tiles
{
if ( waterTiles . find ( tile ) = = waterTiles . end ( ) ) //for ground tiles
continue ;
std : : vector < int3 > groundCoast ;
gen - > foreachDirectNeighbour ( tile , [ this , & waterTiles , & groundCoast ] ( const int3 & t )
{
if ( waterTiles . find ( t ) = = waterTiles . end ( ) & & tileinfo . find ( t ) ! = tileinfo . end ( ) ) //for ground tiles of same zone
{
groundCoast . push_back ( t ) ;
}
} ) ;
if ( groundCoast . size ( ) > = 3 )
{
waterTiles . erase ( tile ) ;
}
else
{
if ( groundCoast . size ( ) = = 2 )
{
if ( groundCoast [ 0 ] + groundCoast [ 1 ] = = int3 ( ) )
{
waterTiles . erase ( tile ) ;
}
}
else
{
if ( ! groundCoast . empty ( ) )
{
coastTiles . insert ( tile ) ;
}
}
}
}
//do not set water on tiles belong to other zones
vstd : : erase_if ( coastTiles , [ & waterTiles ] ( const int3 & tile )
{
return waterTiles . find ( tile ) = = waterTiles . end ( ) ;
} ) ;
//transforming waterTiles to actual water
for ( auto & tile : waterTiles )
{
gen - > getZoneWater ( ) . second - > addTile ( tile ) ;
gen - > setZoneID ( tile , gen - > getZoneWater ( ) . first ) ;
gen - > setOccupied ( tile , ETileType : : POSSIBLE ) ;
tileinfo . erase ( tile ) ;
possibleTiles . erase ( tile ) ;
}
}
void CRmgTemplateZone : : waterInitFreeTiles ( )
{
std : : set < int3 > tilesAll ( tileinfo . begin ( ) , tileinfo . end ( ) ) ; //water tiles
std : : list < int3 > tilesQueue ; //tiles need to be processed
std : : set < int3 > tilesChecked ;
//lambda for increasing distance of negihbour tiles
auto lakeSearch = [ this , & tilesAll , & tilesQueue ] ( const int3 & dst )
{
if ( tilesAll . find ( dst ) = = tilesAll . end ( ) )
{
if ( lakes . back ( ) . tiles . find ( dst ) = = lakes . back ( ) . tiles . end ( ) )
{
//we reach land! let's store this information
assert ( gen - > getZoneID ( dst ) ! = gen - > getZoneWater ( ) . first ) ;
lakes . back ( ) . connectedZones . insert ( gen - > getZoneID ( dst ) ) ;
lakes . back ( ) . coast . insert ( dst ) ;
lakes . back ( ) . distance [ dst ] = 0 ;
return ;
}
}
else
{
if ( lakes . back ( ) . tiles . insert ( dst ) . second )
{
tilesQueue . push_back ( dst ) ;
}
}
} ;
while ( ! tilesAll . empty ( ) )
{
//add some random tile as initial
tilesQueue . push_back ( * tilesAll . begin ( ) ) ;
setPos ( tilesQueue . front ( ) ) ;
addFreePath ( tilesQueue . front ( ) ) ;
lakes . emplace_back ( ) ;
lakes . back ( ) . tiles . insert ( tilesQueue . front ( ) ) ;
//find lake
while ( ! tilesQueue . empty ( ) )
{
int3 tile = tilesQueue . front ( ) ;
tilesQueue . pop_front ( ) ;
gen - > foreachDirectNeighbour ( tile , lakeSearch ) ;
}
//fill distance map
tilesQueue . assign ( lakes . back ( ) . coast . begin ( ) , lakes . back ( ) . coast . end ( ) ) ;
while ( ! tilesQueue . empty ( ) )
{
int3 src = tilesQueue . front ( ) ;
tilesQueue . pop_front ( ) ;
gen - > foreachDirectNeighbour ( src , [ this , & src , & tilesChecked , & tilesQueue ] ( const int3 & dst )
{
if ( tilesChecked . find ( dst ) ! = tilesChecked . end ( ) )
return ;
if ( lakes . back ( ) . tiles . find ( dst ) ! = lakes . back ( ) . tiles . end ( ) )
{
lakes . back ( ) . distance [ dst ] = lakes . back ( ) . distance [ src ] + 1 ;
tilesChecked . insert ( dst ) ;
tilesQueue . push_back ( dst ) ;
}
} ) ;
}
//cleanup
int lakeIdx = lakes . size ( ) ;
for ( auto & t : lakes . back ( ) . tiles )
{
assert ( lakeMap . find ( t ) = = lakeMap . end ( ) ) ;
lakeMap [ t ] = lakeIdx ;
tilesAll . erase ( t ) ;
}
}
# ifdef _BETA
{
std : : ofstream out1 ( " lakes_id.txt " ) ;
std : : ofstream out2 ( " lakes_map.txt " ) ;
std : : ofstream out3 ( " lakes_dist.txt " ) ;
int levels = gen - > map - > twoLevel ? 2 : 1 ;
int width = gen - > map - > width ;
int height = gen - > map - > height ;
for ( int k = 0 ; k < levels ; k + + )
{
for ( int j = 0 ; j < height ; j + + )
{
for ( int i = 0 ; i < width ; i + + )
{
int3 tile { i , j , k } ;
if ( lakeMap [ tile ] > 9 )
out1 < < ' # ' ;
else
out1 < < lakeMap [ tile ] ;
bool found = false ;
for ( auto & lake : lakes )
{
if ( lake . coast . count ( tile ) )
{
out2 < < ' @ ' ;
out3 < < lake . distance [ tile ] ;
found = true ;
}
else if ( lake . tiles . count ( tile ) )
{
out2 < < ' ~ ' ;
out3 < < lake . distance [ tile ] ;
found = true ;
}
}
if ( ! found )
{
out2 < < ' ' ;
out3 < < ' ' ;
}
}
out1 < < std : : endl ;
out2 < < std : : endl ;
out3 < < std : : endl ;
}
out1 < < std : : endl ;
out2 < < std : : endl ;
out3 < < std : : endl ;
}
out1 < < std : : endl ;
out2 < < std : : endl ;
out3 < < std : : endl ;
}
# endif
}
bool CRmgTemplateZone : : waterKeepConnection ( TRmgTemplateZoneId zoneA , TRmgTemplateZoneId zoneB )
{
for ( auto & lake : lakes )
{
if ( lake . connectedZones . count ( zoneA ) & & lake . connectedZones . count ( zoneB ) )
{
lake . keepConnections . insert ( zoneA ) ;
lake . keepConnections . insert ( zoneB ) ;
return true ;
}
}
return false ;
}
void CRmgTemplateZone : : waterConnection ( CRmgTemplateZone & dst )
{
if ( isUnderground ( ) | | dst . getCoastTiles ( ) . empty ( ) )
return ;
//block zones are not connected by template
for ( auto & lake : lakes )
{
if ( lake . connectedZones . count ( dst . getId ( ) ) )
{
if ( ! lake . keepConnections . count ( dst . getId ( ) ) )
{
for ( auto & ct : lake . coast )
{
if ( gen - > getZoneID ( ct ) = = dst . getId ( ) & & gen - > isPossible ( ct ) )
gen - > setOccupied ( ct , ETileType : : BLOCKED ) ;
}
continue ;
}
int3 coastTile ( - 1 , - 1 , - 1 ) ;
int zoneTowns = dst . playerTowns . getTownCount ( ) + dst . playerTowns . getCastleCount ( ) +
dst . neutralTowns . getTownCount ( ) + dst . neutralTowns . getCastleCount ( ) ;
if ( dst . getType ( ) = = ETemplateZoneType : : PLAYER_START | | dst . getType ( ) = = ETemplateZoneType : : CPU_START | | zoneTowns )
{
2022-06-05 08:02:58 +02:00
coastTile = dst . createShipyard ( lake . tiles , gen - > getConfig ( ) . shipyardGuard ) ;
2022-05-31 11:25:39 +02:00
if ( ! coastTile . valid ( ) )
{
coastTile = makeBoat ( dst . getId ( ) , lake . tiles ) ;
}
}
else
{
coastTile = makeBoat ( dst . getId ( ) , lake . tiles ) ;
}
if ( coastTile . valid ( ) )
{
if ( connectPath ( coastTile , true ) )
{
addFreePath ( coastTile ) ;
}
else
logGlobal - > error ( " Cannot build water route for zone %d " , dst . getId ( ) ) ;
}
else
logGlobal - > error ( " No entry from water to zone %d " , dst . getId ( ) ) ;
}
}
}
const std : : set < int3 > & CRmgTemplateZone : : getCoastTiles ( ) const
{
return coastTiles ;
}
bool CRmgTemplateZone : : isWaterConnected ( TRmgTemplateZoneId zone , const int3 & tile ) const
{
int lakeId = gen - > getZoneWater ( ) . second - > lakeMap . at ( tile ) ;
if ( lakeId = = 0 )
return false ;
return gen - > getZoneWater ( ) . second - > lakes . at ( lakeId - 1 ) . connectedZones . count ( zone ) & &
gen - > getZoneWater ( ) . second - > lakes . at ( lakeId - 1 ) . keepConnections . count ( zone ) ;
}
2017-11-03 22:03:51 +02:00
void CRmgTemplateZone : : fractalize ( )
2014-06-12 22:10:43 +03:00
{
2014-09-23 22:12:10 +03:00
for ( auto tile : tileinfo )
{
if ( gen - > isFree ( tile ) )
freePaths . insert ( tile ) ;
}
2014-07-08 12:28:55 +03:00
std : : vector < int3 > clearedTiles ( freePaths . begin ( ) , freePaths . end ( ) ) ;
2014-06-12 22:10:43 +03:00
std : : set < int3 > possibleTiles ;
std : : set < int3 > tilesToIgnore ; //will be erased in this iteration
2014-07-08 12:28:55 +03:00
//the more treasure density, the greater distance between paths. Scaling is experimental.
2014-12-20 15:01:48 +02:00
int totalDensity = 0 ;
2022-05-31 11:25:39 +02:00
for ( auto ti : treasureInfo )
2015-03-30 23:55:37 +02:00
totalDensity + = ti . density ;
2015-01-16 10:40:11 +02:00
const float minDistance = 10 * 10 ; //squared
2014-12-20 15:01:48 +02:00
2022-05-31 11:25:39 +02:00
for ( auto tile : tileinfo )
2014-06-12 22:10:43 +03:00
{
2022-05-31 11:25:39 +02:00
if ( gen - > isPossible ( tile ) )
2014-06-12 22:10:43 +03:00
possibleTiles . insert ( tile ) ;
}
2014-07-15 20:52:58 +03:00
assert ( clearedTiles . size ( ) ) ; //this should come from zone connections
2014-06-12 22:10:43 +03:00
2015-04-11 13:46:17 +02:00
std : : vector < int3 > nodes ; //connect them with a grid
2022-05-31 11:25:39 +02:00
if ( type ! = ETemplateZoneType : : JUNCTION )
2014-06-12 22:10:43 +03:00
{
2015-01-16 18:39:16 +02:00
//junction is not fractalized, has only one straight path
//everything else remains blocked
2022-05-31 11:25:39 +02:00
while ( ! possibleTiles . empty ( ) )
2014-06-12 22:10:43 +03:00
{
2015-01-16 18:39:16 +02:00
//link tiles in random order
std : : vector < int3 > tilesToMakePath ( possibleTiles . begin ( ) , possibleTiles . end ( ) ) ;
RandomGeneratorUtil : : randomShuffle ( tilesToMakePath , gen - > rand ) ;
2014-06-12 22:10:43 +03:00
2015-04-11 13:46:17 +02:00
int3 nodeFound ( - 1 , - 1 , - 1 ) ;
2022-05-31 11:25:39 +02:00
for ( auto tileToMakePath : tilesToMakePath )
2014-06-12 22:10:43 +03:00
{
2015-01-16 18:39:16 +02:00
//find closest free tile
float currentDistance = 1e10 ;
int3 closestTile ( - 1 , - 1 , - 1 ) ;
2022-05-31 11:25:39 +02:00
for ( auto clearTile : clearedTiles )
2014-06-12 22:10:43 +03:00
{
2020-10-01 10:38:06 +02:00
float distance = static_cast < float > ( tileToMakePath . dist2dSQ ( clearTile ) ) ;
2015-01-16 18:39:16 +02:00
2022-05-31 11:25:39 +02:00
if ( distance < currentDistance )
2015-01-16 18:39:16 +02:00
{
currentDistance = distance ;
closestTile = clearTile ;
}
2022-05-31 11:25:39 +02:00
if ( currentDistance < = minDistance )
2015-01-16 18:39:16 +02:00
{
//this tile is close enough. Forget about it and check next one
tilesToIgnore . insert ( tileToMakePath ) ;
break ;
}
2014-06-12 22:10:43 +03:00
}
2015-01-16 18:39:16 +02:00
//if tiles is not close enough, make path to it
if ( currentDistance > minDistance )
2014-06-12 22:10:43 +03:00
{
2015-04-11 13:46:17 +02:00
nodeFound = tileToMakePath ;
nodes . push_back ( nodeFound ) ;
clearedTiles . push_back ( nodeFound ) ; //from now on nearby tiles will be considered handled
2015-01-16 18:39:16 +02:00
break ; //next iteration - use already cleared tiles
2014-06-12 22:10:43 +03:00
}
}
2015-01-16 18:39:16 +02:00
2022-05-31 11:25:39 +02:00
for ( auto tileToClear : tilesToIgnore )
2015-01-16 18:39:16 +02:00
{
//these tiles are already connected, ignore them
vstd : : erase_if_present ( possibleTiles , tileToClear ) ;
}
2022-05-31 11:25:39 +02:00
if ( ! nodeFound . valid ( ) ) //nothing else can be done (?)
2015-01-16 18:39:16 +02:00
break ;
tilesToIgnore . clear ( ) ;
2014-06-12 22:10:43 +03:00
}
}
2014-07-08 12:28:55 +03:00
2016-12-21 23:18:35 +02:00
//cut straight paths towards the center. A* is too slow for that.
2015-04-11 13:46:17 +02:00
for ( auto node : nodes )
2014-07-08 12:28:55 +03:00
{
2022-05-31 11:25:39 +02:00
auto subnodes = nodes ;
boost : : sort ( subnodes , [ & node ] ( const int3 & ourNode , const int3 & otherNode ) - > bool
2015-04-11 13:46:17 +02:00
{
return node . dist2dSQ ( ourNode ) < node . dist2dSQ ( otherNode ) ;
2022-05-31 11:25:39 +02:00
} ) ;
2015-04-11 13:46:17 +02:00
std : : vector < int3 > nearbyNodes ;
2022-05-31 11:25:39 +02:00
if ( subnodes . size ( ) > = 2 )
2015-04-11 13:46:17 +02:00
{
2022-05-31 11:25:39 +02:00
nearbyNodes . push_back ( subnodes [ 1 ] ) ; //node[0] is our node we want to connect
2015-04-11 13:46:17 +02:00
}
2022-05-31 11:25:39 +02:00
if ( subnodes . size ( ) > = 3 )
2015-04-11 13:46:17 +02:00
{
2022-05-31 11:25:39 +02:00
nearbyNodes . push_back ( subnodes [ 2 ] ) ;
2015-04-11 13:46:17 +02:00
}
//connect with all the paths
2017-11-03 22:03:51 +02:00
crunchPath ( node , findClosestTile ( freePaths , node ) , true , & freePaths ) ;
2015-04-11 13:46:17 +02:00
//connect with nearby nodes
for ( auto nearbyNode : nearbyNodes )
{
2022-05-31 11:25:39 +02:00
crunchPath ( node , nearbyNode , true , & freePaths ) ; //do not allow to make another path network
2015-04-11 13:46:17 +02:00
}
2014-07-08 12:28:55 +03:00
}
2015-04-11 13:46:17 +02:00
for ( auto node : nodes )
gen - > setOccupied ( node , ETileType : : FREE ) ; //make sure they are clear
2014-07-08 12:28:55 +03:00
2014-12-24 16:07:20 +02:00
//now block most distant tiles away from passages
2015-01-16 10:40:11 +02:00
float blockDistance = minDistance * 0.25f ;
2014-12-24 16:07:20 +02:00
2015-01-16 10:40:11 +02:00
for ( auto tile : tileinfo )
2014-12-24 16:07:20 +02:00
{
2022-05-31 11:25:39 +02:00
if ( ! gen - > isPossible ( tile ) )
continue ;
if ( freePaths . count ( tile ) )
2015-01-16 10:40:11 +02:00
continue ;
2014-12-24 16:07:20 +02:00
bool closeTileFound = false ;
2022-05-31 11:25:39 +02:00
for ( auto clearTile : freePaths )
2014-12-24 16:07:20 +02:00
{
2020-10-01 10:38:06 +02:00
float distance = static_cast < float > ( tile . dist2dSQ ( clearTile ) ) ;
2014-12-24 16:07:20 +02:00
2022-05-31 11:25:39 +02:00
if ( distance < blockDistance )
2014-12-24 16:07:20 +02:00
{
closeTileFound = true ;
break ;
}
}
if ( ! closeTileFound ) //this tile is far enough from passages
gen - > setOccupied ( tile , ETileType : : BLOCKED ) ;
}
2014-07-08 12:28:55 +03:00
2015-01-16 10:40:11 +02:00
# define PRINT_FRACTALIZED_MAP false
if ( PRINT_FRACTALIZED_MAP ) //enable to debug
2014-07-08 12:28:55 +03:00
{
2022-05-28 15:03:50 +02:00
std : : ofstream out ( boost : : to_string ( boost : : format ( " zone_%d.txt " ) % id ) ) ;
2014-07-08 12:28:55 +03:00
int levels = gen - > map - > twoLevel ? 2 : 1 ;
int width = gen - > map - > width ;
int height = gen - > map - > height ;
for ( int k = 0 ; k < levels ; k + + )
{
for ( int j = 0 ; j < height ; j + + )
{
for ( int i = 0 ; i < width ; i + + )
{
2015-01-16 10:40:11 +02:00
char t = ' ? ' ;
switch ( gen - > getTile ( int3 ( i , j , k ) ) . getTileType ( ) )
{
case ETileType : : FREE :
t = ' ' ; break ;
case ETileType : : BLOCKED :
t = ' # ' ; break ;
case ETileType : : POSSIBLE :
t = ' - ' ; break ;
case ETileType : : USED :
t = ' O ' ; break ;
}
out < < t ;
2014-07-08 12:28:55 +03:00
}
out < < std : : endl ;
}
out < < std : : endl ;
}
2015-01-16 10:40:11 +02:00
out < < std : : endl ;
2014-07-08 12:28:55 +03:00
}
2014-06-12 22:10:43 +03:00
}
2017-11-03 22:03:51 +02:00
void CRmgTemplateZone : : connectLater ( )
2015-06-04 09:02:56 +02:00
{
2022-05-31 11:25:39 +02:00
for ( const int3 & node : tilesToConnectLater )
2015-06-04 09:02:56 +02:00
{
2017-11-03 22:03:51 +02:00
if ( ! connectWithCenter ( node , true ) )
2017-08-11 19:03:05 +02:00
logGlobal - > error ( " Failed to connect node %s with center of the zone " , node . toString ( ) ) ;
2015-06-04 09:02:56 +02:00
}
}
2017-11-03 22:03:51 +02:00
bool CRmgTemplateZone : : crunchPath ( const int3 & src , const int3 & dst , bool onlyStraight , std : : set < int3 > * clearedTiles )
2014-05-31 11:56:14 +03:00
{
/*
make shortest path with free tiles , reachning dst or closest already free tile . Avoid blocks .
do not leave zone border
*/
bool result = false ;
bool end = false ;
int3 currentPos = src ;
2020-10-01 10:38:06 +02:00
float distance = static_cast < float > ( currentPos . dist2dSQ ( dst ) ) ;
2014-05-31 11:56:14 +03:00
while ( ! end )
{
if ( currentPos = = dst )
2014-06-13 13:04:17 +03:00
{
result = true ;
2014-05-31 11:56:14 +03:00
break ;
2014-06-13 13:04:17 +03:00
}
2014-05-31 11:56:14 +03:00
auto lastDistance = distance ;
2016-02-15 12:34:37 +02:00
2017-11-03 22:03:51 +02:00
auto processNeighbours = [ this , & currentPos , dst , & distance , & result , & end , clearedTiles ] ( int3 & pos )
2014-05-31 11:56:14 +03:00
{
if ( ! result ) //not sure if lambda is worth it...
{
if ( pos = = dst )
{
result = true ;
end = true ;
}
if ( pos . dist2dSQ ( dst ) < distance )
{
if ( ! gen - > isBlocked ( pos ) )
{
2016-08-09 10:12:13 +02:00
if ( gen - > getZoneID ( pos ) = = id )
2014-05-31 11:56:14 +03:00
{
if ( gen - > isPossible ( pos ) )
{
gen - > setOccupied ( pos , ETileType : : FREE ) ;
2014-06-12 22:10:43 +03:00
if ( clearedTiles )
clearedTiles - > insert ( pos ) ;
2014-05-31 11:56:14 +03:00
currentPos = pos ;
2020-10-01 10:38:06 +02:00
distance = static_cast < float > ( currentPos . dist2dSQ ( dst ) ) ;
2014-05-31 11:56:14 +03:00
}
else if ( gen - > isFree ( pos ) )
{
end = true ;
result = true ;
}
}
}
}
}
2015-01-03 08:20:52 +02:00
} ;
2016-02-15 12:34:37 +02:00
2015-06-02 16:40:32 +02:00
if ( onlyStraight )
gen - > foreachDirectNeighbour ( currentPos , processNeighbours ) ;
else
gen - > foreach_neighbour ( currentPos , processNeighbours ) ;
2016-02-15 12:34:37 +02:00
2014-09-23 19:32:32 +03:00
int3 anotherPos ( - 1 , - 1 , - 1 ) ;
2015-01-03 08:20:52 +02:00
if ( ! ( result | | distance < lastDistance ) ) //we do not advance, use more advanced pathfinding algorithm?
2014-09-23 19:32:32 +03:00
{
//try any nearby tiles, even if its not closer than current
float lastDistance = 2 * distance ; //start with significantly larger value
2016-02-15 12:34:37 +02:00
2019-01-19 12:52:02 +02:00
auto processNeighbours2 = [ this , & currentPos , dst , & lastDistance , & anotherPos , clearedTiles ] ( int3 & pos )
2014-09-23 19:32:32 +03:00
{
if ( currentPos . dist2dSQ ( dst ) < lastDistance ) //try closest tiles from all surrounding unused tiles
{
2016-08-09 10:12:13 +02:00
if ( gen - > getZoneID ( pos ) = = id )
2014-09-23 19:32:32 +03:00
{
if ( gen - > isPossible ( pos ) )
{
if ( clearedTiles )
clearedTiles - > insert ( pos ) ;
anotherPos = pos ;
2020-10-01 10:38:06 +02:00
lastDistance = static_cast < float > ( currentPos . dist2dSQ ( dst ) ) ;
2014-09-23 19:32:32 +03:00
}
}
}
2016-02-15 12:34:37 +02:00
} ;
2015-06-02 16:40:32 +02:00
if ( onlyStraight )
gen - > foreachDirectNeighbour ( currentPos , processNeighbours2 ) ;
else
gen - > foreach_neighbour ( currentPos , processNeighbours2 ) ;
2016-02-15 12:34:37 +02:00
2014-09-23 19:32:32 +03:00
if ( anotherPos . valid ( ) )
{
if ( clearedTiles )
clearedTiles - > insert ( anotherPos ) ;
gen - > setOccupied ( anotherPos , ETileType : : FREE ) ;
currentPos = anotherPos ;
}
}
if ( ! ( result | | distance < lastDistance | | anotherPos . valid ( ) ) )
2014-05-31 11:56:14 +03:00
{
2015-03-28 23:17:45 +02:00
//FIXME: seemingly this condition is messed up, tells nothing
2017-08-10 20:59:55 +02:00
//logGlobal->warn("No tile closer than %s found on path from %s to %s", currentPos, src , dst);
2014-05-31 11:56:14 +03:00
break ;
}
2014-05-30 22:23:41 +03:00
}
2014-05-31 11:56:14 +03:00
return result ;
2014-05-24 15:06:08 +03:00
}
2020-10-01 10:38:06 +02:00
boost : : heap : : priority_queue < CRmgTemplateZone : : TDistance , boost : : heap : : compare < CRmgTemplateZone : : NodeComparer > > CRmgTemplateZone : : createPriorityQueue ( )
2016-12-15 13:58:16 +02:00
{
return boost : : heap : : priority_queue < TDistance , boost : : heap : : compare < NodeComparer > > ( ) ;
}
2014-05-24 15:06:08 +03:00
2017-11-03 22:03:51 +02:00
bool CRmgTemplateZone : : createRoad ( const int3 & src , const int3 & dst )
2015-01-03 08:20:52 +02:00
{
2015-05-25 16:37:57 +02:00
//A* algorithm taken from Wiki http://en.wikipedia.org/wiki/A*_search_algorithm
std : : set < int3 > closed ; // The set of nodes already evaluated.
2020-10-01 10:38:06 +02:00
auto pq = createPriorityQueue ( ) ; // The set of tentative nodes to be evaluated, initially containing the start node
2015-05-25 16:37:57 +02:00
std : : map < int3 , int3 > cameFrom ; // The map of navigated nodes.
2015-05-25 19:25:48 +02:00
std : : map < int3 , float > distances ;
2016-11-13 12:38:42 +02:00
2022-06-20 16:39:50 +02:00
gen - > setRoad ( src , ROAD_NAMES [ 0 ] ) ; //just in case zone guard already has road under it. Road under nodes will be added at very end
2015-05-25 16:37:57 +02:00
cameFrom [ src ] = int3 ( - 1 , - 1 , - 1 ) ; //first node points to finish condition
2016-12-15 13:36:47 +02:00
pq . push ( std : : make_pair ( src , 0.f ) ) ;
distances [ src ] = 0.f ;
2015-05-25 16:37:57 +02:00
// Cost from start along best known path.
2016-12-15 13:36:47 +02:00
while ( ! pq . empty ( ) )
2015-01-03 08:20:52 +02:00
{
2016-12-15 13:36:47 +02:00
auto node = pq . top ( ) ;
pq . pop ( ) ; //remove top element
int3 currentNode = node . first ;
2015-05-25 19:11:44 +02:00
closed . insert ( currentNode ) ;
2016-12-21 11:10:37 +02:00
auto currentTile = & gen - > map - > getTile ( currentNode ) ;
2015-05-25 16:37:57 +02:00
if ( currentNode = = dst | | gen - > isRoad ( currentNode ) )
{
// The goal node was reached. Trace the path using
// the saved parent information and return path
int3 backTracking = currentNode ;
while ( cameFrom [ backTracking ] . valid ( ) )
{
// add node to path
2022-06-20 16:39:50 +02:00
roads . insert ( backTracking ) ;
gen - > setRoad ( backTracking , gen - > getConfig ( ) . defaultRoadType ) ;
2017-08-10 18:39:27 +02:00
//logGlobal->trace("Setting road at tile %s", backTracking);
2015-05-25 16:37:57 +02:00
// do the same for the predecessor
backTracking = cameFrom [ backTracking ] ;
}
return true ;
}
else
{
2015-05-25 18:06:17 +02:00
bool directNeighbourFound = false ;
2015-05-25 19:25:48 +02:00
float movementCost = 1 ;
2015-05-25 18:06:17 +02:00
2017-11-03 22:03:51 +02:00
auto foo = [ this , & pq , & distances , & closed , & cameFrom , & currentNode , & currentTile , & node , & dst , & directNeighbourFound , & movementCost ] ( int3 & pos ) - > void
2015-05-25 16:37:57 +02:00
{
2016-12-15 13:36:47 +02:00
if ( vstd : : contains ( closed , pos ) ) //we already visited that node
return ;
float distance = node . second + movementCost ;
float bestDistanceSoFar = std : : numeric_limits < float > : : max ( ) ;
2015-05-25 16:37:57 +02:00
auto it = distances . find ( pos ) ;
if ( it ! = distances . end ( ) )
bestDistanceSoFar = it - > second ;
2016-12-15 13:36:47 +02:00
if ( distance < bestDistanceSoFar )
2015-05-25 16:37:57 +02:00
{
2016-12-21 11:10:37 +02:00
auto tile = & gen - > map - > getTile ( pos ) ;
bool canMoveBetween = gen - > map - > canMoveBetween ( currentNode , pos ) ;
2018-04-19 15:06:26 +02:00
if ( ( gen - > isFree ( pos ) & & gen - > isFree ( currentNode ) ) //empty path
2016-12-21 11:10:37 +02:00
| | ( ( tile - > visitable | | currentTile - > visitable ) & & canMoveBetween ) //moving from or to visitable object
| | pos = = dst ) //we already compledted the path
2015-05-25 16:37:57 +02:00
{
2016-08-09 10:12:13 +02:00
if ( gen - > getZoneID ( pos ) = = id | | pos = = dst ) //otherwise guard position may appear already connected to other zone.
2015-05-25 19:11:44 +02:00
{
2015-05-25 16:37:57 +02:00
cameFrom [ pos ] = currentNode ;
distances [ pos ] = distance ;
2016-12-15 13:36:47 +02:00
pq . push ( std : : make_pair ( pos , distance ) ) ;
2015-05-25 18:06:17 +02:00
directNeighbourFound = true ;
2015-05-25 19:11:44 +02:00
}
2015-05-25 16:37:57 +02:00
}
}
2015-05-25 18:06:17 +02:00
} ;
2015-05-25 19:11:44 +02:00
gen - > foreachDirectNeighbour ( currentNode , foo ) ; // roads cannot be rendered correctly for diagonal directions
2015-05-25 18:06:17 +02:00
if ( ! directNeighbourFound )
2015-05-25 19:25:48 +02:00
{
movementCost = 2.1f ; //moving diagonally is penalized over moving two tiles straight
2022-05-28 15:03:50 +02:00
gen - > foreachDiagonalNeighbour ( currentNode , foo ) ;
2015-05-25 19:25:48 +02:00
}
2015-05-25 16:37:57 +02:00
}
2015-01-03 08:20:52 +02:00
}
2017-08-11 19:03:05 +02:00
logGlobal - > warn ( " Failed to create road from %s to %s " , src . toString ( ) , dst . toString ( ) ) ;
2015-05-25 16:37:57 +02:00
return false ;
2015-01-03 08:20:52 +02:00
}
2017-11-03 22:03:51 +02:00
bool CRmgTemplateZone : : connectPath ( const int3 & src , bool onlyStraight )
2015-06-02 16:40:32 +02:00
///connect current tile to any other free tile within zone
{
//A* algorithm taken from Wiki http://en.wikipedia.org/wiki/A*_search_algorithm
std : : set < int3 > closed ; // The set of nodes already evaluated.
2020-10-01 10:38:06 +02:00
auto open = createPriorityQueue ( ) ; // The set of tentative nodes to be evaluated, initially containing the start node
2015-06-02 16:40:32 +02:00
std : : map < int3 , int3 > cameFrom ; // The map of navigated nodes.
std : : map < int3 , float > distances ;
2015-12-02 20:59:38 +02:00
//int3 currentNode = src;
2015-06-02 16:40:32 +02:00
cameFrom [ src ] = int3 ( - 1 , - 1 , - 1 ) ; //first node points to finish condition
2016-12-15 17:29:23 +02:00
distances [ src ] = 0.f ;
open . push ( std : : make_pair ( src , 0.f ) ) ;
2015-06-02 16:40:32 +02:00
// Cost from start along best known path.
// Estimated total cost from start to goal through y.
2016-12-15 17:29:23 +02:00
while ( ! open . empty ( ) )
2015-06-02 16:40:32 +02:00
{
2016-12-15 17:29:23 +02:00
auto node = open . top ( ) ;
open . pop ( ) ;
int3 currentNode = node . first ;
2015-06-02 16:40:32 +02:00
closed . insert ( currentNode ) ;
2015-06-04 09:02:56 +02:00
if ( gen - > isFree ( currentNode ) ) //we reached free paths, stop
2015-06-02 16:40:32 +02:00
{
// Trace the path using the saved parent information and return path
int3 backTracking = currentNode ;
while ( cameFrom [ backTracking ] . valid ( ) )
{
gen - > setOccupied ( backTracking , ETileType : : FREE ) ;
backTracking = cameFrom [ backTracking ] ;
}
return true ;
}
else
{
2017-11-03 22:03:51 +02:00
auto foo = [ this , & open , & closed , & cameFrom , & currentNode , & distances ] ( int3 & pos ) - > void
2015-06-02 16:40:32 +02:00
{
2016-12-15 17:29:23 +02:00
if ( vstd : : contains ( closed , pos ) )
return ;
//no paths through blocked or occupied tiles, stay within zone
if ( gen - > isBlocked ( pos ) | | gen - > getZoneID ( pos ) ! = id )
2016-08-09 10:12:13 +02:00
return ;
2020-10-01 10:38:06 +02:00
int distance = static_cast < int > ( distances [ currentNode ] ) + 1 ;
2016-12-15 17:29:23 +02:00
int bestDistanceSoFar = std : : numeric_limits < int > : : max ( ) ;
2015-06-02 16:40:32 +02:00
auto it = distances . find ( pos ) ;
if ( it ! = distances . end ( ) )
2020-10-01 10:38:06 +02:00
bestDistanceSoFar = static_cast < int > ( it - > second ) ;
2015-06-02 16:40:32 +02:00
2016-12-15 17:29:23 +02:00
if ( distance < bestDistanceSoFar )
2015-06-02 16:40:32 +02:00
{
2016-12-15 17:29:23 +02:00
cameFrom [ pos ] = currentNode ;
2020-10-01 10:38:06 +02:00
open . push ( std : : make_pair ( pos , ( float ) distance ) ) ;
distances [ pos ] = static_cast < float > ( distance ) ;
2015-06-02 16:40:32 +02:00
}
} ;
if ( onlyStraight )
gen - > foreachDirectNeighbour ( currentNode , foo ) ;
2016-02-15 12:34:37 +02:00
else
2015-06-02 16:40:32 +02:00
gen - > foreach_neighbour ( currentNode , foo ) ;
}
}
2015-06-02 20:29:37 +02:00
for ( auto tile : closed ) //these tiles are sealed off and can't be connected anymore
{
2022-05-31 11:25:39 +02:00
if ( gen - > isPossible ( tile ) )
gen - > setOccupied ( tile , ETileType : : BLOCKED ) ;
2015-06-02 20:29:37 +02:00
vstd : : erase_if_present ( possibleTiles , tile ) ;
}
2015-06-02 16:40:32 +02:00
return false ;
}
2022-05-31 11:25:39 +02:00
bool CRmgTemplateZone : : connectWithCenter ( const int3 & src , bool onlyStraight , bool passThroughBlocked )
2015-06-04 09:02:56 +02:00
///connect current tile to any other free tile within zone
{
//A* algorithm taken from Wiki http://en.wikipedia.org/wiki/A*_search_algorithm
std : : set < int3 > closed ; // The set of nodes already evaluated.
2020-10-01 10:38:06 +02:00
auto open = createPriorityQueue ( ) ; // The set of tentative nodes to be evaluated, initially containing the start node
2015-06-04 09:02:56 +02:00
std : : map < int3 , int3 > cameFrom ; // The map of navigated nodes.
std : : map < int3 , float > distances ;
cameFrom [ src ] = int3 ( - 1 , - 1 , - 1 ) ; //first node points to finish condition
distances [ src ] = 0 ;
2016-12-15 17:29:23 +02:00
open . push ( std : : make_pair ( src , 0.f ) ) ;
2015-06-04 09:02:56 +02:00
// Cost from start along best known path.
2016-12-15 17:29:23 +02:00
while ( ! open . empty ( ) )
2015-06-04 09:02:56 +02:00
{
2016-12-15 17:29:23 +02:00
auto node = open . top ( ) ;
open . pop ( ) ;
int3 currentNode = node . first ;
2015-06-04 09:02:56 +02:00
closed . insert ( currentNode ) ;
if ( currentNode = = pos ) //we reached center of the zone, stop
{
// Trace the path using the saved parent information and return path
int3 backTracking = currentNode ;
while ( cameFrom [ backTracking ] . valid ( ) )
{
gen - > setOccupied ( backTracking , ETileType : : FREE ) ;
backTracking = cameFrom [ backTracking ] ;
}
return true ;
}
else
{
2022-05-31 11:25:39 +02:00
auto foo = [ this , & open , & closed , & cameFrom , & currentNode , & distances , passThroughBlocked ] ( int3 & pos ) - > void
2015-06-04 09:02:56 +02:00
{
2016-12-15 17:29:23 +02:00
if ( vstd : : contains ( closed , pos ) )
return ;
if ( gen - > getZoneID ( pos ) ! = id )
return ;
2015-06-04 09:02:56 +02:00
float movementCost = 0 ;
2022-05-31 11:25:39 +02:00
2015-06-04 09:02:56 +02:00
if ( gen - > isFree ( pos ) )
movementCost = 1 ;
else if ( gen - > isPossible ( pos ) )
movementCost = 2 ;
2022-05-31 11:25:39 +02:00
else if ( passThroughBlocked & & gen - > shouldBeBlocked ( pos ) )
movementCost = 3 ;
2015-06-04 09:02:56 +02:00
else
return ;
float distance = distances [ currentNode ] + movementCost ; //we prefer to use already free paths
2016-12-15 17:29:23 +02:00
int bestDistanceSoFar = std : : numeric_limits < int > : : max ( ) ; //FIXME: boost::limits
2015-06-04 09:02:56 +02:00
auto it = distances . find ( pos ) ;
if ( it ! = distances . end ( ) )
2020-10-01 10:38:06 +02:00
bestDistanceSoFar = static_cast < int > ( it - > second ) ;
2015-06-04 09:02:56 +02:00
2016-12-15 17:29:23 +02:00
if ( distance < bestDistanceSoFar )
2015-06-04 09:02:56 +02:00
{
2016-12-15 17:29:23 +02:00
cameFrom [ pos ] = currentNode ;
open . push ( std : : make_pair ( pos , distance ) ) ;
distances [ pos ] = distance ;
2015-06-04 09:02:56 +02:00
}
} ;
if ( onlyStraight )
gen - > foreachDirectNeighbour ( currentNode , foo ) ;
else
gen - > foreach_neighbour ( currentNode , foo ) ;
}
}
return false ;
}
2015-01-03 08:20:52 +02:00
2014-06-01 17:31:15 +03:00
void CRmgTemplateZone : : addRequiredObject ( CGObjectInstance * obj , si32 strength )
2014-05-22 20:25:17 +03:00
{
2014-06-01 17:31:15 +03:00
requiredObjects . push_back ( std : : make_pair ( obj , strength ) ) ;
2014-06-01 13:02:43 +03:00
}
2014-12-20 23:13:10 +02:00
void CRmgTemplateZone : : addCloseObject ( CGObjectInstance * obj , si32 strength )
{
closeObjects . push_back ( std : : make_pair ( obj , strength ) ) ;
}
2022-05-23 12:08:36 +02:00
void CRmgTemplateZone : : addNearbyObject ( CGObjectInstance * obj , CGObjectInstance * nearbyTarget )
{
nearbyObjects . push_back ( std : : make_pair ( obj , nearbyTarget ) ) ;
}
2022-05-31 11:25:39 +02:00
void CRmgTemplateZone : : addObjectAtPosition ( CGObjectInstance * obj , const int3 & position , si32 strength )
{
//TODO: use strength
instantObjects . push_back ( std : : make_pair ( obj , position ) ) ;
}
2014-05-24 15:33:22 +03:00
2015-06-04 09:02:56 +02:00
void CRmgTemplateZone : : addToConnectLater ( const int3 & src )
{
tilesToConnectLater . insert ( src ) ;
}
2022-06-20 16:39:50 +02:00
int CRmgTemplateZone : : chooseRandomAppearance ( si32 ObjID ) const
{
auto factories = VLC - > objtypeh - > knownSubObjects ( ObjID ) ;
vstd : : erase_if ( factories , [ this , ObjID ] ( si32 f )
{
return VLC - > objtypeh - > getHandlerFor ( ObjID , f ) - > getTemplates ( terrainType ) . empty ( ) ;
} ) ;
return * RandomGeneratorUtil : : nextItem ( factories , gen - > rand ) ;
}
2017-11-03 22:03:51 +02:00
bool CRmgTemplateZone : : addMonster ( int3 & pos , si32 strength , bool clearSurroundingTiles , bool zoneGuard )
2014-06-01 15:10:44 +03:00
{
2014-06-04 11:16:08 +03:00
//precalculate actual (randomized) monster strength based on this post
//http://forum.vcmi.eu/viewtopic.php?p=12426#12426
2022-05-28 15:03:50 +02:00
int mapMonsterStrength = gen - > getMapGenOptions ( ) . getMonsterStrength ( ) ;
2014-07-15 15:38:05 +03:00
int monsterStrength = ( zoneGuard ? 0 : zoneMonsterStrength ) + mapMonsterStrength - 1 ; //array index from 0 to 4
2014-06-04 11:16:08 +03:00
static const int value1 [ ] = { 2500 , 1500 , 1000 , 500 , 0 } ;
static const int value2 [ ] = { 7500 , 7500 , 7500 , 5000 , 5000 } ;
static const float multiplier1 [ ] = { 0.5 , 0.75 , 1.0 , 1.5 , 1.5 } ;
static const float multiplier2 [ ] = { 0.5 , 0.75 , 1.0 , 1.0 , 1.5 } ;
2020-10-01 10:38:06 +02:00
int strength1 = static_cast < int > ( std : : max ( 0.f , ( strength - value1 [ monsterStrength ] ) * multiplier1 [ monsterStrength ] ) ) ;
int strength2 = static_cast < int > ( std : : max ( 0.f , ( strength - value2 [ monsterStrength ] ) * multiplier2 [ monsterStrength ] ) ) ;
2014-06-04 11:16:08 +03:00
strength = strength1 + strength2 ;
2022-06-05 08:02:58 +02:00
if ( strength < gen - > getConfig ( ) . minGuardStrength )
2014-06-04 21:59:01 +03:00
return false ; //no guard at all
2014-06-04 11:16:08 +03:00
2014-06-01 15:10:44 +03:00
CreatureID creId = CreatureID : : NONE ;
int amount = 0 ;
2014-06-07 23:27:36 +03:00
std : : vector < CreatureID > possibleCreatures ;
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
for ( auto cre : VLC - > creh - > objects )
2014-06-01 15:10:44 +03:00
{
2014-06-07 23:27:36 +03:00
if ( cre - > special )
continue ;
2017-05-27 14:57:45 +02:00
if ( ! cre - > AIValue ) //bug #2681
continue ;
2014-10-30 14:03:53 +02:00
if ( ! vstd : : contains ( monsterTypes , cre - > faction ) )
continue ;
2020-10-01 10:38:06 +02:00
if ( ( ( si32 ) ( cre - > AIValue * ( cre - > ammMin + cre - > ammMax ) / 2 ) < strength ) & & ( strength < ( si32 ) cre - > AIValue * 100 ) ) //at least one full monster. size between average size of given stack and 100
2014-06-04 11:16:08 +03:00
{
2014-06-07 23:27:36 +03:00
possibleCreatures . push_back ( cre - > idNumber ) ;
2014-06-04 11:16:08 +03:00
}
2014-06-01 15:10:44 +03:00
}
2014-06-07 23:27:36 +03:00
if ( possibleCreatures . size ( ) )
2014-06-08 09:42:29 +03:00
{
2014-06-07 23:27:36 +03:00
creId = * RandomGeneratorUtil : : nextItem ( possibleCreatures , gen - > rand ) ;
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
amount = strength / VLC - > creh - > objects [ creId ] - > AIValue ;
2014-06-08 09:42:29 +03:00
if ( amount > = 4 )
2020-10-01 10:38:06 +02:00
amount = static_cast < int > ( amount * gen - > rand . nextDouble ( 0.75 , 1.25 ) ) ;
2014-06-08 09:42:29 +03:00
}
2014-06-07 23:27:36 +03:00
else //just pick any available creature
{
creId = CreatureID ( 132 ) ; //Azure Dragon
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
amount = strength / VLC - > creh - > objects [ creId ] - > AIValue ;
2014-06-07 23:27:36 +03:00
}
2015-11-14 15:50:29 +02:00
auto guardFactory = VLC - > objtypeh - > getHandlerFor ( Obj : : MONSTER , creId ) ;
2014-06-01 15:10:44 +03:00
2015-11-14 15:50:29 +02:00
auto guard = ( CGCreature * ) guardFactory - > create ( ObjectTemplate ( ) ) ;
2015-01-10 11:23:58 +02:00
guard - > character = CGCreature : : HOSTILE ;
2014-06-01 15:10:44 +03:00
auto hlp = new CStackInstance ( creId , amount ) ;
//will be set during initialization
guard - > putStack ( SlotID ( 0 ) , hlp ) ;
2017-11-03 22:03:51 +02:00
placeObject ( guard , pos ) ;
2014-07-09 23:09:06 +03:00
if ( clearSurroundingTiles )
{
//do not spawn anything near monster
2017-11-03 22:03:51 +02:00
gen - > foreach_neighbour ( pos , [ this ] ( int3 pos )
2014-07-09 23:09:06 +03:00
{
if ( gen - > isPossible ( pos ) )
gen - > setOccupied ( pos , ETileType : : FREE ) ;
} ) ;
}
2014-06-04 21:59:01 +03:00
return true ;
2014-06-01 15:10:44 +03:00
}
2017-11-03 22:03:51 +02:00
bool CRmgTemplateZone : : createTreasurePile ( int3 & pos , float minDistance , const CTreasureInfo & treasureInfo )
2014-06-04 11:16:08 +03:00
{
2014-07-08 21:13:51 +03:00
CTreasurePileInfo info ;
2014-06-05 18:19:11 +03:00
std : : map < int3 , CGObjectInstance * > treasures ;
std : : set < int3 > boundary ;
2014-06-13 13:04:17 +03:00
int3 guardPos ( - 1 , - 1 , - 1 ) ;
2014-07-08 21:13:51 +03:00
info . nextTreasurePos = pos ;
2014-06-04 16:16:23 +03:00
2014-12-20 15:01:48 +02:00
int maxValue = treasureInfo . max ;
int minValue = treasureInfo . min ;
2014-06-07 16:51:03 +03:00
2014-12-26 20:35:45 +02:00
ui32 desiredValue = ( gen - > rand . nextInt ( minValue , maxValue ) ) ;
2014-06-04 11:16:08 +03:00
int currentValue = 0 ;
CGObjectInstance * object = nullptr ;
2020-10-01 10:38:06 +02:00
while ( currentValue < = ( int ) desiredValue - 100 ) //no objects with value below 100 are available
2014-06-04 11:16:08 +03:00
{
2014-07-08 21:13:51 +03:00
treasures [ info . nextTreasurePos ] = nullptr ;
2014-07-09 10:22:50 +03:00
2014-12-26 20:35:45 +02:00
for ( auto treasurePos : treasures )
2014-06-05 18:19:11 +03:00
{
2017-11-03 22:03:51 +02:00
gen - > foreach_neighbour ( treasurePos . first , [ & boundary ] ( int3 pos )
2014-06-05 18:19:11 +03:00
{
2014-12-26 20:35:45 +02:00
boundary . insert ( pos ) ;
} ) ;
}
for ( auto treasurePos : treasures )
{
//leaving only boundary around objects
vstd : : erase_if_present ( boundary , treasurePos . first ) ;
}
2014-07-09 10:22:50 +03:00
2014-12-26 20:35:45 +02:00
for ( auto tile : boundary )
{
//we can't extend boundary anymore
if ( ! ( gen - > isBlocked ( tile ) | | gen - > isPossible ( tile ) ) )
break ;
2014-06-05 18:19:11 +03:00
}
2017-11-03 22:03:51 +02:00
ObjectInfo oi = getRandomObject ( info , desiredValue , maxValue , currentValue ) ;
2014-07-29 16:58:54 +03:00
if ( ! oi . value ) //0 value indicates no object
2014-07-08 21:13:51 +03:00
{
vstd : : erase_if_present ( treasures , info . nextTreasurePos ) ;
2014-06-04 20:08:04 +03:00
break ;
2014-07-08 21:13:51 +03:00
}
else
{
2014-07-29 16:58:54 +03:00
object = oi . generateObject ( ) ;
2022-05-31 11:25:39 +02:00
object - > appearance = oi . templ ;
2014-07-29 16:58:54 +03:00
2014-07-15 23:33:51 +03:00
//remove from possible objects
auto oiptr = std : : find ( possibleObjects . begin ( ) , possibleObjects . end ( ) , oi ) ;
assert ( oiptr ! = possibleObjects . end ( ) ) ;
oiptr - > maxPerZone - - ;
2014-07-27 08:37:56 +03:00
if ( ! oiptr - > maxPerZone )
possibleObjects . erase ( oiptr ) ;
2014-07-15 23:33:51 +03:00
2014-07-08 21:13:51 +03:00
//update treasure pile area
2014-07-09 13:05:14 +03:00
int3 visitablePos = info . nextTreasurePos ;
2014-07-08 21:13:51 +03:00
if ( oi . templ . isVisitableFromTop ( ) )
info . visitableFromTopPositions . insert ( visitablePos ) ; //can be accessed from any direction
2014-07-09 10:22:50 +03:00
else
info . visitableFromBottomPositions . insert ( visitablePos ) ; //can be accessed only from bottom or side
2014-07-08 21:13:51 +03:00
for ( auto blockedOffset : oi . templ . getBlockedOffsets ( ) )
2014-07-09 10:22:50 +03:00
{
2014-07-09 13:05:14 +03:00
int3 blockPos = info . nextTreasurePos + blockedOffset + oi . templ . getVisitableOffset ( ) ; //object will be moved to align vistable pos to treasure pos
info . occupiedPositions . insert ( blockPos ) ;
info . blockedPositions . insert ( blockPos ) ;
2014-07-09 10:22:50 +03:00
}
2016-01-27 22:08:08 +02:00
info . occupiedPositions . insert ( visitablePos + oi . templ . getVisitableOffset ( ) ) ;
2014-06-04 20:08:04 +03:00
2014-07-29 16:58:54 +03:00
currentValue + = oi . value ;
2016-02-15 12:34:37 +02:00
2014-07-29 16:58:54 +03:00
treasures [ info . nextTreasurePos ] = object ;
2014-06-05 18:19:11 +03:00
2014-07-29 16:58:54 +03:00
//now find place for next object
int3 placeFound ( - 1 , - 1 , - 1 ) ;
2014-06-07 15:02:57 +03:00
2014-07-29 20:19:15 +03:00
//randomize next position from among possible ones
std : : vector < int3 > boundaryCopy ( boundary . begin ( ) , boundary . end ( ) ) ;
2014-11-22 15:17:53 +02:00
//RandomGeneratorUtil::randomShuffle(boundaryCopy, gen->rand);
auto chooseTopTile = [ ] ( const int3 & lhs , const int3 & rhs ) - > bool
{
return lhs . y < rhs . y ;
} ;
boost : : sort ( boundaryCopy , chooseTopTile ) ; //start from top tiles to allow objects accessible from bottom
2014-07-29 20:19:15 +03:00
for ( auto tile : boundaryCopy )
2014-06-05 18:19:11 +03:00
{
2022-05-31 11:25:39 +02:00
if ( gen - > isPossible ( tile ) & & gen - > getZoneID ( tile ) = = getId ( ) ) //we can place new treasure only on possible tile
2014-06-05 18:19:11 +03:00
{
2014-07-29 16:58:54 +03:00
bool here = true ;
2017-11-03 22:03:51 +02:00
gen - > foreach_neighbour ( tile , [ this , & here , minDistance ] ( int3 pos )
2014-07-29 16:58:54 +03:00
{
2022-05-31 11:25:39 +02:00
if ( ! ( gen - > isBlocked ( pos ) | | gen - > isPossible ( pos ) ) | | gen - > getZoneID ( pos ) ! = getId ( ) | | gen - > getNearestObjectDistance ( pos ) < minDistance )
2014-07-29 16:58:54 +03:00
here = false ;
} ) ;
if ( here )
{
placeFound = tile ;
break ;
}
2014-06-05 18:19:11 +03:00
}
}
2014-07-29 16:58:54 +03:00
if ( placeFound . valid ( ) )
info . nextTreasurePos = placeFound ;
2015-01-15 12:21:29 +02:00
else
break ; //no more place to add any objects
}
2014-06-04 20:08:04 +03:00
}
2014-07-08 21:13:51 +03:00
2014-11-22 14:01:28 +02:00
if ( treasures . size ( ) )
2014-06-04 20:08:04 +03:00
{
2015-06-02 20:29:37 +02:00
//find object closest to free path, then connect it to the middle of the zone
2014-07-09 12:38:16 +03:00
2014-06-13 09:00:26 +03:00
int3 closestTile = int3 ( - 1 , - 1 , - 1 ) ;
2018-10-29 17:56:14 +02:00
float minTreasureDistance = 1e10 ;
2016-01-13 21:03:25 +02:00
2014-07-09 10:22:50 +03:00
for ( auto visitablePos : info . visitableFromBottomPositions ) //objects that are not visitable from top must be accessible from bottom or side
2014-06-13 09:00:26 +03:00
{
2015-06-02 20:29:37 +02:00
int3 closestFreeTile = findClosestTile ( freePaths , visitablePos ) ;
2018-10-29 17:56:14 +02:00
if ( closestFreeTile . dist2d ( visitablePos ) < minTreasureDistance )
2014-06-13 09:00:26 +03:00
{
2016-01-13 21:03:25 +02:00
closestTile = visitablePos + int3 ( 0 , 1 , 0 ) ; //start below object (y+1), possibly even outside the map, to not make path up through it
2020-10-01 10:38:06 +02:00
minTreasureDistance = static_cast < float > ( closestFreeTile . dist2d ( visitablePos ) ) ;
2014-07-09 10:22:50 +03:00
}
}
2016-01-13 21:03:25 +02:00
for ( auto visitablePos : info . visitableFromTopPositions ) //all objects are accessible from any direction
2014-07-09 10:22:50 +03:00
{
2016-01-13 21:03:25 +02:00
int3 closestFreeTile = findClosestTile ( freePaths , visitablePos ) ;
2018-10-29 17:56:14 +02:00
if ( closestFreeTile . dist2d ( visitablePos ) < minTreasureDistance )
2014-07-09 10:22:50 +03:00
{
2016-01-13 21:03:25 +02:00
closestTile = visitablePos ;
2020-10-01 10:38:06 +02:00
minTreasureDistance = static_cast < float > ( closestFreeTile . dist2d ( visitablePos ) ) ;
2014-06-13 09:00:26 +03:00
}
}
assert ( closestTile . valid ( ) ) ;
2014-07-09 10:22:50 +03:00
2014-07-09 12:38:16 +03:00
for ( auto tile : info . occupiedPositions )
2014-07-09 10:22:50 +03:00
{
2022-05-31 11:25:39 +02:00
if ( gen - > map - > isInTheMap ( tile ) & & gen - > isPossible ( tile ) & & gen - > getZoneID ( tile ) = = id ) //pile boundary may reach map border
2014-07-09 12:38:16 +03:00
gen - > setOccupied ( tile , ETileType : : BLOCKED ) ; //so that crunch path doesn't cut through objects
2014-07-09 10:22:50 +03:00
}
2017-11-03 22:03:51 +02:00
if ( ! connectPath ( closestTile , false ) ) //this place is sealed off, need to find new position
2014-06-13 13:04:17 +03:00
{
2015-06-02 20:29:37 +02:00
return false ;
2014-06-13 13:04:17 +03:00
}
2014-06-13 09:00:26 +03:00
2014-07-09 15:27:12 +03:00
//update boundary around our objects, including knowledge about objects visitable from bottom
boundary . clear ( ) ;
2014-12-26 17:17:39 +02:00
2014-12-26 20:35:45 +02:00
for ( auto tile : info . visitableFromBottomPositions )
2014-07-09 15:27:12 +03:00
{
2014-12-26 20:35:45 +02:00
gen - > foreach_neighbour ( tile , [ tile , & boundary ] ( int3 pos )
2014-07-09 15:27:12 +03:00
{
2014-12-26 20:35:45 +02:00
if ( pos . y > = tile . y ) //don't block these objects from above
2014-12-26 17:17:39 +02:00
boundary . insert ( pos ) ;
2014-12-26 20:35:45 +02:00
} ) ;
}
for ( auto tile : info . visitableFromTopPositions )
{
gen - > foreach_neighbour ( tile , [ & boundary ] ( int3 pos )
{
boundary . insert ( pos ) ;
} ) ;
}
2014-07-09 15:27:12 +03:00
2022-05-31 11:25:39 +02:00
bool isPileGuarded = isGuardNeededForTreasure ( currentValue ) ;
2014-12-26 22:13:13 +02:00
2014-12-26 20:35:45 +02:00
for ( auto tile : boundary ) //guard must be standing there
{
if ( gen - > isFree ( tile ) ) //this tile could be already blocked, don't place a monster here
2014-06-05 22:37:39 +03:00
{
2014-12-26 20:35:45 +02:00
guardPos = tile ;
break ;
2014-06-12 22:51:16 +03:00
}
2014-12-26 20:35:45 +02:00
}
2014-06-05 22:37:39 +03:00
2014-12-26 20:35:45 +02:00
if ( guardPos . valid ( ) )
{
for ( auto treasure : treasures )
2014-06-13 13:04:17 +03:00
{
2014-12-26 20:35:45 +02:00
int3 visitableOffset = treasure . second - > getVisitableOffset ( ) ;
2017-11-03 22:03:51 +02:00
placeObject ( treasure . second , treasure . first + visitableOffset ) ;
2014-12-26 20:35:45 +02:00
}
2022-05-31 11:25:39 +02:00
if ( isPileGuarded & & addMonster ( guardPos , currentValue , false ) )
2014-12-26 20:35:45 +02:00
{ //block only if the object is guarded
for ( auto tile : boundary )
2014-06-13 13:04:17 +03:00
{
2014-12-26 20:35:45 +02:00
if ( gen - > isPossible ( tile ) )
gen - > setOccupied ( tile , ETileType : : BLOCKED ) ;
2014-06-13 13:04:17 +03:00
}
2014-12-26 20:35:45 +02:00
//do not spawn anything near monster
2017-11-03 22:03:51 +02:00
gen - > foreach_neighbour ( guardPos , [ this ] ( int3 pos )
2014-12-26 20:35:45 +02:00
{
if ( gen - > isPossible ( pos ) )
gen - > setOccupied ( pos , ETileType : : FREE ) ;
} ) ;
2014-06-13 13:04:17 +03:00
}
2014-12-26 20:35:45 +02:00
}
2014-12-26 22:13:13 +02:00
else if ( isPileGuarded ) //we couldn't make a connection to this location, block it
2014-12-26 20:35:45 +02:00
{
for ( auto treasure : treasures )
2014-06-05 18:19:11 +03:00
{
2014-12-26 20:35:45 +02:00
if ( gen - > isPossible ( treasure . first ) )
gen - > setOccupied ( treasure . first , ETileType : : BLOCKED ) ;
2014-07-29 16:58:54 +03:00
2014-12-26 20:35:45 +02:00
delete treasure . second ;
2014-06-05 18:19:11 +03:00
}
}
2014-06-13 13:04:17 +03:00
2014-06-04 20:08:04 +03:00
return true ;
}
else //we did not place eveyrthing successfully
2015-06-02 20:29:37 +02:00
{
2022-05-31 11:25:39 +02:00
if ( gen - > isPossible ( pos ) )
gen - > setOccupied ( pos , ETileType : : BLOCKED ) ; //TODO: refactor stop condition
2015-06-02 20:29:37 +02:00
vstd : : erase_if_present ( possibleTiles , pos ) ;
2014-06-04 20:08:04 +03:00
return false ;
2015-06-02 20:29:37 +02:00
}
2014-06-04 20:08:04 +03:00
}
2017-11-03 22:03:51 +02:00
void CRmgTemplateZone : : initTownType ( )
2014-06-01 13:02:43 +03:00
{
2014-06-15 22:23:32 +03:00
//FIXME: handle case that this player is not present -> towns should be set to neutral
int totalTowns = 0 ;
2016-12-21 23:18:35 +02:00
//cut a ring around town to ensure crunchPath always hits it.
2017-11-03 22:03:51 +02:00
auto cutPathAroundTown = [ this ] ( const CGTownInstance * town )
2014-09-23 22:12:10 +03:00
{
2022-05-23 12:08:36 +02:00
auto clearPos = [ this ] ( const int3 & pos )
{
if ( gen - > isPossible ( pos ) )
gen - > setOccupied ( pos , ETileType : : FREE ) ;
} ;
2016-12-21 11:43:24 +02:00
for ( auto blockedTile : town - > getBlockedPos ( ) )
2014-09-23 22:12:10 +03:00
{
2022-05-23 12:08:36 +02:00
gen - > foreach_neighbour ( blockedTile , clearPos ) ;
2016-12-21 11:43:24 +02:00
}
2022-05-23 12:08:36 +02:00
//clear town entry
gen - > foreach_neighbour ( town - > visitablePos ( ) + int3 { 0 , 1 , 0 } , clearPos ) ;
2014-09-23 22:12:10 +03:00
} ;
2017-11-03 22:03:51 +02:00
auto addNewTowns = [ & totalTowns , this , & cutPathAroundTown ] ( int count , bool hasFort , PlayerColor player )
2014-07-03 10:39:26 +03:00
{
for ( int i = 0 ; i < count ; i + + )
{
2015-11-14 15:50:29 +02:00
si32 subType = townType ;
2014-07-03 10:39:26 +03:00
2015-11-14 15:50:29 +02:00
if ( totalTowns > 0 )
2014-10-30 14:03:53 +02:00
{
2015-11-14 15:50:29 +02:00
if ( ! this - > townsAreSameType )
{
if ( townTypes . size ( ) )
subType = * RandomGeneratorUtil : : nextItem ( townTypes , gen - > rand ) ;
else
subType = * RandomGeneratorUtil : : nextItem ( getDefaultTownTypes ( ) , gen - > rand ) ; //it is possible to have zone with no towns allowed
}
2014-10-30 14:03:53 +02:00
}
2014-07-03 10:39:26 +03:00
2015-11-14 15:50:29 +02:00
auto townFactory = VLC - > objtypeh - > getHandlerFor ( Obj : : TOWN , subType ) ;
auto town = ( CGTownInstance * ) townFactory - > create ( ObjectTemplate ( ) ) ;
town - > ID = Obj : : TOWN ;
2014-07-03 10:39:26 +03:00
town - > tempOwner = player ;
if ( hasFort )
town - > builtBuildings . insert ( BuildingID : : FORT ) ;
town - > builtBuildings . insert ( BuildingID : : DEFAULT ) ;
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
for ( auto spell : VLC - > spellh - > objects ) //add all regular spells to town
2014-10-26 14:09:59 +02:00
{
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
if ( ! spell - > isSpecial ( ) & & ! spell - > isCreatureAbility ( ) )
2014-10-26 14:09:59 +02:00
town - > possibleSpells . push_back ( spell - > id ) ;
}
2015-11-14 15:50:29 +02:00
if ( totalTowns < = 0 )
2014-07-07 12:29:16 +03:00
{
2022-05-31 11:25:39 +02:00
//FIXME: discovered bug with small zones - getPos is close to map boarder and we have outOfMap exception
2014-07-07 17:20:48 +03:00
//register MAIN town of zone
gen - > registerZone ( town - > subID ) ;
2014-07-07 12:29:16 +03:00
//first town in zone goes in the middle
2017-11-03 22:03:51 +02:00
placeObject ( town , getPos ( ) + town - > getVisitableOffset ( ) , true ) ;
2014-09-23 22:12:10 +03:00
cutPathAroundTown ( town ) ;
2016-12-21 14:37:19 +02:00
setPos ( town - > visitablePos ( ) ) ; //roads lead to mian town
2014-07-07 12:29:16 +03:00
}
2014-07-03 10:39:26 +03:00
else
addRequiredObject ( town ) ;
totalTowns + + ;
}
} ;
2014-05-22 20:25:17 +03:00
if ( ( type = = ETemplateZoneType : : CPU_START ) | | ( type = = ETemplateZoneType : : PLAYER_START ) )
{
2014-06-15 22:23:32 +03:00
//set zone types to player faction, generate main town
2017-08-10 18:39:27 +02:00
logGlobal - > info ( " Preparing playing zone " ) ;
2014-05-22 20:25:17 +03:00
int player_id = * owner - 1 ;
auto & playerInfo = gen - > map - > players [ player_id ] ;
2015-06-02 09:30:56 +02:00
PlayerColor player ( player_id ) ;
2014-05-22 20:25:17 +03:00
if ( playerInfo . canAnyonePlay ( ) )
{
2015-06-02 09:30:56 +02:00
player = PlayerColor ( player_id ) ;
2022-05-28 15:03:50 +02:00
townType = gen - > getMapGenOptions ( ) . getPlayersSettings ( ) . find ( player ) - > second . getStartingTown ( ) ;
2014-05-22 20:25:17 +03:00
2014-10-30 11:00:29 +02:00
if ( townType = = CMapGenOptions : : CPlayerSettings : : RANDOM_TOWN )
2022-05-28 15:03:50 +02:00
randomizeTownType ( true ) ;
2015-06-02 09:30:56 +02:00
}
else //no player - randomize town
{
player = PlayerColor : : NEUTRAL ;
2017-11-03 22:03:51 +02:00
randomizeTownType ( ) ;
2015-06-02 09:30:56 +02:00
}
2014-05-22 20:25:17 +03:00
2015-11-14 15:50:29 +02:00
auto townFactory = VLC - > objtypeh - > getHandlerFor ( Obj : : TOWN , townType ) ;
2014-10-26 14:09:59 +02:00
2015-11-14 15:50:29 +02:00
CGTownInstance * town = ( CGTownInstance * ) townFactory - > create ( ObjectTemplate ( ) ) ;
2015-06-02 09:30:56 +02:00
town - > tempOwner = player ;
town - > builtBuildings . insert ( BuildingID : : FORT ) ;
town - > builtBuildings . insert ( BuildingID : : DEFAULT ) ;
2014-05-22 20:25:17 +03:00
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
for ( auto spell : VLC - > spellh - > objects ) //add all regular spells to town
2015-06-02 09:30:56 +02:00
{
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
if ( ! spell - > isSpecial ( ) & & ! spell - > isCreatureAbility ( ) )
2015-06-02 09:30:56 +02:00
town - > possibleSpells . push_back ( spell - > id ) ;
}
//towns are big objects and should be centered around visitable position
2017-11-03 22:03:51 +02:00
placeObject ( town , getPos ( ) + town - > getVisitableOffset ( ) , true ) ;
2015-06-02 09:30:56 +02:00
cutPathAroundTown ( town ) ;
2016-12-21 14:37:19 +02:00
setPos ( town - > visitablePos ( ) ) ; //roads lead to mian town
2015-06-02 09:30:56 +02:00
totalTowns + + ;
//register MAIN town of zone only
gen - > registerZone ( town - > subID ) ;
2014-06-15 22:23:32 +03:00
2015-06-02 09:30:56 +02:00
if ( playerInfo . canAnyonePlay ( ) ) //configure info for owning player
{
2017-08-11 19:03:05 +02:00
logGlobal - > trace ( " Fill player info %d " , player_id ) ;
2014-06-05 18:19:11 +03:00
2014-05-22 20:25:17 +03:00
// Update player info
playerInfo . allowedFactions . clear ( ) ;
2015-06-02 09:30:56 +02:00
playerInfo . allowedFactions . insert ( townType ) ;
2014-05-22 20:25:17 +03:00
playerInfo . hasMainTown = true ;
2016-11-13 12:38:42 +02:00
playerInfo . posOfMainTown = town - > pos ;
2014-05-22 20:25:17 +03:00
playerInfo . generateHeroAtMainTown = true ;
2014-06-15 22:23:32 +03:00
//now create actual towns
2015-06-02 09:30:56 +02:00
addNewTowns ( playerTowns . getCastleCount ( ) - 1 , true , player ) ;
addNewTowns ( playerTowns . getTownCount ( ) , false , player ) ;
2014-05-22 20:25:17 +03:00
}
else
2015-06-02 09:30:56 +02:00
{
addNewTowns ( playerTowns . getCastleCount ( ) - 1 , true , PlayerColor : : NEUTRAL ) ;
addNewTowns ( playerTowns . getTownCount ( ) , false , PlayerColor : : NEUTRAL ) ;
2014-05-22 20:25:17 +03:00
}
}
2015-06-02 09:30:56 +02:00
else //randomize town types for any other zones as well
2014-05-24 15:33:22 +03:00
{
2017-11-03 22:03:51 +02:00
randomizeTownType ( ) ;
2014-05-24 15:33:22 +03:00
}
2014-06-15 22:23:32 +03:00
2014-07-03 10:39:26 +03:00
addNewTowns ( neutralTowns . getCastleCount ( ) , true , PlayerColor : : NEUTRAL ) ;
addNewTowns ( neutralTowns . getTownCount ( ) , false , PlayerColor : : NEUTRAL ) ;
2014-10-30 14:03:53 +02:00
if ( ! totalTowns ) //if there's no town present, get random faction for dwellings and pandoras
{
//25% chance for neutral
if ( gen - > rand . nextInt ( 1 , 100 ) < = 25 )
{
townType = ETownType : : NEUTRAL ;
}
else
{
if ( townTypes . size ( ) )
townType = * RandomGeneratorUtil : : nextItem ( townTypes , gen - > rand ) ;
else if ( monsterTypes . size ( ) )
townType = * RandomGeneratorUtil : : nextItem ( monsterTypes , gen - > rand ) ; //this happens in Clash of Dragons in treasure zones, where all towns are banned
2015-06-02 09:30:56 +02:00
else //just in any case
2017-11-03 22:03:51 +02:00
randomizeTownType ( ) ;
2014-10-30 14:03:53 +02:00
}
}
2014-06-14 18:14:59 +03:00
}
2022-05-28 15:03:50 +02:00
void CRmgTemplateZone : : randomizeTownType ( bool matchUndergroundType )
2015-06-02 09:30:56 +02:00
{
2022-05-28 15:03:50 +02:00
auto townTypesAllowed = ( townTypes . size ( ) ? townTypes : getDefaultTownTypes ( ) ) ;
if ( matchUndergroundType & & gen - > getMapGenOptions ( ) . getHasTwoLevels ( ) )
{
std : : set < TFaction > townTypesVerify ;
for ( TFaction factionIdx : townTypesAllowed )
{
bool preferUnderground = ( * VLC - > townh ) [ factionIdx ] - > preferUndergroundPlacement ;
if ( isUnderground ( ) ? preferUnderground : ! preferUnderground )
{
townTypesVerify . insert ( factionIdx ) ;
}
}
if ( ! townTypesVerify . empty ( ) )
townTypesAllowed = townTypesVerify ;
}
townType = * RandomGeneratorUtil : : nextItem ( townTypesAllowed , gen - > rand ) ;
2015-06-02 09:30:56 +02:00
}
2017-11-03 22:03:51 +02:00
void CRmgTemplateZone : : initTerrainType ( )
2014-06-14 18:14:59 +03:00
{
2022-05-31 11:25:39 +02:00
if ( type = = ETemplateZoneType : : WATER )
2014-07-04 11:45:00 +03:00
{
2022-06-20 16:39:50 +02:00
//collect all water terrain types
std : : vector < Terrain > waterTerrains ;
for ( auto & terrain : Terrain : : Manager : : terrains ( ) )
if ( terrain . isWater ( ) )
waterTerrains . push_back ( terrain ) ;
terrainType = * RandomGeneratorUtil : : nextItem ( waterTerrains , gen - > rand ) ;
2014-07-04 11:45:00 +03:00
}
else
{
2022-05-31 11:25:39 +02:00
if ( matchTerrainToTown & & townType ! = ETownType : : NEUTRAL )
2022-06-20 16:39:50 +02:00
{
2022-05-31 11:25:39 +02:00
terrainType = ( * VLC - > townh ) [ townType ] - > nativeTerrain ;
2022-06-20 16:39:50 +02:00
}
2022-05-31 11:25:39 +02:00
else
2022-06-20 16:39:50 +02:00
{
2022-05-31 11:25:39 +02:00
terrainType = * RandomGeneratorUtil : : nextItem ( terrainTypes , gen - > rand ) ;
2022-06-20 16:39:50 +02:00
}
2014-07-03 18:24:28 +03:00
2022-05-31 11:25:39 +02:00
//TODO: allow new types of terrain?
{
2022-06-05 08:02:58 +02:00
if ( isUnderground ( ) )
2022-05-31 11:25:39 +02:00
{
2022-06-05 08:02:58 +02:00
if ( ! vstd : : contains ( gen - > getConfig ( ) . terrainUndergroundAllowed , terrainType ) )
2022-06-20 16:39:50 +02:00
{
//collect all underground terrain types
std : : vector < Terrain > undegroundTerrains ;
for ( auto & terrain : Terrain : : Manager : : terrains ( ) )
if ( terrain . isUnderground ( ) )
undegroundTerrains . push_back ( terrain ) ;
terrainType = * RandomGeneratorUtil : : nextItem ( undegroundTerrains , gen - > rand ) ;
}
2022-05-31 11:25:39 +02:00
}
else
{
2022-06-20 16:39:50 +02:00
if ( vstd : : contains ( gen - > getConfig ( ) . terrainGroundProhibit , terrainType ) | | terrainType . isUnderground ( ) )
terrainType = Terrain ( " dirt " ) ;
2022-05-31 11:25:39 +02:00
}
}
}
2017-11-03 22:03:51 +02:00
paintZoneTerrain ( terrainType ) ;
2014-07-03 18:24:28 +03:00
}
2022-06-20 16:39:50 +02:00
void CRmgTemplateZone : : paintZoneTerrain ( Terrain terrainType )
2014-07-03 18:24:28 +03:00
{
2016-08-11 07:49:08 +02:00
std : : vector < int3 > tiles ( tileinfo . begin ( ) , tileinfo . end ( ) ) ;
2022-05-28 15:03:50 +02:00
gen - > getEditManager ( ) - > getTerrainSelection ( ) . setSelection ( tiles ) ;
gen - > getEditManager ( ) - > drawTerrain ( terrainType , & gen - > rand ) ;
2014-06-14 18:14:59 +03:00
}
2017-11-03 22:03:51 +02:00
bool CRmgTemplateZone : : placeMines ( )
2014-06-14 21:05:19 +03:00
{
2022-05-23 12:08:36 +02:00
using namespace Res ;
std : : vector < CGMine * > createdMines ;
for ( const auto & mineInfo : mines )
2015-11-14 15:50:29 +02:00
{
2022-05-23 12:08:36 +02:00
ERes res = ( ERes ) mineInfo . first ;
for ( int i = 0 ; i < mineInfo . second ; + + i )
2014-06-14 21:05:19 +03:00
{
2022-05-23 12:08:36 +02:00
auto mine = ( CGMine * ) VLC - > objtypeh - > getHandlerFor ( Obj : : MINE , res ) - > create ( ObjectTemplate ( ) ) ;
2014-06-14 21:05:19 +03:00
mine - > producedResource = res ;
2016-02-14 11:13:30 +02:00
mine - > tempOwner = PlayerColor : : NEUTRAL ;
2014-06-14 21:05:19 +03:00
mine - > producedQuantity = mine - > defaultResProduction ( ) ;
2022-05-23 12:08:36 +02:00
createdMines . push_back ( mine ) ;
if ( ! i & & ( res = = ERes : : WOOD | | res = = ERes : : ORE ) )
2022-06-05 08:02:58 +02:00
addCloseObject ( mine , gen - > getConfig ( ) . mineValues . at ( res ) ) ; //only first wood&ore mines are close
2015-09-29 11:01:59 +02:00
else
2022-06-05 08:02:58 +02:00
addRequiredObject ( mine , gen - > getConfig ( ) . mineValues . at ( res ) ) ;
2014-06-14 21:05:19 +03:00
}
}
2022-05-23 12:08:36 +02:00
//create extra resources
2022-06-05 08:02:58 +02:00
if ( int extraRes = gen - > getConfig ( ) . mineExtraResources )
2014-06-14 21:05:19 +03:00
{
2022-06-05 08:02:58 +02:00
for ( auto * mine : createdMines )
2014-06-14 21:05:19 +03:00
{
2022-06-05 08:02:58 +02:00
for ( int rc = gen - > rand . nextInt ( 1 , extraRes ) ; rc > 0 ; - - rc )
{
auto resourse = ( CGResource * ) VLC - > objtypeh - > getHandlerFor ( Obj : : RESOURCE , mine - > producedResource ) - > create ( ObjectTemplate ( ) ) ;
resourse - > amount = CGResource : : RANDOM_AMOUNT ;
addNearbyObject ( resourse , mine ) ;
}
2014-06-14 21:05:19 +03:00
}
}
2022-06-05 08:02:58 +02:00
2014-06-14 21:05:19 +03:00
return true ;
}
2022-05-31 11:25:39 +02:00
EObjectPlacingResult : : EObjectPlacingResult CRmgTemplateZone : : tryToPlaceObjectAndConnectToPath ( CGObjectInstance * obj , const int3 & pos )
2016-07-12 11:42:03 +02:00
{
//check if we can find a path around this object. Tiles will be set to "USED" after object is successfully placed.
obj - > pos = pos ;
gen - > setOccupied ( obj - > visitablePos ( ) , ETileType : : BLOCKED ) ;
for ( auto tile : obj - > getBlockedPos ( ) )
{
if ( gen - > map - > isInTheMap ( tile ) )
gen - > setOccupied ( tile , ETileType : : BLOCKED ) ;
}
2017-11-03 22:03:51 +02:00
int3 accessibleOffset = getAccessibleOffset ( obj - > appearance , pos ) ;
2016-07-12 11:42:03 +02:00
if ( ! accessibleOffset . valid ( ) )
{
2017-08-11 19:03:05 +02:00
logGlobal - > warn ( " Cannot access required object at position %s, retrying " , pos . toString ( ) ) ;
2016-07-12 11:42:03 +02:00
return EObjectPlacingResult : : CANNOT_FIT ;
}
2017-11-03 22:03:51 +02:00
if ( ! connectPath ( accessibleOffset , true ) )
2016-07-12 11:42:03 +02:00
{
2017-08-11 19:03:05 +02:00
logGlobal - > trace ( " Failed to create path to required object at position %s, retrying " , pos . toString ( ) ) ;
2016-07-12 11:42:03 +02:00
return EObjectPlacingResult : : SEALED_OFF ;
}
else
return EObjectPlacingResult : : SUCCESS ;
}
2017-11-03 22:03:51 +02:00
bool CRmgTemplateZone : : createRequiredObjects ( )
2014-06-14 18:14:59 +03:00
{
2017-08-10 18:39:27 +02:00
logGlobal - > trace ( " Creating required objects " ) ;
2016-02-15 12:34:37 +02:00
2015-06-02 20:29:37 +02:00
for ( const auto & object : requiredObjects )
2014-05-22 20:25:17 +03:00
{
2015-06-02 20:29:37 +02:00
auto obj = object . first ;
2022-06-20 16:39:50 +02:00
if ( ! obj - > appearance . canBePlacedAt ( terrainType ) )
continue ;
2014-05-22 20:25:17 +03:00
int3 pos ;
2015-06-03 19:38:53 +02:00
while ( true )
2014-05-22 20:25:17 +03:00
{
2017-11-03 22:03:51 +02:00
if ( ! findPlaceForObject ( obj , 3 , pos ) )
2015-06-02 20:29:37 +02:00
{
2017-08-10 18:39:27 +02:00
logGlobal - > error ( " Failed to fill zone %d due to lack of space " , id ) ;
2015-06-02 20:29:37 +02:00
return false ;
}
2022-06-20 16:39:50 +02:00
2017-11-03 22:03:51 +02:00
if ( tryToPlaceObjectAndConnectToPath ( obj , pos ) = = EObjectPlacingResult : : SUCCESS )
2015-06-03 19:38:53 +02:00
{
2016-07-12 11:42:03 +02:00
//paths to required objects constitute main paths of zone. otherwise they just may lead to middle and create dead zones
2017-11-03 22:03:51 +02:00
placeObject ( obj , pos ) ;
guardObject ( obj , object . second , ( obj - > ID = = Obj : : MONOLITH_TWO_WAY ) , true ) ;
2015-06-03 19:38:53 +02:00
break ;
2016-07-12 11:42:03 +02:00
}
2015-06-02 20:29:37 +02:00
}
2014-05-22 20:25:17 +03:00
}
2014-12-20 23:13:10 +02:00
for ( const auto & obj : closeObjects )
{
2017-11-03 22:03:51 +02:00
setTemplateForObject ( obj . first ) ;
2022-06-20 16:39:50 +02:00
if ( ! obj . first - > appearance . canBePlacedAt ( terrainType ) )
continue ;
2016-07-12 11:42:03 +02:00
auto tilesBlockedByObject = obj . first - > getBlockedOffsets ( ) ;
2016-02-15 12:34:37 +02:00
2016-07-12 11:42:03 +02:00
bool finished = false ;
2022-04-05 19:41:56 +02:00
bool attempt = true ;
while ( ! finished & & attempt )
2014-12-20 23:13:10 +02:00
{
2022-04-05 19:41:56 +02:00
attempt = false ;
2016-07-12 11:42:03 +02:00
std : : vector < int3 > tiles ( possibleTiles . begin ( ) , possibleTiles . end ( ) ) ;
//new tiles vector after each object has been placed, OR misplaced area has been sealed off
2015-09-29 11:01:59 +02:00
2017-11-03 22:03:51 +02:00
boost : : remove_if ( tiles , [ obj , this ] ( int3 & tile ) - > bool
2016-07-12 11:42:03 +02:00
{
//object must be accessible from at least one surounding tile
2022-05-31 11:25:39 +02:00
return ! this - > isAccessibleFromSomewhere ( obj . first - > appearance , tile ) ;
2016-07-12 11:42:03 +02:00
} ) ;
2014-12-20 23:13:10 +02:00
2022-05-31 11:25:39 +02:00
auto targetPosition = requestedPositions . find ( obj . first ) ! = requestedPositions . end ( ) ? requestedPositions [ obj . first ] : pos ;
2016-07-12 11:42:03 +02:00
// smallest distance to zone center, greatest distance to nearest object
2022-05-31 11:25:39 +02:00
auto isCloser = [ this , & targetPosition , & tilesBlockedByObject ] ( const int3 & lhs , const int3 & rhs ) - > bool
2016-07-12 11:42:03 +02:00
{
2022-05-31 11:25:39 +02:00
float lDist = std : : numeric_limits < float > : : max ( ) ;
float rDist = std : : numeric_limits < float > : : max ( ) ;
for ( int3 t : tilesBlockedByObject )
{
t + = targetPosition ;
lDist = fmin ( lDist , static_cast < float > ( t . dist2d ( lhs ) ) ) ;
rDist = fmin ( rDist , static_cast < float > ( t . dist2d ( rhs ) ) ) ;
}
2016-07-12 11:42:03 +02:00
lDist * = ( lDist > 12 ) ? 10 : 1 ; //objects within 12 tile radius are preferred (smaller distance rating)
rDist * = ( rDist > 12 ) ? 10 : 1 ;
2014-12-20 23:13:10 +02:00
2016-07-12 11:42:03 +02:00
return ( lDist * 0.5f - std : : sqrt ( gen - > getNearestObjectDistance ( lhs ) ) ) < ( rDist * 0.5f - std : : sqrt ( gen - > getNearestObjectDistance ( rhs ) ) ) ;
} ;
2022-05-31 11:25:39 +02:00
2016-07-12 11:42:03 +02:00
boost : : sort ( tiles , isCloser ) ;
2014-12-20 23:13:10 +02:00
2016-07-12 11:42:03 +02:00
if ( tiles . empty ( ) )
{
2017-08-10 18:39:27 +02:00
logGlobal - > error ( " Failed to fill zone %d due to lack of space " , id ) ;
2016-07-12 11:42:03 +02:00
return false ;
}
for ( auto tile : tiles )
2014-12-20 23:13:10 +02:00
{
2016-07-12 11:42:03 +02:00
//code partially adapted from findPlaceForObject()
2022-05-31 11:25:39 +02:00
if ( ! areAllTilesAvailable ( obj . first , tile , tilesBlockedByObject ) )
2016-07-12 11:42:03 +02:00
continue ;
2022-04-05 19:41:56 +02:00
attempt = true ;
2017-11-03 22:03:51 +02:00
EObjectPlacingResult : : EObjectPlacingResult result = tryToPlaceObjectAndConnectToPath ( obj . first , tile ) ;
2016-07-12 11:42:03 +02:00
if ( result = = EObjectPlacingResult : : SUCCESS )
2014-12-20 23:13:10 +02:00
{
2017-11-03 22:03:51 +02:00
placeObject ( obj . first , tile ) ;
guardObject ( obj . first , obj . second , ( obj . first - > ID = = Obj : : MONOLITH_TWO_WAY ) , true ) ;
2016-07-12 11:42:03 +02:00
finished = true ;
2014-12-20 23:13:10 +02:00
break ;
}
2016-07-12 11:42:03 +02:00
else if ( result = = EObjectPlacingResult : : CANNOT_FIT )
continue ; // next tile
else if ( result = = EObjectPlacingResult : : SEALED_OFF )
{
break ; //tiles expired, pick new ones
}
else
throw ( rmgException ( " Wrong result of tryToPlaceObjectAndConnectToPath() " ) ) ;
2014-12-20 23:13:10 +02:00
}
}
}
2022-05-23 12:08:36 +02:00
//create nearby objects (e.g. extra resources close to mines)
for ( const auto & object : nearbyObjects )
{
auto obj = object . first ;
std : : set < int3 > possiblePositions ;
for ( auto blockedTile : object . second - > getBlockedPos ( ) )
{
gen - > foreachDirectNeighbour ( blockedTile , [ this , & possiblePositions ] ( int3 pos )
{
2022-05-31 11:25:39 +02:00
if ( ! gen - > isBlocked ( pos ) & & tileinfo . count ( pos ) )
2022-05-23 12:08:36 +02:00
{
//some resources still could be unaccessible, at least one free cell shall be
gen - > foreach_neighbour ( pos , [ this , & possiblePositions , & pos ] ( int3 p )
{
if ( gen - > isFree ( p ) )
possiblePositions . insert ( pos ) ;
} ) ;
}
} ) ;
}
if ( possiblePositions . empty ( ) )
{
delete obj ; //is it correct way to prevent leak?
}
else
{
auto pos = * RandomGeneratorUtil : : nextItem ( possiblePositions , gen - > rand ) ;
placeObject ( obj , pos ) ;
}
}
2022-05-31 11:25:39 +02:00
//create object on specific positions
//TODO: implement guards
for ( const auto & obj : instantObjects )
{
if ( tryToPlaceObjectAndConnectToPath ( obj . first , obj . second ) = = EObjectPlacingResult : : SUCCESS )
{
placeObject ( obj . first , obj . second ) ;
//TODO: guardObject(...)
}
}
requiredObjects . clear ( ) ;
closeObjects . clear ( ) ;
nearbyObjects . clear ( ) ;
instantObjects . clear ( ) ;
2022-05-23 12:08:36 +02:00
2014-06-15 09:48:46 +03:00
return true ;
}
2022-05-31 11:25:39 +02:00
int3 CRmgTemplateZone : : makeBoat ( TRmgTemplateZoneId land , const std : : set < int3 > & lake )
{
std : : set < int3 > lakeCoast ;
std : : set_intersection ( gen - > getZones ( ) [ land ] - > getCoastTiles ( ) . begin ( ) , gen - > getZones ( ) [ land ] - > getCoastTiles ( ) . end ( ) , lake . begin ( ) , lake . end ( ) , std : : inserter ( lakeCoast , lakeCoast . begin ( ) ) ) ;
for ( int randomAttempts = 0 ; randomAttempts < 5 ; + + randomAttempts )
{
auto coastTile = * RandomGeneratorUtil : : nextItem ( lakeCoast , gen - > rand ) ;
if ( gen - > getZoneID ( coastTile ) = = gen - > getZoneWater ( ) . first & & isWaterConnected ( land , coastTile ) & & makeBoat ( land , coastTile ) )
return coastTile ;
}
//if no success on random selection, use brute force
for ( const auto & coastTile : lakeCoast )
{
if ( gen - > getZoneID ( coastTile ) = = gen - > getZoneWater ( ) . first & & isWaterConnected ( land , coastTile ) & & makeBoat ( land , coastTile ) )
return coastTile ;
}
return int3 ( - 1 , - 1 , - 1 ) ;
}
bool CRmgTemplateZone : : makeBoat ( TRmgTemplateZoneId land , const int3 & coast )
{
//verify coast
if ( gen - > getZoneWater ( ) . first ! = id )
throw rmgException ( " Cannot make a ship: not a water zone " ) ;
if ( gen - > getZoneID ( coast ) ! = id )
throw rmgException ( " Cannot make a ship: coast tile doesn't belong to water " ) ;
//find zone for ship boarding
std : : vector < int3 > landTiles ;
gen - > foreach_neighbour ( coast , [ this , & landTiles , land ] ( const int3 & t )
{
if ( land = = gen - > getZoneID ( t ) & & gen - > isPossible ( t ) )
{
landTiles . push_back ( t ) ;
}
} ) ;
if ( landTiles . empty ( ) )
return false ;
int3 landTile = { - 1 , - 1 , - 1 } ;
for ( auto & lt : landTiles )
{
if ( gen - > getZones ( ) [ land ] - > connectPath ( lt , false ) )
{
landTile = lt ;
gen - > setOccupied ( landTile , ETileType : : FREE ) ;
break ;
}
}
if ( ! landTile . valid ( ) )
return false ;
auto subObjects = VLC - > objtypeh - > knownSubObjects ( Obj : : BOAT ) ;
auto * boat = ( CGBoat * ) VLC - > objtypeh - > getHandlerFor ( Obj : : BOAT , * RandomGeneratorUtil : : nextItem ( subObjects , gen - > rand ) ) - > create ( ObjectTemplate ( ) ) ;
auto targetPos = boat - > getVisitableOffset ( ) + coast + int3 { 1 , 0 , 0 } ; //+1 offset for boat - bug?
if ( gen - > map - > isInTheMap ( targetPos ) & & gen - > isPossible ( targetPos ) & & gen - > getZoneID ( targetPos ) = = getId ( ) )
{
//don't connect to path because it's not initialized
addObjectAtPosition ( boat , targetPos ) ;
gen - > setOccupied ( targetPos , ETileType : : USED ) ;
return true ;
}
return false ;
}
int3 CRmgTemplateZone : : createShipyard ( const std : : set < int3 > & lake , si32 guardStrength )
{
std : : set < int3 > lakeCoast ;
std : : set_intersection ( getCoastTiles ( ) . begin ( ) , getCoastTiles ( ) . end ( ) , lake . begin ( ) , lake . end ( ) , std : : inserter ( lakeCoast , lakeCoast . begin ( ) ) ) ;
for ( int randomAttempts = 0 ; randomAttempts < 5 ; + + randomAttempts )
{
auto coastTile = * RandomGeneratorUtil : : nextItem ( lakeCoast , gen - > rand ) ;
if ( gen - > getZoneID ( coastTile ) = = gen - > getZoneWater ( ) . first & & isWaterConnected ( id , coastTile ) & & createShipyard ( coastTile , guardStrength ) )
return coastTile ;
}
//if no success on random selection, use brute force
for ( const auto & coastTile : lakeCoast )
{
if ( gen - > getZoneID ( coastTile ) = = gen - > getZoneWater ( ) . first & & isWaterConnected ( id , coastTile ) & & createShipyard ( coastTile , guardStrength ) )
return coastTile ;
}
return int3 ( - 1 , - 1 , - 1 ) ;
}
bool CRmgTemplateZone : : createShipyard ( const int3 & position , si32 guardStrength )
{
2022-06-20 16:39:50 +02:00
int subtype = chooseRandomAppearance ( Obj : : SHIPYARD ) ;
auto shipyard = ( CGShipyard * ) VLC - > objtypeh - > getHandlerFor ( Obj : : SHIPYARD , subtype ) - > create ( ObjectTemplate ( ) ) ;
2022-05-31 11:25:39 +02:00
shipyard - > tempOwner = PlayerColor : : NEUTRAL ;
setTemplateForObject ( shipyard ) ;
2022-06-05 08:02:58 +02:00
std : : vector < int3 > outOffsets ;
2022-05-31 11:25:39 +02:00
auto tilesBlockedByObject = shipyard - > getBlockedOffsets ( ) ;
tilesBlockedByObject . insert ( shipyard - > getVisitableOffset ( ) ) ;
2022-06-05 08:02:58 +02:00
shipyard - > getOutOffsets ( outOffsets ) ;
2022-05-31 11:25:39 +02:00
int3 targetTile ( - 1 , - 1 , - 1 ) ;
std : : set < int3 > shipAccessCandidates ;
2022-06-05 08:02:58 +02:00
for ( const auto & outOffset : outOffsets )
2022-05-31 11:25:39 +02:00
{
2022-06-05 08:02:58 +02:00
auto candidateTile = position - outOffset ;
std : : set < int3 > tilesBlockedAbsolute ;
//check space under object
bool allClear = true ;
for ( const auto & objectTileOffset : tilesBlockedByObject )
2022-05-31 11:25:39 +02:00
{
2022-06-05 08:02:58 +02:00
auto objectTile = candidateTile + objectTileOffset ;
tilesBlockedAbsolute . insert ( objectTile ) ;
if ( ! gen - > map - > isInTheMap ( objectTile ) | | ! gen - > isPossible ( objectTile ) | | gen - > getZoneID ( objectTile ) ! = id )
2022-05-31 11:25:39 +02:00
{
2022-06-05 08:02:58 +02:00
allClear = false ;
break ;
2022-05-31 11:25:39 +02:00
}
}
2022-06-05 08:02:58 +02:00
if ( ! allClear ) //cannot place shipyard anyway
continue ;
//prepare temporary map
for ( auto & blockedPos : tilesBlockedAbsolute )
gen - > setOccupied ( blockedPos , ETileType : : USED ) ;
2022-05-31 11:25:39 +02:00
2022-06-05 08:02:58 +02:00
//check if boarding position is accessible
gen - > foreach_neighbour ( position , [ this , & shipAccessCandidates ] ( const int3 & v )
{
if ( ! gen - > isBlocked ( v ) & & gen - > getZoneID ( v ) = = id )
{
//make sure that it's possible to create path to boarding position
if ( connectWithCenter ( v , false , false ) )
shipAccessCandidates . insert ( v ) ;
}
} ) ;
//check if we can connect shipyard entrance with path
if ( ! connectWithCenter ( candidateTile + shipyard - > getVisitableOffset ( ) , false ) )
shipAccessCandidates . clear ( ) ;
//rollback temporary map
for ( auto & blockedPos : tilesBlockedAbsolute )
gen - > setOccupied ( blockedPos , ETileType : : POSSIBLE ) ;
if ( ! shipAccessCandidates . empty ( ) & & isAccessibleFromSomewhere ( shipyard - > appearance , candidateTile ) )
2022-05-31 11:25:39 +02:00
{
targetTile = candidateTile ;
2022-06-05 08:02:58 +02:00
break ; //no need to check other offsets as we already found position
2022-05-31 11:25:39 +02:00
}
shipAccessCandidates . clear ( ) ; //invalidate positions
}
if ( ! targetTile . valid ( ) )
{
delete shipyard ;
return false ;
}
2022-06-05 08:02:58 +02:00
if ( tryToPlaceObjectAndConnectToPath ( shipyard , targetTile ) = = EObjectPlacingResult : : SUCCESS )
2022-05-31 11:25:39 +02:00
{
2022-06-05 08:02:58 +02:00
placeObject ( shipyard , targetTile ) ;
guardObject ( shipyard , guardStrength , false , true ) ;
for ( auto & accessPosition : shipAccessCandidates )
2022-05-31 11:25:39 +02:00
{
2022-06-05 08:02:58 +02:00
if ( connectPath ( accessPosition , false ) )
{
gen - > setOccupied ( accessPosition , ETileType : : FREE ) ;
return true ;
}
2022-05-31 11:25:39 +02:00
}
}
2022-06-05 08:02:58 +02:00
logGlobal - > warn ( " Cannot find path to shipyard boarding position " ) ;
delete shipyard ;
return false ;
2022-05-31 11:25:39 +02:00
}
2017-11-03 22:03:51 +02:00
void CRmgTemplateZone : : createTreasures ( )
2014-06-15 09:48:46 +03:00
{
2022-05-28 15:03:50 +02:00
int mapMonsterStrength = gen - > getMapGenOptions ( ) . getMonsterStrength ( ) ;
2014-12-26 17:17:39 +02:00
int monsterStrength = zoneMonsterStrength + mapMonsterStrength - 1 ; //array index from 0 to 4
static int minGuardedValues [ ] = { 6500 , 4167 , 3000 , 1833 , 1333 } ;
minGuardedValue = minGuardedValues [ monsterStrength ] ;
2014-12-20 15:01:48 +02:00
auto valueComparator = [ ] ( const CTreasureInfo & lhs , const CTreasureInfo & rhs ) - > bool
{
return lhs . max > rhs . max ;
} ;
2014-07-30 14:52:21 +03:00
2014-12-20 15:01:48 +02:00
//place biggest treasures first at large distance, place smaller ones inbetween
boost : : sort ( treasureInfo , valueComparator ) ;
2014-06-04 11:16:08 +03:00
2016-06-05 10:18:10 +02:00
//sort treasures by ascending value so we can stop checking treasures with too high value
boost : : sort ( possibleObjects , [ ] ( const ObjectInfo & oi1 , const ObjectInfo & oi2 ) - > bool
{
return oi1 . value < oi2 . value ;
} ) ;
2014-12-20 15:01:48 +02:00
int totalDensity = 0 ;
for ( auto t : treasureInfo )
{
2015-03-11 17:19:03 +02:00
//discard objects with too high value to be ever placed
vstd : : erase_if ( possibleObjects , [ t ] ( const ObjectInfo & oi ) - > bool
{
return oi . value > t . max ;
} ) ;
2014-12-20 15:01:48 +02:00
totalDensity + = t . density ;
2014-07-24 20:16:49 +03:00
2014-12-20 15:01:48 +02:00
//treasure density is inversely proportional to zone size but must be scaled back to map size
//also, normalize it to zone count - higher count means relatively smaller zones
//this is squared distance for optimization purposes
2020-10-01 10:38:06 +02:00
const float minDistance = std : : max < float > ( ( 125.f / totalDensity ) , 2.0f ) ;
2014-12-20 15:01:48 +02:00
//distance lower than 2 causes objects to overlap and crash
2014-05-23 20:14:20 +03:00
2015-06-02 20:29:37 +02:00
bool stop = false ;
2014-12-20 15:01:48 +02:00
do {
//optimization - don't check tiles which are not allowed
2017-11-03 22:03:51 +02:00
vstd : : erase_if ( possibleTiles , [ this ] ( const int3 & tile ) - > bool
2014-12-20 15:01:48 +02:00
{
2022-06-05 08:02:58 +02:00
//for water area we sholdn't place treasures close to coast
for ( auto & lake : lakes )
if ( vstd : : contains ( lake . distance , tile ) & & lake . distance [ tile ] < 2 )
return true ;
return ! gen - > isPossible ( tile ) | | gen - > getZoneID ( tile ) ! = getId ( ) ;
2014-12-20 15:01:48 +02:00
} ) ;
2016-02-15 12:34:37 +02:00
2015-06-02 20:29:37 +02:00
int3 treasureTilePos ;
2014-12-26 20:35:45 +02:00
//If we are able to place at least one object with value lower than minGuardedValue, it's ok
2015-06-02 20:29:37 +02:00
do
2014-12-20 15:01:48 +02:00
{
2017-11-03 22:03:51 +02:00
if ( ! findPlaceForTreasurePile ( minDistance , treasureTilePos , t . min ) )
2015-06-02 20:29:37 +02:00
{
stop = true ;
break ;
}
2014-12-20 15:01:48 +02:00
}
2017-11-03 22:03:51 +02:00
while ( ! createTreasurePile ( treasureTilePos , minDistance , t ) ) ; //failed creation - position was wrong, cannot connect it
2014-12-20 15:01:48 +02:00
2015-06-02 20:29:37 +02:00
} while ( ! stop ) ;
2014-12-20 15:01:48 +02:00
}
2014-06-15 09:48:46 +03:00
}
2014-05-22 20:25:17 +03:00
2017-11-03 22:03:51 +02:00
void CRmgTemplateZone : : createObstacles1 ( )
2015-03-28 23:03:38 +02:00
{
if ( pos . z ) //underground
{
//now make sure all accessible tiles have no additional rock on them
std : : vector < int3 > accessibleTiles ;
for ( auto tile : tileinfo )
{
if ( gen - > isFree ( tile ) | | gen - > isUsed ( tile ) )
{
accessibleTiles . push_back ( tile ) ;
}
}
2022-05-28 15:03:50 +02:00
gen - > getEditManager ( ) - > getTerrainSelection ( ) . setSelection ( accessibleTiles ) ;
gen - > getEditManager ( ) - > drawTerrain ( terrainType , & gen - > rand ) ;
2015-03-28 23:03:38 +02:00
}
}
2017-11-03 22:03:51 +02:00
void CRmgTemplateZone : : createObstacles2 ( )
2014-09-23 17:53:26 +03:00
{
2014-07-25 18:55:48 +03:00
typedef std : : vector < ObjectTemplate > obstacleVector ;
//obstacleVector possibleObstacles;
std : : map < ui8 , obstacleVector > obstaclesBySize ;
typedef std : : pair < ui8 , obstacleVector > obstaclePair ;
std : : vector < obstaclePair > possibleObstacles ;
2014-07-04 00:11:24 +03:00
2014-06-28 10:46:32 +03:00
//get all possible obstacles for this terrain
2016-02-15 12:34:37 +02:00
for ( auto primaryID : VLC - > objtypeh - > knownObjects ( ) )
{
for ( auto secondaryID : VLC - > objtypeh - > knownSubObjects ( primaryID ) )
{
auto handler = VLC - > objtypeh - > getHandlerFor ( primaryID , secondaryID ) ;
2014-06-28 10:46:32 +03:00
if ( handler - > isStaticObject ( ) )
{
for ( auto temp : handler - > getTemplates ( ) )
{
if ( temp . canBePlacedAt ( terrainType ) & & temp . getBlockMapOffset ( ) . valid ( ) )
2020-10-01 10:38:06 +02:00
obstaclesBySize [ ( ui8 ) temp . getBlockedOffsets ( ) . size ( ) ] . push_back ( temp ) ;
2014-06-28 10:46:32 +03:00
}
}
2016-02-15 12:34:37 +02:00
}
2014-06-28 10:46:32 +03:00
}
2014-07-25 18:55:48 +03:00
for ( auto o : obstaclesBySize )
{
possibleObstacles . push_back ( std : : make_pair ( o . first , o . second ) ) ;
}
2014-07-26 09:12:45 +03:00
boost : : sort ( possibleObstacles , [ ] ( const obstaclePair & p1 , const obstaclePair & p2 ) - > bool
2014-07-25 18:55:48 +03:00
{
return p1 . first > p2 . first ; //bigger obstacles first
} ) ;
2014-06-28 10:46:32 +03:00
2022-05-28 15:03:50 +02:00
auto sel = gen - > getEditManager ( ) - > getTerrainSelection ( ) ;
2014-05-22 20:25:17 +03:00
sel . clearSelection ( ) ;
2014-06-28 10:46:32 +03:00
2017-11-03 22:03:51 +02:00
auto tryToPlaceObstacleHere = [ this , & possibleObstacles ] ( int3 & tile , int index ) - > bool
2014-05-22 20:25:17 +03:00
{
2014-07-25 18:55:48 +03:00
auto temp = * RandomGeneratorUtil : : nextItem ( possibleObstacles [ index ] . second , gen - > rand ) ;
2015-03-12 09:31:30 +02:00
int3 obstaclePos = tile + temp . getBlockMapOffset ( ) ;
2017-11-03 22:03:51 +02:00
if ( canObstacleBePlacedHere ( temp , obstaclePos ) ) //can be placed here
2014-06-28 10:46:32 +03:00
{
auto obj = VLC - > objtypeh - > getHandlerFor ( temp . id , temp . subid ) - > create ( temp ) ;
2017-11-03 22:03:51 +02:00
placeObject ( obj , obstaclePos , false ) ;
2014-06-28 10:46:32 +03:00
return true ;
}
return false ;
} ;
2014-05-31 13:26:37 +03:00
2014-07-25 19:52:24 +03:00
//reverse order, since obstacles begin in bottom-right corner, while the map coordinates begin in top-left
for ( auto tile : boost : : adaptors : : reverse ( tileinfo ) )
2014-06-28 10:46:32 +03:00
{
2022-05-31 11:25:39 +02:00
//fill tiles that should be blocked with obstacles
if ( gen - > shouldBeBlocked ( tile ) )
2014-05-22 20:25:17 +03:00
{
2014-07-25 18:55:48 +03:00
//start from biggets obstacles
for ( int i = 0 ; i < possibleObstacles . size ( ) ; i + + )
{
if ( tryToPlaceObstacleHere ( tile , i ) )
break ;
}
2014-06-28 10:46:32 +03:00
}
2014-05-22 20:25:17 +03:00
}
2015-06-03 15:16:11 +02:00
//cleanup - remove unused possible tiles to make space for roads
for ( auto tile : tileinfo )
{
if ( gen - > isPossible ( tile ) )
{
gen - > setOccupied ( tile , ETileType : : FREE ) ;
}
}
2014-06-15 09:48:46 +03:00
}
2017-11-03 22:03:51 +02:00
void CRmgTemplateZone : : connectRoads ( )
2015-01-18 15:19:00 +02:00
{
2015-03-12 05:08:48 +02:00
logGlobal - > debug ( " Started building roads " ) ;
2016-02-15 12:34:37 +02:00
2015-05-25 19:00:00 +02:00
std : : set < int3 > roadNodesCopy ( roadNodes ) ;
2015-03-18 16:57:49 +02:00
std : : set < int3 > processed ;
2016-02-15 12:34:37 +02:00
2015-05-25 19:00:00 +02:00
while ( ! roadNodesCopy . empty ( ) )
2015-01-18 15:19:00 +02:00
{
2016-02-15 12:34:37 +02:00
int3 node = * roadNodesCopy . begin ( ) ;
2015-05-25 19:00:00 +02:00
roadNodesCopy . erase ( node ) ;
int3 cross ( - 1 , - 1 , - 1 ) ;
auto comparator = [ = ] ( int3 lhs , int3 rhs ) { return node . dist2dSQ ( lhs ) < node . dist2dSQ ( rhs ) ; } ;
if ( processed . size ( ) ) //connect with already existing network
2015-01-18 15:19:00 +02:00
{
2015-05-25 19:00:00 +02:00
cross = * boost : : range : : min_element ( processed , comparator ) ; //find another remaining node
2015-01-18 15:19:00 +02:00
}
2015-05-25 19:00:00 +02:00
else if ( roadNodesCopy . size ( ) ) //connect with any other unconnected node
2015-01-18 15:19:00 +02:00
{
2015-05-25 19:00:00 +02:00
cross = * boost : : range : : min_element ( roadNodesCopy , comparator ) ; //find another remaining node
}
else //no other nodes left, for example single road node in this zone
break ;
2017-08-11 19:03:05 +02:00
logGlobal - > debug ( " Building road from %s to %s " , node . toString ( ) , cross . toString ( ) ) ;
2017-11-03 22:03:51 +02:00
if ( createRoad ( node , cross ) )
2015-05-25 19:00:00 +02:00
{
processed . insert ( cross ) ; //don't draw road starting at end point which is already connected
vstd : : erase_if_present ( roadNodesCopy , cross ) ;
2015-03-12 05:08:48 +02:00
}
2016-02-15 12:34:37 +02:00
processed . insert ( node ) ;
2015-01-18 15:19:00 +02:00
}
2015-05-25 16:37:57 +02:00
2017-11-03 22:03:51 +02:00
drawRoads ( ) ;
2016-02-15 12:34:37 +02:00
logGlobal - > debug ( " Finished building roads " ) ;
2015-01-18 15:19:00 +02:00
}
2017-11-03 22:03:51 +02:00
void CRmgTemplateZone : : drawRoads ( )
2015-01-03 05:59:51 +02:00
{
std : : vector < int3 > tiles ;
2015-01-03 08:20:52 +02:00
for ( auto tile : roads )
2015-01-03 05:59:51 +02:00
{
2016-02-15 12:34:37 +02:00
if ( gen - > map - > isInTheMap ( tile ) )
2015-01-18 15:19:00 +02:00
tiles . push_back ( tile ) ;
2015-01-03 05:59:51 +02:00
}
2015-05-25 19:00:00 +02:00
for ( auto tile : roadNodes )
2015-05-25 20:02:04 +02:00
{
2016-08-09 10:12:13 +02:00
if ( gen - > getZoneID ( tile ) = = id ) //mark roads for our nodes, but not for zone guards in other zones
2015-05-25 20:02:04 +02:00
tiles . push_back ( tile ) ;
}
2015-05-25 19:00:00 +02:00
2022-05-28 15:03:50 +02:00
gen - > getEditManager ( ) - > getTerrainSelection ( ) . setSelection ( tiles ) ;
2022-06-05 08:02:58 +02:00
gen - > getEditManager ( ) - > drawRoad ( gen - > getConfig ( ) . defaultRoadType , & gen - > rand ) ;
2015-01-03 05:59:51 +02:00
}
2017-11-03 22:03:51 +02:00
bool CRmgTemplateZone : : fill ( )
2014-06-15 09:48:46 +03:00
{
2017-11-03 22:03:51 +02:00
initTerrainType ( ) ;
2022-05-31 11:25:39 +02:00
addAllPossibleObjects ( ) ;
2022-06-05 08:02:58 +02:00
//zone center should be always clear to allow other tiles to connect
initFreeTiles ( ) ;
connectLater ( ) ; //ideally this should work after fractalize, but fails
fractalize ( ) ;
placeMines ( ) ;
createRequiredObjects ( ) ;
createTreasures ( ) ;
2016-02-15 12:34:37 +02:00
2017-08-11 19:03:05 +02:00
logGlobal - > info ( " Zone %d filled successfully " , id ) ;
2014-05-22 20:25:17 +03:00
return true ;
}
2017-11-03 22:03:51 +02:00
bool CRmgTemplateZone : : findPlaceForTreasurePile ( float min_dist , int3 & pos , int value )
2014-06-07 10:09:50 +03:00
{
2014-07-25 18:10:16 +03:00
float best_distance = 0 ;
2014-06-07 10:09:50 +03:00
bool result = false ;
2022-05-31 11:25:39 +02:00
bool needsGuard = isGuardNeededForTreasure ( value ) ;
2014-12-26 17:17:39 +02:00
2017-08-10 19:17:10 +02:00
//logGlobal->info("Min dist for density %f is %d", density, min_dist);
2014-07-24 20:16:49 +03:00
for ( auto tile : possibleTiles )
2014-06-07 10:09:50 +03:00
{
2014-09-22 15:18:01 +03:00
auto dist = gen - > getNearestObjectDistance ( tile ) ;
2014-06-07 10:09:50 +03:00
2014-07-24 20:16:49 +03:00
if ( ( dist > = min_dist ) & & ( dist > best_distance ) )
2014-06-07 10:09:50 +03:00
{
bool allTilesAvailable = true ;
2017-11-03 22:03:51 +02:00
gen - > foreach_neighbour ( tile , [ this , & allTilesAvailable , needsGuard ] ( int3 neighbour )
2014-06-07 10:09:50 +03:00
{
2022-05-31 11:25:39 +02:00
if ( ! ( gen - > isPossible ( neighbour ) | | gen - > shouldBeBlocked ( neighbour ) | | gen - > getZoneID ( neighbour ) = = getId ( ) | | ( ! needsGuard & & gen - > isFree ( neighbour ) ) ) )
2014-06-07 10:09:50 +03:00
{
allTilesAvailable = false ; //all present tiles must be already blocked or ready for new objects
}
} ) ;
if ( allTilesAvailable )
{
best_distance = dist ;
pos = tile ;
result = true ;
}
}
}
if ( result )
{
2014-12-26 17:17:39 +02:00
gen - > setOccupied ( pos , ETileType : : BLOCKED ) ; //block that tile //FIXME: why?
2014-06-07 10:09:50 +03:00
}
return result ;
}
2017-11-03 22:03:51 +02:00
bool CRmgTemplateZone : : canObstacleBePlacedHere ( ObjectTemplate & temp , int3 & pos )
2014-06-28 10:46:32 +03:00
{
2014-07-08 09:59:13 +03:00
if ( ! gen - > map - > isInTheMap ( pos ) ) //blockmap may fit in the map, but botom-right corner does not
return false ;
2014-06-28 10:46:32 +03:00
auto tilesBlockedByObject = temp . getBlockedOffsets ( ) ;
for ( auto blockingTile : tilesBlockedByObject )
{
int3 t = pos + blockingTile ;
2022-05-31 11:25:39 +02:00
if ( ! gen - > map - > isInTheMap ( t ) | | ! ( gen - > isPossible ( t ) | | gen - > shouldBeBlocked ( t ) ) | | ! temp . canBePlacedAt ( gen - > map - > getTile ( t ) . terType ) )
2014-06-28 10:46:32 +03:00
{
2014-07-08 09:59:13 +03:00
return false ; //if at least one tile is not possible, object can't be placed here
2014-06-28 10:46:32 +03:00
}
}
2014-07-08 09:59:13 +03:00
return true ;
2014-06-28 10:46:32 +03:00
}
2022-05-31 11:25:39 +02:00
bool CRmgTemplateZone : : isAccessibleFromSomewhere ( ObjectTemplate & appearance , const int3 & tile ) const
2014-07-09 12:38:16 +03:00
{
2017-11-03 22:03:51 +02:00
return getAccessibleOffset ( appearance , tile ) . valid ( ) ;
2015-06-02 20:29:37 +02:00
}
2022-05-31 11:25:39 +02:00
int3 CRmgTemplateZone : : getAccessibleOffset ( ObjectTemplate & appearance , const int3 & tile ) const
2015-06-02 20:29:37 +02:00
{
2015-06-03 21:57:22 +02:00
auto tilesBlockedByObject = appearance . getBlockedOffsets ( ) ;
2015-06-02 20:29:37 +02:00
int3 ret ( - 1 , - 1 , - 1 ) ;
2014-07-09 12:38:16 +03:00
for ( int x = - 1 ; x < 2 ; x + + )
{
for ( int y = - 1 ; y < 2 ; y + + )
{
if ( x & & y ) //check only if object is visitable from another tile
{
2015-06-03 19:38:53 +02:00
int3 offset = int3 ( x , y , 0 ) - appearance . getVisitableOffset ( ) ;
2014-07-09 12:38:16 +03:00
if ( ! vstd : : contains ( tilesBlockedByObject , offset ) )
{
int3 nearbyPos = tile + offset ;
if ( gen - > map - > isInTheMap ( nearbyPos ) )
{
2022-05-31 11:25:39 +02:00
if ( appearance . isVisitableFrom ( x , y ) & & ! gen - > isBlocked ( nearbyPos ) & & tileinfo . find ( nearbyPos ) ! = tileinfo . end ( ) )
2015-06-02 20:29:37 +02:00
ret = nearbyPos ;
2014-07-09 12:38:16 +03:00
}
}
}
2017-07-12 21:01:10 +02:00
}
2014-07-09 12:38:16 +03:00
}
2015-06-02 20:29:37 +02:00
return ret ;
2014-07-09 12:38:16 +03:00
}
2017-11-03 22:03:51 +02:00
void CRmgTemplateZone : : setTemplateForObject ( CGObjectInstance * obj )
2014-05-22 20:25:17 +03:00
{
2014-05-31 13:26:37 +03:00
if ( obj - > appearance . id = = Obj : : NO_OBJ )
{
2014-06-03 22:45:18 +03:00
auto templates = VLC - > objtypeh - > getHandlerFor ( obj - > ID , obj - > subID ) - > getTemplates ( gen - > map - > getTile ( getPos ( ) ) . terType ) ;
2014-05-31 13:26:37 +03:00
if ( templates . empty ( ) )
2017-08-11 19:03:05 +02:00
throw rmgException ( boost : : to_string ( boost : : format ( " Did not find graphics for object (%d,%d) at % s " ) % obj->ID % obj->subID % pos.toString())) ;
2014-12-20 23:13:10 +02:00
2014-05-31 13:26:37 +03:00
obj - > appearance = templates . front ( ) ;
}
2014-12-20 23:13:10 +02:00
}
2022-05-31 11:25:39 +02:00
bool CRmgTemplateZone : : areAllTilesAvailable ( CGObjectInstance * obj , int3 & tile , const std : : set < int3 > & tilesBlockedByObject ) const
2014-12-20 23:13:10 +02:00
{
for ( auto blockingTile : tilesBlockedByObject )
{
int3 t = tile + blockingTile ;
2022-05-31 11:25:39 +02:00
if ( ! gen - > map - > isInTheMap ( t ) | | ! gen - > isPossible ( t ) | | gen - > getZoneID ( t ) ! = getId ( ) )
2014-12-20 23:13:10 +02:00
{
//if at least one tile is not possible, object can't be placed here
return false ;
}
}
return true ;
}
2017-11-03 22:03:51 +02:00
bool CRmgTemplateZone : : findPlaceForObject ( CGObjectInstance * obj , si32 min_dist , int3 & pos )
2014-12-20 23:13:10 +02:00
{
//we need object apperance to deduce free tile
2017-11-03 22:03:51 +02:00
setTemplateForObject ( obj ) ;
2014-05-31 13:26:37 +03:00
2014-05-22 20:25:17 +03:00
int best_distance = 0 ;
bool result = false ;
2014-06-07 11:13:59 +03:00
auto tilesBlockedByObject = obj - > getBlockedOffsets ( ) ;
for ( auto tile : tileinfo )
2014-05-22 20:25:17 +03:00
{
2014-06-07 11:13:59 +03:00
//object must be accessible from at least one surounding tile
2022-05-31 11:25:39 +02:00
if ( ! isAccessibleFromSomewhere ( obj - > appearance , tile ) )
2014-06-07 11:13:59 +03:00
continue ;
2014-05-31 15:11:20 +03:00
auto ti = gen - > getTile ( tile ) ;
2014-05-22 20:25:17 +03:00
auto dist = ti . getNearestObjectDistance ( ) ;
//avoid borders
2014-05-30 22:23:41 +03:00
if ( gen - > isPossible ( tile ) & & ( dist > = min_dist ) & & ( dist > best_distance ) )
2014-05-22 20:25:17 +03:00
{
2017-11-03 22:03:51 +02:00
if ( areAllTilesAvailable ( obj , tile , tilesBlockedByObject ) )
2014-05-31 11:56:14 +03:00
{
2020-10-01 10:38:06 +02:00
best_distance = static_cast < int > ( dist ) ;
2014-05-31 11:56:14 +03:00
pos = tile ;
result = true ;
}
2014-05-22 20:25:17 +03:00
}
}
2014-05-31 11:56:14 +03:00
if ( result )
{
gen - > setOccupied ( pos , ETileType : : BLOCKED ) ; //block that tile
}
2014-05-22 20:25:17 +03:00
return result ;
}
2017-11-03 22:03:51 +02:00
void CRmgTemplateZone : : checkAndPlaceObject ( CGObjectInstance * object , const int3 & pos )
2014-05-22 20:25:17 +03:00
{
2014-05-23 20:14:20 +03:00
if ( ! gen - > map - > isInTheMap ( pos ) )
2017-08-11 19:03:05 +02:00
throw rmgException ( boost : : to_string ( boost : : format ( " Position of object %d at %s is outside the map " ) % object - > id % pos . toString ( ) ) ) ;
2014-05-22 20:25:17 +03:00
object - > pos = pos ;
2014-05-23 18:12:31 +03:00
2014-05-23 20:14:20 +03:00
if ( object - > isVisitable ( ) & & ! gen - > map - > isInTheMap ( object - > visitablePos ( ) ) )
2017-08-11 19:03:05 +02:00
throw rmgException ( boost : : to_string ( boost : : format ( " Visitable tile %s of object %d at %s is outside the map " ) % object - > visitablePos ( ) . toString ( ) % object - > id % object - > pos . toString ( ) ) ) ;
2014-05-23 18:12:31 +03:00
for ( auto tile : object - > getBlockedPos ( ) )
{
if ( ! gen - > map - > isInTheMap ( tile ) )
2017-08-11 19:03:05 +02:00
throw rmgException ( boost : : to_string ( boost : : format ( " Tile %s of object %d at %s is outside the map " ) % tile . toString ( ) % object - > id % object - > pos . toString ( ) ) ) ;
2014-05-23 18:12:31 +03:00
}
2014-05-31 13:26:37 +03:00
if ( object - > appearance . id = = Obj : : NO_OBJ )
{
2014-07-03 18:24:28 +03:00
auto terrainType = gen - > map - > getTile ( pos ) . terType ;
2022-06-20 16:39:50 +02:00
auto h = VLC - > objtypeh - > getHandlerFor ( object - > ID , object - > subID ) ;
auto templates = h - > getTemplates ( terrainType ) ;
2014-05-31 13:26:37 +03:00
if ( templates . empty ( ) )
2017-08-11 19:03:05 +02:00
throw rmgException ( boost : : to_string ( boost : : format ( " Did not find graphics for object (%d,%d) at % s ( terrain % d ) " ) % object->ID % object->subID % pos.toString() % terrainType)) ;
2016-02-15 12:34:37 +02:00
2014-05-31 13:26:37 +03:00
object - > appearance = templates . front ( ) ;
}
2022-05-28 15:03:50 +02:00
gen - > getEditManager ( ) - > insertObject ( object ) ;
2014-05-23 18:12:31 +03:00
}
2017-11-03 22:03:51 +02:00
void CRmgTemplateZone : : placeObject ( CGObjectInstance * object , const int3 & pos , bool updateDistance )
2014-05-23 18:12:31 +03:00
{
2017-11-03 22:03:51 +02:00
checkAndPlaceObject ( object , pos ) ;
2014-05-23 18:12:31 +03:00
2014-05-22 20:25:17 +03:00
auto points = object - > getBlockedPos ( ) ;
if ( object - > isVisitable ( ) )
2014-05-29 13:42:05 +03:00
points . insert ( pos + object - > getVisitableOffset ( ) ) ;
points . insert ( pos ) ;
2014-06-01 15:10:44 +03:00
for ( auto p : points )
2016-02-15 12:34:37 +02:00
{
2014-06-01 15:10:44 +03:00
if ( gen - > map - > isInTheMap ( p ) )
2014-05-22 20:25:17 +03:00
{
2014-06-01 15:10:44 +03:00
gen - > setOccupied ( p , ETileType : : USED ) ;
2014-05-22 20:25:17 +03:00
}
}
2014-07-24 20:16:49 +03:00
if ( updateDistance )
2017-11-03 22:03:51 +02:00
updateDistances ( pos ) ;
2016-02-15 12:34:37 +02:00
2015-01-18 15:19:00 +02:00
switch ( object - > ID )
{
case Obj : : TOWN :
case Obj : : RANDOM_TOWN :
case Obj : : MONOLITH_TWO_WAY :
case Obj : : MONOLITH_ONE_WAY_ENTRANCE :
case Obj : : MONOLITH_ONE_WAY_EXIT :
case Obj : : SUBTERRANEAN_GATE :
2022-06-05 08:02:58 +02:00
case Obj : : SHIPYARD :
2015-01-18 15:19:00 +02:00
{
2015-05-25 19:00:00 +02:00
addRoadNode ( object - > visitablePos ( ) ) ;
2015-01-18 15:19:00 +02:00
}
break ;
2016-02-15 12:34:37 +02:00
2015-01-18 15:19:00 +02:00
default :
break ;
2016-02-15 12:34:37 +02:00
}
2014-05-22 20:25:17 +03:00
}
2017-11-03 22:03:51 +02:00
void CRmgTemplateZone : : updateDistances ( const int3 & pos )
2016-12-20 12:58:39 +02:00
{
for ( auto tile : possibleTiles ) //don't need to mark distance for not possible tiles
{
ui32 d = pos . dist2dSQ ( tile ) ; //optimization, only relative distance is interesting
2020-10-01 10:38:06 +02:00
gen - > setNearestObjectDistance ( tile , std : : min ( ( float ) d , gen - > getNearestObjectDistance ( tile ) ) ) ;
2016-12-20 12:58:39 +02:00
}
}
2017-11-03 22:03:51 +02:00
void CRmgTemplateZone : : placeAndGuardObject ( CGObjectInstance * object , const int3 & pos , si32 str , bool zoneGuard )
2014-07-03 18:24:28 +03:00
{
2017-11-03 22:03:51 +02:00
placeObject ( object , pos ) ;
guardObject ( object , str , zoneGuard ) ;
2014-07-03 18:24:28 +03:00
}
2017-11-03 22:03:51 +02:00
void CRmgTemplateZone : : placeSubterraneanGate ( int3 pos , si32 guardStrength )
2015-06-04 09:02:56 +02:00
{
2015-11-14 15:50:29 +02:00
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : SUBTERRANEAN_GATE , 0 ) ;
auto gate = factory - > create ( ObjectTemplate ( ) ) ;
2017-11-03 22:03:51 +02:00
placeObject ( gate , pos , true ) ;
addToConnectLater ( getAccessibleOffset ( gate - > appearance , pos ) ) ; //guard will be placed on accessibleOffset
guardObject ( gate , guardStrength , true ) ;
2015-06-04 09:02:56 +02:00
}
2017-11-03 22:03:51 +02:00
std : : vector < int3 > CRmgTemplateZone : : getAccessibleOffsets ( const CGObjectInstance * object )
2014-05-22 20:25:17 +03:00
{
2014-06-15 20:56:58 +03:00
//get all tiles from which this object can be accessed
2014-05-23 20:14:20 +03:00
int3 visitable = object - > visitablePos ( ) ;
2014-05-22 20:25:17 +03:00
std : : vector < int3 > tiles ;
2014-06-15 10:39:30 +03:00
auto tilesBlockedByObject = object - > getBlockedPos ( ) ; //absolue value, as object is already placed
2016-02-15 12:34:37 +02:00
gen - > foreach_neighbour ( visitable , [ & ] ( int3 & pos )
2014-05-22 20:25:17 +03:00
{
2015-06-02 20:29:37 +02:00
if ( gen - > isPossible ( pos ) | | gen - > isFree ( pos ) )
2014-05-22 20:25:17 +03:00
{
2014-06-15 10:39:30 +03:00
if ( ! vstd : : contains ( tilesBlockedByObject , pos ) )
{
if ( object - > appearance . isVisitableFrom ( pos . x - visitable . x , pos . y - visitable . y ) & & ! gen - > isBlocked ( pos ) ) //TODO: refactor - info about visitability from absolute coordinates
{
tiles . push_back ( pos ) ;
}
}
2014-05-30 22:23:41 +03:00
} ;
} ) ;
2014-06-15 20:56:58 +03:00
return tiles ;
}
2022-05-31 11:25:39 +02:00
bool CRmgTemplateZone : : isGuardNeededForTreasure ( int value )
{
return getType ( ) ! = ETemplateZoneType : : WATER & & value > minGuardedValue ;
}
2017-11-03 22:03:51 +02:00
bool CRmgTemplateZone : : guardObject ( CGObjectInstance * object , si32 str , bool zoneGuard , bool addToFreePaths )
2014-06-15 20:56:58 +03:00
{
2017-11-03 22:03:51 +02:00
std : : vector < int3 > tiles = getAccessibleOffsets ( object ) ;
2014-06-15 20:56:58 +03:00
2015-06-02 20:29:37 +02:00
int3 guardTile ( - 1 , - 1 , - 1 ) ;
2014-06-15 20:56:58 +03:00
2015-06-02 20:29:37 +02:00
if ( tiles . size ( ) )
2014-06-15 20:56:58 +03:00
{
2015-06-03 21:57:22 +02:00
//guardTile = tiles.front();
2017-11-03 22:03:51 +02:00
guardTile = getAccessibleOffset ( object - > appearance , object - > pos ) ;
2017-08-11 19:03:05 +02:00
logGlobal - > trace ( " Guard object at %s " , object - > pos . toString ( ) ) ;
2014-06-15 20:56:58 +03:00
}
2015-06-02 20:29:37 +02:00
else
2014-06-15 20:56:58 +03:00
{
2017-08-11 19:03:05 +02:00
logGlobal - > error ( " Failed to guard object at %s " , object - > pos . toString ( ) ) ;
2014-05-22 20:25:17 +03:00
return false ;
}
2017-11-03 22:03:51 +02:00
if ( addMonster ( guardTile , str , false , zoneGuard ) ) //do not place obstacles around unguarded object
2014-06-04 21:59:01 +03:00
{
for ( auto pos : tiles )
2014-06-15 20:56:58 +03:00
{
2022-05-31 11:25:39 +02:00
if ( gen - > isPossible ( pos ) & & gen - > getZoneID ( pos ) = = id )
2014-06-15 20:56:58 +03:00
gen - > setOccupied ( pos , ETileType : : BLOCKED ) ;
}
2016-02-15 12:34:37 +02:00
gen - > foreach_neighbour ( guardTile , [ & ] ( int3 & pos )
2014-07-23 12:42:05 +03:00
{
2022-05-31 11:25:39 +02:00
if ( gen - > isPossible ( pos ) & & gen - > getZoneID ( pos ) = = id )
gen - > setOccupied ( pos , ETileType : : FREE ) ;
2014-07-23 12:42:05 +03:00
} ) ;
2014-06-04 21:59:01 +03:00
2014-06-15 20:56:58 +03:00
gen - > setOccupied ( guardTile , ETileType : : USED ) ;
2014-06-04 21:59:01 +03:00
}
2014-07-01 08:07:40 +03:00
else //allow no guard or other object in front of this object
{
for ( auto tile : tiles )
2014-07-23 12:42:05 +03:00
if ( gen - > isPossible ( tile ) )
2022-05-31 11:25:39 +02:00
gen - > setOccupied ( tile , ETileType : : FREE ) ;
2014-07-01 08:07:40 +03:00
}
2014-06-01 15:10:44 +03:00
2014-05-22 20:25:17 +03:00
return true ;
}
2014-06-05 21:19:42 +03:00
2017-11-03 22:03:51 +02:00
ObjectInfo CRmgTemplateZone : : getRandomObject ( CTreasurePileInfo & info , ui32 desiredValue , ui32 maxValue , ui32 currentValue )
2014-06-05 21:19:42 +03:00
{
2014-07-09 15:27:12 +03:00
//int objectsVisitableFromBottom = 0; //for debug
2016-08-11 10:49:19 +02:00
std : : vector < std : : pair < ui32 , ObjectInfo * > > thresholds ; //handle complex object via pointer
2014-06-05 21:19:42 +03:00
ui32 total = 0 ;
2014-07-29 16:58:54 +03:00
//calculate actual treasure value range based on remaining value
2014-12-26 20:35:45 +02:00
ui32 maxVal = desiredValue - currentValue ;
2020-10-01 10:38:06 +02:00
ui32 minValue = static_cast < ui32 > ( 0.25f * ( desiredValue - currentValue ) ) ;
2014-06-05 21:19:42 +03:00
//roulette wheel
2014-07-24 20:16:49 +03:00
for ( ObjectInfo & oi : possibleObjects ) //copy constructor turned out to be costly
2014-06-05 21:19:42 +03:00
{
2016-06-05 10:18:10 +02:00
if ( oi . value > maxVal )
break ; //this assumes values are sorted in ascending order
if ( oi . value > = minValue & & oi . maxPerZone > 0 )
2014-06-05 21:19:42 +03:00
{
2014-07-09 12:38:16 +03:00
int3 newVisitableOffset = oi . templ . getVisitableOffset ( ) ; //visitablePos assumes object will be shifter by visitableOffset
int3 newVisitablePos = info . nextTreasurePos ;
if ( ! oi . templ . isVisitableFromTop ( ) )
{
2014-07-09 15:27:12 +03:00
//objectsVisitableFromBottom++;
2014-07-09 12:38:16 +03:00
//there must be free tiles under object
2014-07-18 08:40:27 +03:00
auto blockedOffsets = oi . templ . getBlockedOffsets ( ) ;
2022-05-31 11:25:39 +02:00
if ( ! isAccessibleFromSomewhere ( oi . templ , newVisitablePos ) )
2014-07-09 12:38:16 +03:00
continue ;
}
2014-07-09 10:22:50 +03:00
2014-07-09 17:39:17 +03:00
//NOTE: y coordinate grows downwards
2014-07-09 10:22:50 +03:00
if ( info . visitableFromBottomPositions . size ( ) + info . visitableFromTopPositions . size ( ) ) //do not try to match first object in zone
2014-07-08 21:13:51 +03:00
{
bool fitsHere = false ;
2014-07-09 10:22:50 +03:00
2014-07-09 12:38:16 +03:00
if ( oi . templ . isVisitableFromTop ( ) ) //new can be accessed from any direction
2014-07-08 21:13:51 +03:00
{
2014-07-09 10:22:50 +03:00
for ( auto tile : info . visitableFromTopPositions )
{
2014-07-09 12:38:16 +03:00
int3 actualTile = tile + newVisitableOffset ;
if ( newVisitablePos . areNeighbours ( actualTile ) ) //we access other removable object from any position
2014-07-09 10:22:50 +03:00
{
fitsHere = true ;
break ;
}
}
for ( auto tile : info . visitableFromBottomPositions )
2014-07-08 21:13:51 +03:00
{
2014-07-09 12:38:16 +03:00
int3 actualTile = tile + newVisitableOffset ;
2014-07-09 17:39:17 +03:00
if ( newVisitablePos . areNeighbours ( actualTile ) & & newVisitablePos . y > = actualTile . y ) //we access existing static object from side or bottom only
2014-07-08 21:13:51 +03:00
{
fitsHere = true ;
break ;
}
}
}
2014-07-09 12:38:16 +03:00
else //if new object is not visitable from top, it must be accessible from below or side
2014-07-08 21:13:51 +03:00
{
for ( auto tile : info . visitableFromTopPositions )
{
2014-07-09 12:38:16 +03:00
int3 actualTile = tile + newVisitableOffset ;
2014-07-09 17:39:17 +03:00
if ( newVisitablePos . areNeighbours ( actualTile ) & & newVisitablePos . y < = actualTile . y ) //we access existing removable object from top or side only
2014-07-09 10:22:50 +03:00
{
fitsHere = true ;
break ;
}
}
for ( auto tile : info . visitableFromBottomPositions )
{
2014-07-09 12:38:16 +03:00
int3 actualTile = tile + newVisitableOffset ;
if ( newVisitablePos . areNeighbours ( actualTile ) & & newVisitablePos . y = = actualTile . y ) //we access other static object from side only
2014-07-08 21:13:51 +03:00
{
fitsHere = true ;
break ;
}
}
}
2014-07-09 12:38:16 +03:00
if ( ! fitsHere )
continue ;
2014-07-08 21:13:51 +03:00
}
2014-07-09 10:22:50 +03:00
//now check blockmap, including our already reserved pile area
2014-07-08 21:13:51 +03:00
bool fitsBlockmap = true ;
std : : set < int3 > blockedOffsets = oi . templ . getBlockedOffsets ( ) ;
2014-07-09 12:38:16 +03:00
blockedOffsets . insert ( newVisitableOffset ) ;
2014-07-08 21:13:51 +03:00
for ( auto blockingTile : blockedOffsets )
{
2014-07-09 12:38:16 +03:00
int3 t = info . nextTreasurePos + newVisitableOffset + blockingTile ;
2014-07-08 21:13:51 +03:00
if ( ! gen - > map - > isInTheMap ( t ) | | vstd : : contains ( info . occupiedPositions , t ) )
{
fitsBlockmap = false ; //if at least one tile is not possible, object can't be placed here
break ;
}
if ( ! ( gen - > isPossible ( t ) | | gen - > isBlocked ( t ) ) ) //blocked tiles of object may cover blocked tiles, but not used or free tiles
{
fitsBlockmap = false ;
break ;
}
}
if ( ! fitsBlockmap )
continue ;
2014-06-05 21:19:42 +03:00
total + = oi . probability ;
2016-09-08 13:24:28 +02:00
2016-08-11 10:49:19 +02:00
thresholds . push_back ( std : : make_pair ( total , & oi ) ) ;
2014-06-05 21:19:42 +03:00
}
}
2022-06-05 08:02:58 +02:00
if ( thresholds . empty ( ) )
2014-06-05 21:19:42 +03:00
{
2014-07-29 16:58:54 +03:00
ObjectInfo oi ;
//Generate pandora Box with gold if the value is extremely high
2022-06-05 08:02:58 +02:00
if ( minValue > gen - > getConfig ( ) . treasureValueLimit ) //we don't have object valuable enough
2014-06-05 21:19:42 +03:00
{
2014-06-07 23:27:36 +03:00
oi . generateObject = [ minValue ] ( ) - > CGObjectInstance *
{
2015-11-14 15:50:29 +02:00
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : PANDORAS_BOX , 0 ) ;
auto obj = ( CGPandoraBox * ) factory - > create ( ObjectTemplate ( ) ) ;
2014-06-07 23:27:36 +03:00
obj - > resources [ Res : : GOLD ] = minValue ;
return obj ;
} ;
2014-07-08 21:13:51 +03:00
oi . setTemplate ( Obj : : PANDORAS_BOX , 0 , terrainType ) ;
2014-06-07 23:27:36 +03:00
oi . value = minValue ;
oi . probability = 0 ;
}
2014-07-29 16:58:54 +03:00
else //generate empty object with 0 value if the value if we can't spawn anything
2014-06-07 23:27:36 +03:00
{
2017-11-03 22:03:51 +02:00
oi . generateObject = [ ] ( ) - > CGObjectInstance *
2014-06-07 23:27:36 +03:00
{
return nullptr ;
} ;
2014-07-08 21:13:51 +03:00
oi . setTemplate ( Obj : : PANDORAS_BOX , 0 , terrainType ) ; //TODO: null template or something? should be never used, but hell knows
2014-07-29 16:58:54 +03:00
oi . value = 0 ; // this field is checked to determine no object
2014-06-07 23:27:36 +03:00
oi . probability = 0 ;
}
return oi ;
2014-06-05 21:19:42 +03:00
}
2014-07-29 16:58:54 +03:00
else
2014-06-05 21:19:42 +03:00
{
2014-07-29 16:58:54 +03:00
int r = gen - > rand . nextInt ( 1 , total ) ;
2016-08-08 22:16:31 +02:00
//binary search = fastest
auto it = std : : lower_bound ( thresholds . begin ( ) , thresholds . end ( ) , r ,
2016-08-11 10:49:19 +02:00
[ ] ( const std : : pair < ui32 , ObjectInfo * > & rhs , const int lhs ) - > bool
2014-07-29 16:58:54 +03:00
{
2020-10-01 10:38:06 +02:00
return ( int ) rhs . first < lhs ;
2016-08-08 22:16:31 +02:00
} ) ;
2016-08-11 10:49:19 +02:00
return * ( it - > second ) ;
2014-06-05 21:19:42 +03:00
}
}
2017-11-03 22:03:51 +02:00
void CRmgTemplateZone : : addAllPossibleObjects ( )
2014-06-05 21:19:42 +03:00
{
ObjectInfo oi ;
2020-10-01 10:38:06 +02:00
int numZones = static_cast < int > ( gen - > getZones ( ) . size ( ) ) ;
2014-07-23 19:02:17 +03:00
2015-02-28 23:37:04 +02:00
for ( auto primaryID : VLC - > objtypeh - > knownObjects ( ) )
{
for ( auto secondaryID : VLC - > objtypeh - > knownSubObjects ( primaryID ) )
{
auto handler = VLC - > objtypeh - > getHandlerFor ( primaryID , secondaryID ) ;
2014-07-05 20:35:46 +03:00
if ( ! handler - > isStaticObject ( ) & & handler - > getRMGInfo ( ) . value )
{
for ( auto temp : handler - > getTemplates ( ) )
{
if ( temp . canBePlacedAt ( terrainType ) )
{
2017-11-03 22:03:51 +02:00
oi . generateObject = [ temp ] ( ) - > CGObjectInstance *
2014-07-05 20:35:46 +03:00
{
return VLC - > objtypeh - > getHandlerFor ( temp . id , temp . subid ) - > create ( temp ) ;
} ;
2014-07-23 19:02:17 +03:00
auto rmgInfo = handler - > getRMGInfo ( ) ;
oi . value = rmgInfo . value ;
oi . probability = rmgInfo . rarity ;
2014-07-08 21:13:51 +03:00
oi . templ = temp ;
2014-07-23 19:02:17 +03:00
oi . maxPerZone = rmgInfo . zoneLimit ;
2015-02-28 23:37:04 +02:00
vstd : : amin ( oi . maxPerZone , rmgInfo . mapLimit / numZones ) ; //simple, but should distribute objects evenly on large maps
possibleObjects . push_back ( oi ) ;
2014-07-05 20:35:46 +03:00
}
}
}
2015-02-28 23:37:04 +02:00
}
2014-07-06 23:14:37 +03:00
}
2022-05-31 11:25:39 +02:00
if ( type = = ETemplateZoneType : : WATER )
return ;
2014-07-06 23:14:37 +03:00
2014-07-25 11:44:17 +03:00
//prisons
//levels 1, 5, 10, 20, 30
2022-06-05 08:02:58 +02:00
static int prisonsLevels = std : : min ( gen - > getConfig ( ) . prisonExperience . size ( ) , gen - > getConfig ( ) . prisonValues . size ( ) ) ;
for ( int i = 0 ; i < prisonsLevels ; i + + )
2014-07-25 11:44:17 +03:00
{
2017-11-03 22:03:51 +02:00
oi . generateObject = [ i , this ] ( ) - > CGObjectInstance *
2014-07-25 11:44:17 +03:00
{
std : : vector < ui32 > possibleHeroes ;
2022-06-05 08:02:58 +02:00
for ( int j = 0 ; j < gen - > map - > allowedHeroes . size ( ) ; j + + )
2014-07-25 11:44:17 +03:00
{
2022-06-05 08:02:58 +02:00
if ( gen - > map - > allowedHeroes [ j ] )
2014-07-25 11:44:17 +03:00
possibleHeroes . push_back ( j ) ;
}
auto hid = * RandomGeneratorUtil : : nextItem ( possibleHeroes , gen - > rand ) ;
2015-11-14 15:50:29 +02:00
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : PRISON , 0 ) ;
auto obj = ( CGHeroInstance * ) factory - > create ( ObjectTemplate ( ) ) ;
2014-09-21 16:59:35 +03:00
obj - > subID = hid ; //will be initialized later
2022-06-05 08:02:58 +02:00
obj - > exp = gen - > getConfig ( ) . prisonExperience [ i ] ;
2014-07-25 11:44:17 +03:00
obj - > setOwner ( PlayerColor : : NEUTRAL ) ;
gen - > map - > allowedHeroes [ hid ] = false ; //ban this hero
gen - > decreasePrisonsRemaining ( ) ;
2014-09-21 16:59:35 +03:00
obj - > appearance = VLC - > objtypeh - > getHandlerFor ( Obj : : PRISON , 0 ) - > getTemplates ( terrainType ) . front ( ) ; //can't init template with hero subID
2014-07-25 11:44:17 +03:00
return obj ;
} ;
2015-02-28 23:37:04 +02:00
oi . setTemplate ( Obj : : PRISON , 0 , terrainType ) ;
2022-06-05 08:02:58 +02:00
oi . value = gen - > getConfig ( ) . prisonValues [ i ] ;
2014-07-25 11:44:17 +03:00
oi . probability = 30 ;
oi . maxPerZone = gen - > getPrisonsRemaning ( ) / 5 ; //probably not perfect, but we can't generate more prisons than hereos.
2015-02-28 23:37:04 +02:00
possibleObjects . push_back ( oi ) ;
2014-07-25 11:44:17 +03:00
}
2014-07-15 23:33:51 +03:00
//all following objects are unlimited
oi . maxPerZone = std : : numeric_limits < ui32 > ( ) . max ( ) ;
2022-05-31 11:25:39 +02:00
std : : vector < CCreature * > creatures ; //native creatures for this zone
for ( auto cre : VLC - > creh - > objects )
{
if ( ! cre - > special & & cre - > faction = = townType )
{
creatures . push_back ( cre ) ;
}
}
2014-07-15 23:33:51 +03:00
2014-07-06 23:14:37 +03:00
//dwellings
2022-04-05 19:41:56 +02:00
auto dwellingTypes = { Obj : : CREATURE_GENERATOR1 , Obj : : CREATURE_GENERATOR4 } ;
2014-07-06 23:14:37 +03:00
2022-04-05 19:41:56 +02:00
for ( auto dwellingType : dwellingTypes )
2014-07-06 23:14:37 +03:00
{
2022-04-05 19:41:56 +02:00
auto subObjects = VLC - > objtypeh - > knownSubObjects ( dwellingType ) ;
2014-07-06 23:14:37 +03:00
2022-04-05 19:41:56 +02:00
if ( dwellingType = = Obj : : CREATURE_GENERATOR1 )
2014-07-06 23:14:37 +03:00
{
2022-04-05 19:41:56 +02:00
//don't spawn original "neutral" dwellings that got replaced by Conflux dwellings in AB
static int elementalConfluxROE [ ] = { 7 , 13 , 16 , 47 } ;
for ( int i = 0 ; i < 4 ; i + + )
vstd : : erase_if_present ( subObjects , elementalConfluxROE [ i ] ) ;
}
2014-07-06 23:14:37 +03:00
2022-04-05 19:41:56 +02:00
for ( auto secondaryID : subObjects )
{
auto dwellingHandler = dynamic_cast < const CDwellingInstanceConstructor * > ( VLC - > objtypeh - > getHandlerFor ( dwellingType , secondaryID ) . get ( ) ) ;
auto creatures = dwellingHandler - > getProducedCreatures ( ) ;
if ( creatures . empty ( ) )
continue ;
auto cre = creatures . front ( ) ;
if ( cre - > faction = = townType )
2014-07-06 23:14:37 +03:00
{
2022-04-05 19:41:56 +02:00
float nativeZonesCount = static_cast < float > ( gen - > getZoneCount ( cre - > faction ) ) ;
oi . value = static_cast < ui32 > ( cre - > AIValue * cre - > growth * ( 1 + ( nativeZonesCount / gen - > getTotalZoneCount ( ) ) + ( nativeZonesCount / 2 ) ) ) ;
oi . probability = 40 ;
for ( auto tmplate : dwellingHandler - > getTemplates ( ) )
2014-07-06 23:14:37 +03:00
{
2022-04-05 19:41:56 +02:00
if ( tmplate . canBePlacedAt ( terrainType ) )
2014-07-06 23:14:37 +03:00
{
2022-04-05 19:41:56 +02:00
oi . generateObject = [ tmplate , secondaryID , dwellingType ] ( ) - > CGObjectInstance *
{
auto obj = VLC - > objtypeh - > getHandlerFor ( dwellingType , secondaryID ) - > create ( tmplate ) ;
obj - > tempOwner = PlayerColor : : NEUTRAL ;
return obj ;
} ;
2014-07-06 23:14:37 +03:00
2022-04-05 19:41:56 +02:00
oi . templ = tmplate ;
possibleObjects . push_back ( oi ) ;
}
2014-07-06 23:14:37 +03:00
}
}
}
}
2014-06-05 21:19:42 +03:00
2022-06-05 08:02:58 +02:00
for ( int i = 0 ; i < gen - > getConfig ( ) . scrollValues . size ( ) ; i + + )
2014-06-05 21:19:42 +03:00
{
2017-11-03 22:03:51 +02:00
oi . generateObject = [ i , this ] ( ) - > CGObjectInstance *
2014-06-05 21:19:42 +03:00
{
2015-11-14 15:50:29 +02:00
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : SPELL_SCROLL , 0 ) ;
auto obj = ( CGArtifact * ) factory - > create ( ObjectTemplate ( ) ) ;
2014-06-05 21:19:42 +03:00
std : : vector < SpellID > out ;
2015-08-12 16:40:08 +02:00
for ( auto spell : VLC - > spellh - > objects ) //spellh size appears to be greater (?)
2014-06-05 21:19:42 +03:00
{
2015-08-12 16:40:08 +02:00
if ( gen - > isAllowedSpell ( spell - > id ) & & spell - > level = = i + 1 )
2014-06-05 21:19:42 +03:00
{
out . push_back ( spell - > id ) ;
}
}
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
auto a = CArtifactInstance : : createScroll ( * RandomGeneratorUtil : : nextItem ( out , gen - > rand ) ) ;
2014-06-05 21:19:42 +03:00
obj - > storedArtifact = a ;
return obj ;
} ;
2015-02-28 23:37:04 +02:00
oi . setTemplate ( Obj : : SPELL_SCROLL , 0 , terrainType ) ;
2022-06-05 08:02:58 +02:00
oi . value = gen - > getConfig ( ) . scrollValues [ i ] ;
2014-06-05 21:19:42 +03:00
oi . probability = 30 ;
2015-02-28 23:37:04 +02:00
possibleObjects . push_back ( oi ) ;
2014-06-05 21:19:42 +03:00
}
2014-07-06 08:15:52 +03:00
//pandora box with gold
2022-06-05 08:02:58 +02:00
for ( int i = 1 ; i < 5 ; i + + )
2014-07-06 08:15:52 +03:00
{
oi . generateObject = [ i ] ( ) - > CGObjectInstance *
{
2015-11-14 15:50:29 +02:00
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : PANDORAS_BOX , 0 ) ;
auto obj = ( CGPandoraBox * ) factory - > create ( ObjectTemplate ( ) ) ;
2014-07-06 08:15:52 +03:00
obj - > resources [ Res : : GOLD ] = i * 5000 ;
return obj ;
} ;
2015-02-28 23:37:04 +02:00
oi . setTemplate ( Obj : : PANDORAS_BOX , 0 , terrainType ) ;
2022-06-05 08:02:58 +02:00
oi . value = i * gen - > getConfig ( ) . pandoraMultiplierGold ;
2014-07-06 08:15:52 +03:00
oi . probability = 5 ;
2015-02-28 23:37:04 +02:00
possibleObjects . push_back ( oi ) ;
2014-07-06 08:15:52 +03:00
}
//pandora box with experience
for ( int i = 1 ; i < 5 ; i + + )
{
oi . generateObject = [ i ] ( ) - > CGObjectInstance *
{
2015-11-14 15:50:29 +02:00
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : PANDORAS_BOX , 0 ) ;
auto obj = ( CGPandoraBox * ) factory - > create ( ObjectTemplate ( ) ) ;
2014-07-06 08:15:52 +03:00
obj - > gainedExp = i * 5000 ;
return obj ;
} ;
2015-02-28 23:37:04 +02:00
oi . setTemplate ( Obj : : PANDORAS_BOX , 0 , terrainType ) ;
2022-06-05 08:02:58 +02:00
oi . value = i * gen - > getConfig ( ) . pandoraMultiplierExperience ;
2014-07-06 08:15:52 +03:00
oi . probability = 20 ;
2015-02-28 23:37:04 +02:00
possibleObjects . push_back ( oi ) ;
2014-07-06 08:15:52 +03:00
}
//pandora box with creatures
2022-06-05 08:02:58 +02:00
const std : : vector < int > & tierValues = gen - > getConfig ( ) . pandoraCreatureValues ;
2014-07-06 08:15:52 +03:00
2022-06-05 08:02:58 +02:00
auto creatureToCount = [ & tierValues ] ( CCreature * creature ) - > int
2015-02-28 22:14:45 +02:00
{
2017-05-27 14:57:45 +02:00
if ( ! creature - > AIValue ) //bug #2681
return 0 ; //this box won't be generated
2022-06-05 08:02:58 +02:00
int actualTier = creature - > level > tierValues . size ( ) ?
tierValues . size ( ) - 1 :
creature - > level - 1 ;
2015-03-11 20:21:58 +02:00
float creaturesAmount = ( ( float ) tierValues [ actualTier ] ) / creature - > AIValue ;
2015-02-28 22:14:45 +02:00
if ( creaturesAmount < = 5 )
{
creaturesAmount = boost : : math : : round ( creaturesAmount ) ; //allow single monsters
if ( creaturesAmount < 1 )
return 0 ;
}
else if ( creaturesAmount < = 12 )
{
( creaturesAmount / = 2 ) * = 2 ;
}
else if ( creaturesAmount < = 50 )
{
creaturesAmount = boost : : math : : round ( creaturesAmount / 5 ) * 5 ;
}
else
{
creaturesAmount = boost : : math : : round ( creaturesAmount / 10 ) * 10 ;
}
2020-10-01 10:38:06 +02:00
return static_cast < int > ( creaturesAmount ) ;
2015-02-28 22:14:45 +02:00
} ;
2015-03-11 20:21:58 +02:00
for ( auto creature : creatures )
2014-07-06 08:15:52 +03:00
{
2015-03-11 20:21:58 +02:00
int creaturesAmount = creatureToCount ( creature ) ;
if ( ! creaturesAmount )
continue ;
2014-07-06 08:15:52 +03:00
2015-03-11 20:21:58 +02:00
oi . generateObject = [ creature , creaturesAmount ] ( ) - > CGObjectInstance *
{
2015-11-14 15:50:29 +02:00
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : PANDORAS_BOX , 0 ) ;
auto obj = ( CGPandoraBox * ) factory - > create ( ObjectTemplate ( ) ) ;
2015-03-11 20:21:58 +02:00
auto stack = new CStackInstance ( creature , creaturesAmount ) ;
obj - > creatures . putStack ( SlotID ( 0 ) , stack ) ;
return obj ;
} ;
oi . setTemplate ( Obj : : PANDORAS_BOX , 0 , terrainType ) ;
2020-10-01 10:38:06 +02:00
oi . value = static_cast < ui32 > ( ( 2 * ( creature - > AIValue ) * creaturesAmount * ( 1 + ( float ) ( gen - > getZoneCount ( creature - > faction ) ) / gen - > getTotalZoneCount ( ) ) ) / 3 ) ;
2015-03-11 20:21:58 +02:00
oi . probability = 3 ;
possibleObjects . push_back ( oi ) ;
2014-07-06 08:15:52 +03:00
}
//Pandora with 12 spells of certain level
for ( int i = 1 ; i < = GameConstants : : SPELL_LEVELS ; i + + )
{
2017-11-03 22:03:51 +02:00
oi . generateObject = [ i , this ] ( ) - > CGObjectInstance *
2014-07-06 08:15:52 +03:00
{
2015-11-14 15:50:29 +02:00
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : PANDORAS_BOX , 0 ) ;
auto obj = ( CGPandoraBox * ) factory - > create ( ObjectTemplate ( ) ) ;
2014-07-06 08:15:52 +03:00
std : : vector < CSpell * > spells ;
for ( auto spell : VLC - > spellh - > objects )
{
2015-08-12 16:40:08 +02:00
if ( gen - > isAllowedSpell ( spell - > id ) & & spell - > level = = i )
2014-07-06 08:15:52 +03:00
spells . push_back ( spell ) ;
}
RandomGeneratorUtil : : randomShuffle ( spells , gen - > rand ) ;
2020-10-01 10:38:06 +02:00
for ( int j = 0 ; j < std : : min ( 12 , ( int ) spells . size ( ) ) ; j + + )
2014-07-06 08:15:52 +03:00
{
2015-02-28 23:37:04 +02:00
obj - > spells . push_back ( spells [ j ] - > id ) ;
2014-07-06 08:15:52 +03:00
}
return obj ;
} ;
2015-02-28 23:37:04 +02:00
oi . setTemplate ( Obj : : PANDORAS_BOX , 0 , terrainType ) ;
2022-06-05 08:02:58 +02:00
oi . value = ( i + 1 ) * gen - > getConfig ( ) . pandoraMultiplierSpells ; //5000 - 15000
2014-07-06 08:15:52 +03:00
oi . probability = 2 ;
2015-02-28 23:37:04 +02:00
possibleObjects . push_back ( oi ) ;
2014-07-06 08:15:52 +03:00
}
//Pandora with 15 spells of certain school
2015-01-30 08:39:36 +02:00
for ( int i = 0 ; i < 4 ; i + + )
2014-07-06 08:15:52 +03:00
{
2017-11-03 22:03:51 +02:00
oi . generateObject = [ i , this ] ( ) - > CGObjectInstance *
2014-07-06 08:15:52 +03:00
{
2015-11-14 15:50:29 +02:00
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : PANDORAS_BOX , 0 ) ;
auto obj = ( CGPandoraBox * ) factory - > create ( ObjectTemplate ( ) ) ;
2014-07-06 08:15:52 +03:00
std : : vector < CSpell * > spells ;
for ( auto spell : VLC - > spellh - > objects )
{
2015-08-12 16:40:08 +02:00
if ( gen - > isAllowedSpell ( spell - > id ) & & spell - > school [ ( ESpellSchool ) i ] )
2015-01-30 08:39:36 +02:00
spells . push_back ( spell ) ;
2014-07-06 08:15:52 +03:00
}
RandomGeneratorUtil : : randomShuffle ( spells , gen - > rand ) ;
2020-10-01 10:38:06 +02:00
for ( int j = 0 ; j < std : : min ( 15 , ( int ) spells . size ( ) ) ; j + + )
2014-07-06 08:15:52 +03:00
{
2015-02-28 23:37:04 +02:00
obj - > spells . push_back ( spells [ j ] - > id ) ;
2014-07-06 08:15:52 +03:00
}
return obj ;
} ;
2015-02-28 23:37:04 +02:00
oi . setTemplate ( Obj : : PANDORAS_BOX , 0 , terrainType ) ;
2022-06-05 08:02:58 +02:00
oi . value = gen - > getConfig ( ) . pandoraSpellSchool ;
2014-07-06 08:15:52 +03:00
oi . probability = 2 ;
2015-02-28 23:37:04 +02:00
possibleObjects . push_back ( oi ) ;
2014-07-06 08:15:52 +03:00
}
// Pandora box with 60 random spells
2017-11-03 22:03:51 +02:00
oi . generateObject = [ this ] ( ) - > CGObjectInstance *
2014-07-06 08:15:52 +03:00
{
2015-11-14 15:50:29 +02:00
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : PANDORAS_BOX , 0 ) ;
auto obj = ( CGPandoraBox * ) factory - > create ( ObjectTemplate ( ) ) ;
2014-07-06 08:15:52 +03:00
std : : vector < CSpell * > spells ;
for ( auto spell : VLC - > spellh - > objects )
{
2015-08-12 16:40:08 +02:00
if ( gen - > isAllowedSpell ( spell - > id ) )
2014-07-06 08:15:52 +03:00
spells . push_back ( spell ) ;
}
RandomGeneratorUtil : : randomShuffle ( spells , gen - > rand ) ;
2020-10-01 10:38:06 +02:00
for ( int j = 0 ; j < std : : min ( 60 , ( int ) spells . size ( ) ) ; j + + )
2014-07-06 08:15:52 +03:00
{
2015-02-28 23:37:04 +02:00
obj - > spells . push_back ( spells [ j ] - > id ) ;
2014-07-06 08:15:52 +03:00
}
return obj ;
} ;
2015-02-28 23:37:04 +02:00
oi . setTemplate ( Obj : : PANDORAS_BOX , 0 , terrainType ) ;
2022-06-05 08:02:58 +02:00
oi . value = gen - > getConfig ( ) . pandoraSpell60 ;
2014-07-06 08:15:52 +03:00
oi . probability = 2 ;
2015-02-28 23:37:04 +02:00
possibleObjects . push_back ( oi ) ;
2015-02-28 22:14:45 +02:00
//seer huts with creatures or generic rewards
2018-03-09 20:11:20 +02:00
if ( questArtZone . lock ( ) ) //we won't be placing seer huts if there is no zone left to place arties
2015-03-01 11:20:49 +02:00
{
static const int genericSeerHuts = 8 ;
int seerHutsPerType = 0 ;
2020-10-01 10:38:06 +02:00
const int questArtsRemaining = static_cast < int > ( gen - > getQuestArtsRemaning ( ) . size ( ) ) ;
2015-02-28 22:14:45 +02:00
2015-03-01 11:20:49 +02:00
//general issue is that not many artifact types are available for quests
2015-02-28 22:14:45 +02:00
2020-10-01 10:38:06 +02:00
if ( questArtsRemaining > = genericSeerHuts + ( int ) creatures . size ( ) )
2015-03-01 11:20:49 +02:00
{
2020-10-01 10:38:06 +02:00
seerHutsPerType = questArtsRemaining / ( genericSeerHuts + ( int ) creatures . size ( ) ) ;
2015-03-01 11:20:49 +02:00
}
else if ( questArtsRemaining > = genericSeerHuts )
{
seerHutsPerType = 1 ;
}
oi . maxPerZone = seerHutsPerType ;
2015-02-28 22:14:45 +02:00
2015-03-01 11:20:49 +02:00
RandomGeneratorUtil : : randomShuffle ( creatures , gen - > rand ) ;
2015-03-01 10:46:09 +02:00
2015-03-01 14:26:54 +02:00
auto generateArtInfo = [ this ] ( ArtifactID id ) - > ObjectInfo
2015-03-01 11:20:49 +02:00
{
ObjectInfo artInfo ;
2015-03-30 23:55:37 +02:00
artInfo . probability = std : : numeric_limits < ui16 > : : max ( ) ; //99,9% to spawn that art in first treasure pile
2015-03-01 11:20:49 +02:00
artInfo . maxPerZone = 1 ;
artInfo . value = 2000 ; //treasure art
2015-03-01 14:26:54 +02:00
artInfo . setTemplate ( Obj : : ARTIFACT , id , this - > terrainType ) ;
2015-03-01 11:20:49 +02:00
artInfo . generateObject = [ id ] ( ) - > CGObjectInstance *
{
2015-03-01 14:26:54 +02:00
auto handler = VLC - > objtypeh - > getHandlerFor ( Obj : : ARTIFACT , id ) ;
return handler - > create ( handler - > getTemplates ( ) . front ( ) ) ;
2015-03-01 11:20:49 +02:00
} ;
return artInfo ;
} ;
2015-03-01 10:46:09 +02:00
2022-06-05 08:02:58 +02:00
for ( int i = 0 ; i < std : : min ( ( int ) creatures . size ( ) , questArtsRemaining - genericSeerHuts ) ; i + + )
2015-02-28 23:37:04 +02:00
{
2015-03-01 11:20:49 +02:00
auto creature = creatures [ i ] ;
int creaturesAmount = creatureToCount ( creature ) ;
2015-02-28 22:14:45 +02:00
2015-03-01 11:20:49 +02:00
if ( ! creaturesAmount )
continue ;
2015-02-28 23:37:04 +02:00
2022-06-20 16:39:50 +02:00
int randomAppearance = chooseRandomAppearance ( Obj : : SEER_HUT ) ;
2015-02-28 23:37:04 +02:00
2017-11-03 22:03:51 +02:00
oi . generateObject = [ creature , creaturesAmount , randomAppearance , this , generateArtInfo ] ( ) - > CGObjectInstance *
2015-03-01 11:20:49 +02:00
{
2015-11-14 15:50:29 +02:00
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : SEER_HUT , randomAppearance ) ;
auto obj = ( CGSeerHut * ) factory - > create ( ObjectTemplate ( ) ) ;
2015-03-01 11:20:49 +02:00
obj - > rewardType = CGSeerHut : : CREATURE ;
obj - > rID = creature - > idNumber ;
obj - > rVal = creaturesAmount ;
2015-02-28 23:37:04 +02:00
2015-03-01 11:20:49 +02:00
obj - > quest - > missionType = CQuest : : MISSION_ART ;
ArtifactID artid = * RandomGeneratorUtil : : nextItem ( gen - > getQuestArtsRemaning ( ) , gen - > rand ) ;
obj - > quest - > m5arts . push_back ( artid ) ;
2016-01-08 22:14:57 +02:00
obj - > quest - > lastDay = - 1 ;
obj - > quest - > isCustomFirst = obj - > quest - > isCustomNext = obj - > quest - > isCustomComplete = false ;
2015-03-01 11:20:49 +02:00
gen - > banQuestArt ( artid ) ;
2018-03-09 20:11:20 +02:00
this - > questArtZone . lock ( ) - > possibleObjects . push_back ( generateArtInfo ( artid ) ) ;
2015-03-01 11:20:49 +02:00
return obj ;
} ;
oi . setTemplate ( Obj : : SEER_HUT , randomAppearance , terrainType ) ;
2020-10-01 10:38:06 +02:00
oi . value = static_cast < ui32 > ( ( ( 2 * ( creature - > AIValue ) * creaturesAmount * ( 1 + ( float ) ( gen - > getZoneCount ( creature - > faction ) ) / gen - > getTotalZoneCount ( ) ) ) - 4000 ) / 3 ) ;
2015-03-01 11:20:49 +02:00
oi . probability = 3 ;
possibleObjects . push_back ( oi ) ;
}
2015-03-01 10:46:09 +02:00
2022-06-05 08:02:58 +02:00
static int seerLevels = std : : min ( gen - > getConfig ( ) . questValues . size ( ) , gen - > getConfig ( ) . questRewardValues . size ( ) ) ;
for ( int i = 0 ; i < seerLevels ; i + + ) //seems that code for exp and gold reward is similiar
2015-03-01 10:46:09 +02:00
{
2022-06-20 16:39:50 +02:00
int randomAppearance = chooseRandomAppearance ( Obj : : SEER_HUT ) ;
2015-03-01 10:46:09 +02:00
2015-03-01 11:20:49 +02:00
oi . setTemplate ( Obj : : SEER_HUT , randomAppearance , terrainType ) ;
2022-06-05 08:02:58 +02:00
oi . value = gen - > getConfig ( ) . questValues [ i ] ;
2015-03-01 11:20:49 +02:00
oi . probability = 10 ;
2015-03-01 10:46:09 +02:00
2017-11-03 22:03:51 +02:00
oi . generateObject = [ i , randomAppearance , this , generateArtInfo ] ( ) - > CGObjectInstance *
2015-03-01 11:20:49 +02:00
{
2015-11-14 15:50:29 +02:00
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : SEER_HUT , randomAppearance ) ;
auto obj = ( CGSeerHut * ) factory - > create ( ObjectTemplate ( ) ) ;
2015-03-01 11:20:49 +02:00
obj - > rewardType = CGSeerHut : : EXPERIENCE ;
obj - > rID = 0 ; //unitialized?
2022-06-05 08:02:58 +02:00
obj - > rVal = gen - > getConfig ( ) . questRewardValues [ i ] ;
2015-03-01 10:46:09 +02:00
2015-03-01 11:20:49 +02:00
obj - > quest - > missionType = CQuest : : MISSION_ART ;
ArtifactID artid = * RandomGeneratorUtil : : nextItem ( gen - > getQuestArtsRemaning ( ) , gen - > rand ) ;
obj - > quest - > m5arts . push_back ( artid ) ;
2016-01-08 22:14:57 +02:00
obj - > quest - > lastDay = - 1 ;
obj - > quest - > isCustomFirst = obj - > quest - > isCustomNext = obj - > quest - > isCustomComplete = false ;
2015-03-01 11:20:49 +02:00
gen - > banQuestArt ( artid ) ;
2015-03-01 10:46:09 +02:00
2018-03-09 20:11:20 +02:00
this - > questArtZone . lock ( ) - > possibleObjects . push_back ( generateArtInfo ( artid ) ) ;
2015-03-01 10:46:09 +02:00
2015-03-01 11:20:49 +02:00
return obj ;
} ;
2015-03-01 10:46:09 +02:00
2015-03-01 11:20:49 +02:00
possibleObjects . push_back ( oi ) ;
2015-03-01 10:46:09 +02:00
2017-11-03 22:03:51 +02:00
oi . generateObject = [ i , randomAppearance , this , generateArtInfo ] ( ) - > CGObjectInstance *
2015-03-01 11:20:49 +02:00
{
2015-11-14 15:50:29 +02:00
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : SEER_HUT , randomAppearance ) ;
auto obj = ( CGSeerHut * ) factory - > create ( ObjectTemplate ( ) ) ;
2015-03-01 11:20:49 +02:00
obj - > rewardType = CGSeerHut : : RESOURCES ;
obj - > rID = Res : : GOLD ;
2022-06-05 08:02:58 +02:00
obj - > rVal = gen - > getConfig ( ) . questRewardValues [ i ] ;
2015-03-01 11:20:49 +02:00
obj - > quest - > missionType = CQuest : : MISSION_ART ;
ArtifactID artid = * RandomGeneratorUtil : : nextItem ( gen - > getQuestArtsRemaning ( ) , gen - > rand ) ;
obj - > quest - > m5arts . push_back ( artid ) ;
2016-01-08 22:14:57 +02:00
obj - > quest - > lastDay = - 1 ;
obj - > quest - > isCustomFirst = obj - > quest - > isCustomNext = obj - > quest - > isCustomComplete = false ;
2015-03-01 11:20:49 +02:00
gen - > banQuestArt ( artid ) ;
2018-03-09 20:11:20 +02:00
this - > questArtZone . lock ( ) - > possibleObjects . push_back ( generateArtInfo ( artid ) ) ;
2015-03-01 11:20:49 +02:00
return obj ;
} ;
possibleObjects . push_back ( oi ) ;
}
2015-02-28 22:14:45 +02:00
}
2014-06-05 21:19:42 +03:00
}
2014-07-08 21:13:51 +03:00
2016-11-25 15:47:16 +02:00
ObjectInfo : : ObjectInfo ( )
: templ ( ) , value ( 0 ) , probability ( 0 ) , maxPerZone ( 1 )
{
}
2022-06-20 16:39:50 +02:00
void ObjectInfo : : setTemplate ( si32 type , si32 subtype , Terrain terrainType )
2014-07-08 21:13:51 +03:00
{
2022-05-31 11:25:39 +02:00
auto templHandler = VLC - > objtypeh - > getHandlerFor ( type , subtype ) ;
if ( ! templHandler )
return ;
auto templates = templHandler - > getTemplates ( terrainType ) ;
if ( templates . empty ( ) )
return ;
templ = templates . front ( ) ;
2014-07-26 09:12:45 +03:00
}