2022-08-09 07:54:32 +02:00
/*
* TreasurePlacer . 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 "TreasurePlacer.h"
2023-05-20 10:17:37 +02:00
# include "../CMapGenerator.h"
# include "../Functions.h"
2022-08-09 07:54:32 +02:00
# include "ObjectManager.h"
# include "RoadPlacer.h"
# include "ConnectionsPlacer.h"
2023-05-20 10:17:37 +02:00
# include "../RmgMap.h"
# include "../TileInfo.h"
# include "../CZonePlacer.h"
2023-04-23 10:08:16 +02:00
# include "QuestArtifactPlacer.h"
2023-06-05 17:53:17 +02:00
# include "../../ArtifactUtils.h"
2023-06-02 20:47:37 +02:00
# include "../../mapObjectConstructors/AObjectTypeHandler.h"
# include "../../mapObjectConstructors/CObjectClassesHandler.h"
2023-06-08 16:29:29 +02:00
# include "../../mapObjectConstructors/DwellingInstanceConstructor.h"
2023-06-07 23:42:47 +02:00
# include "../../mapObjects/CGHeroInstance.h"
2023-06-02 20:47:37 +02:00
# include "../../mapObjects/CGPandoraBox.h"
2023-05-20 10:17:37 +02:00
# include "../../CCreatureHandler.h"
# include "../../spells/CSpellHandler.h" //for choosing random spells
# include "../../mapping/CMap.h"
# include "../../mapping/CMapEditManager.h"
2022-08-09 07:54:32 +02:00
2022-07-26 15:07:42 +02:00
VCMI_LIB_NAMESPACE_BEGIN
2022-08-09 07:54:32 +02:00
void TreasurePlacer : : process ( )
{
addAllPossibleObjects ( ) ;
auto * m = zone . getModificator < ObjectManager > ( ) ;
if ( m )
createTreasures ( * m ) ;
}
void TreasurePlacer : : init ( )
{
2023-06-07 15:08:50 +02:00
maxPrisons = 0 ; //Should be in the constructor, but we use macro for that
2022-08-09 07:54:32 +02:00
DEPENDENCY ( ObjectManager ) ;
DEPENDENCY ( ConnectionsPlacer ) ;
POSTFUNCTION ( RoadPlacer ) ;
}
2023-03-27 09:09:58 +02:00
void TreasurePlacer : : addObjectToRandomPool ( const ObjectInfo & oi )
{
2023-05-19 20:30:15 +02:00
RecursiveLock lock ( externalAccessMutex ) ;
2023-03-27 09:09:58 +02:00
possibleObjects . push_back ( oi ) ;
}
2022-08-09 07:54:32 +02:00
void TreasurePlacer : : addAllPossibleObjects ( )
{
ObjectInfo oi ;
for ( auto primaryID : VLC - > objtypeh - > knownObjects ( ) )
{
for ( auto secondaryID : VLC - > objtypeh - > knownSubObjects ( primaryID ) )
{
auto handler = VLC - > objtypeh - > getHandlerFor ( primaryID , secondaryID ) ;
if ( ! handler - > isStaticObject ( ) & & handler - > getRMGInfo ( ) . value )
{
2023-03-27 09:09:58 +02:00
auto rmgInfo = handler - > getRMGInfo ( ) ;
2023-03-27 17:29:46 +02:00
if ( rmgInfo . mapLimit | | rmgInfo . value > zone . getMaxTreasureValue ( ) )
2023-03-27 09:09:58 +02:00
{
//Skip objects with per-map limit here
continue ;
}
2023-03-28 17:53:08 +02:00
auto templates = handler - > getTemplates ( zone . getTerrainType ( ) ) ;
if ( templates . empty ( ) )
continue ;
2023-04-29 11:46:03 +02:00
//TODO: Reuse chooseRandomAppearance (eg. WoG treasure chests)
2023-03-28 17:53:08 +02:00
//Assume the template with fewest terrains is the most suitable
auto temp = * boost : : min_element ( templates , [ ] ( std : : shared_ptr < const ObjectTemplate > lhs , std : : shared_ptr < const ObjectTemplate > rhs ) - > bool
2022-08-09 07:54:32 +02:00
{
2023-03-28 17:53:08 +02:00
return lhs - > getAllowedTerrains ( ) . size ( ) < rhs - > getAllowedTerrains ( ) . size ( ) ;
} ) ;
2023-03-27 08:44:49 +02:00
2023-03-28 17:53:08 +02:00
oi . generateObject = [ temp ] ( ) - > CGObjectInstance *
{
return VLC - > objtypeh - > getHandlerFor ( temp - > id , temp - > subid ) - > create ( temp ) ;
} ;
oi . value = rmgInfo . value ;
oi . probability = rmgInfo . rarity ;
oi . templ = temp ;
oi . maxPerZone = rmgInfo . zoneLimit ;
addObjectToRandomPool ( oi ) ;
2022-08-09 07:54:32 +02:00
}
}
}
2023-06-07 13:00:24 +02:00
//Generate Prison on water only if it has a template
auto prisonTemplates = VLC - > objtypeh - > getHandlerFor ( Obj : : PRISON , 0 ) - > getTemplates ( zone . getTerrainType ( ) ) ;
if ( ! prisonTemplates . empty ( ) )
2022-08-09 07:54:32 +02:00
{
2023-06-07 13:00:24 +02:00
//prisons
//levels 1, 5, 10, 20, 30
static int prisonsLevels = std : : min ( generator . getConfig ( ) . prisonExperience . size ( ) , generator . getConfig ( ) . prisonValues . size ( ) ) ;
2023-05-05 10:30:36 +02:00
2023-06-07 13:00:24 +02:00
size_t prisonsLeft = getMaxPrisons ( ) ;
for ( int i = prisonsLevels - 1 ; i > = 0 ; i - - )
2022-08-09 07:54:32 +02:00
{
2023-06-07 13:00:24 +02:00
oi . value = generator . getConfig ( ) . prisonValues [ i ] ;
if ( oi . value > zone . getMaxTreasureValue ( ) )
{
continue ;
}
oi . generateObject = [ i , this ] ( ) - > CGObjectInstance *
{
auto possibleHeroes = generator . getAllPossibleHeroes ( ) ;
HeroTypeID hid = * RandomGeneratorUtil : : nextItem ( possibleHeroes , zone . getRand ( ) ) ;
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : PRISON , 0 ) ;
auto * obj = dynamic_cast < CGHeroInstance * > ( factory - > create ( ) ) ;
obj - > subID = hid ; //will be initialized later
obj - > exp = generator . getConfig ( ) . prisonExperience [ i ] ;
obj - > setOwner ( PlayerColor : : NEUTRAL ) ;
generator . banHero ( hid ) ;
obj - > appearance = VLC - > objtypeh - > getHandlerFor ( Obj : : PRISON , 0 ) - > getTemplates ( zone . getTerrainType ( ) ) . front ( ) ; //can't init template with hero subID
return obj ;
} ;
oi . setTemplate ( Obj : : PRISON , 0 , zone . getTerrainType ( ) ) ;
oi . value = generator . getConfig ( ) . prisonValues [ i ] ;
oi . probability = 30 ;
//Distribute all allowed prisons, starting from the most valuable
oi . maxPerZone = ( std : : ceil ( ( float ) prisonsLeft / ( i + 1 ) ) ) ;
prisonsLeft - = oi . maxPerZone ;
addObjectToRandomPool ( oi ) ;
}
2022-08-09 07:54:32 +02:00
}
2023-06-07 13:00:24 +02:00
if ( zone . getType ( ) = = ETemplateZoneType : : WATER )
return ;
2022-08-09 07:54:32 +02:00
//all following objects are unlimited
2023-02-11 18:05:02 +02:00
oi . maxPerZone = std : : numeric_limits < ui32 > : : max ( ) ;
2022-08-09 07:54:32 +02:00
std : : vector < CCreature * > creatures ; //native creatures for this zone
for ( auto cre : VLC - > creh - > objects )
{
2023-04-09 17:26:32 +02:00
if ( ! cre - > special & & cre - > getFaction ( ) = = zone . getTownType ( ) )
2022-08-09 07:54:32 +02:00
{
creatures . push_back ( cre ) ;
}
}
//dwellings
auto dwellingTypes = { Obj : : CREATURE_GENERATOR1 , Obj : : CREATURE_GENERATOR4 } ;
for ( auto dwellingType : dwellingTypes )
{
auto subObjects = VLC - > objtypeh - > knownSubObjects ( dwellingType ) ;
if ( dwellingType = = Obj : : CREATURE_GENERATOR1 )
{
//don't spawn original "neutral" dwellings that got replaced by Conflux dwellings in AB
static int elementalConfluxROE [ ] = { 7 , 13 , 16 , 47 } ;
2023-02-11 18:05:02 +02:00
for ( int & i : elementalConfluxROE )
vstd : : erase_if_present ( subObjects , i ) ;
2022-08-09 07:54:32 +02:00
}
for ( auto secondaryID : subObjects )
{
2023-06-08 16:29:29 +02:00
const auto * dwellingHandler = dynamic_cast < const DwellingInstanceConstructor * > ( VLC - > objtypeh - > getHandlerFor ( dwellingType , secondaryID ) . get ( ) ) ;
2022-08-09 07:54:32 +02:00
auto creatures = dwellingHandler - > getProducedCreatures ( ) ;
if ( creatures . empty ( ) )
continue ;
2023-02-11 18:05:02 +02:00
const auto * cre = creatures . front ( ) ;
2023-04-09 17:26:32 +02:00
if ( cre - > getFaction ( ) = = zone . getTownType ( ) )
2022-08-09 07:54:32 +02:00
{
2023-04-09 17:26:32 +02:00
auto nativeZonesCount = static_cast < float > ( map . getZoneCount ( cre - > getFaction ( ) ) ) ;
2023-04-05 02:26:29 +02:00
oi . value = static_cast < ui32 > ( cre - > getAIValue ( ) * cre - > getGrowth ( ) * ( 1 + ( nativeZonesCount / map . getTotalZoneCount ( ) ) + ( nativeZonesCount / 2 ) ) ) ;
2022-08-09 07:54:32 +02:00
oi . probability = 40 ;
2023-02-11 18:05:02 +02:00
for ( const auto & tmplate : dwellingHandler - > getTemplates ( ) )
2022-08-09 07:54:32 +02:00
{
2022-09-11 15:12:35 +02:00
if ( tmplate - > canBePlacedAt ( zone . getTerrainType ( ) ) )
2022-08-09 07:54:32 +02:00
{
oi . generateObject = [ tmplate , secondaryID , dwellingType ] ( ) - > CGObjectInstance *
{
2023-02-11 18:05:02 +02:00
auto * obj = VLC - > objtypeh - > getHandlerFor ( dwellingType , secondaryID ) - > create ( tmplate ) ;
2022-08-09 07:54:32 +02:00
obj - > tempOwner = PlayerColor : : NEUTRAL ;
return obj ;
} ;
2023-02-11 18:05:02 +02:00
2022-08-09 07:54:32 +02:00
oi . templ = tmplate ;
2023-03-27 09:09:58 +02:00
addObjectToRandomPool ( oi ) ;
2022-08-09 07:54:32 +02:00
}
}
}
}
}
for ( int i = 0 ; i < generator . getConfig ( ) . scrollValues . size ( ) ; i + + )
{
oi . generateObject = [ i , this ] ( ) - > CGObjectInstance *
{
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : SPELL_SCROLL , 0 ) ;
2023-02-11 18:05:02 +02:00
auto * obj = dynamic_cast < CGArtifact * > ( factory - > create ( ) ) ;
2022-08-09 07:54:32 +02:00
std : : vector < SpellID > out ;
for ( auto spell : VLC - > spellh - > objects ) //spellh size appears to be greater (?)
{
2023-08-19 20:13:57 +02:00
if ( map . isAllowedSpell ( spell - > id ) & & spell - > getLevel ( ) = = i + 1 )
2022-08-09 07:54:32 +02:00
{
out . push_back ( spell - > id ) ;
}
}
2023-06-05 17:53:17 +02:00
auto * a = ArtifactUtils : : createScroll ( * RandomGeneratorUtil : : nextItem ( out , zone . getRand ( ) ) ) ;
2022-08-09 07:54:32 +02:00
obj - > storedArtifact = a ;
return obj ;
} ;
oi . setTemplate ( Obj : : SPELL_SCROLL , 0 , zone . getTerrainType ( ) ) ;
oi . value = generator . getConfig ( ) . scrollValues [ i ] ;
oi . probability = 30 ;
2023-03-27 09:09:58 +02:00
addObjectToRandomPool ( oi ) ;
2022-08-09 07:54:32 +02:00
}
//pandora box with gold
for ( int i = 1 ; i < 5 ; i + + )
{
oi . generateObject = [ i ] ( ) - > CGObjectInstance *
{
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : PANDORAS_BOX , 0 ) ;
2023-02-11 18:05:02 +02:00
auto * obj = dynamic_cast < CGPandoraBox * > ( factory - > create ( ) ) ;
2023-04-05 02:26:29 +02:00
obj - > resources [ EGameResID : : GOLD ] = i * 5000 ;
2022-08-09 07:54:32 +02:00
return obj ;
} ;
oi . setTemplate ( Obj : : PANDORAS_BOX , 0 , zone . getTerrainType ( ) ) ;
oi . value = i * generator . getConfig ( ) . pandoraMultiplierGold ;
oi . probability = 5 ;
2023-03-27 09:09:58 +02:00
addObjectToRandomPool ( oi ) ;
2022-08-09 07:54:32 +02:00
}
//pandora box with experience
for ( int i = 1 ; i < 5 ; i + + )
{
oi . generateObject = [ i ] ( ) - > CGObjectInstance *
{
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : PANDORAS_BOX , 0 ) ;
2023-02-11 18:05:02 +02:00
auto * obj = dynamic_cast < CGPandoraBox * > ( factory - > create ( ) ) ;
2022-08-09 07:54:32 +02:00
obj - > gainedExp = i * 5000 ;
return obj ;
} ;
oi . setTemplate ( Obj : : PANDORAS_BOX , 0 , zone . getTerrainType ( ) ) ;
oi . value = i * generator . getConfig ( ) . pandoraMultiplierExperience ;
oi . probability = 20 ;
2023-03-27 09:09:58 +02:00
addObjectToRandomPool ( oi ) ;
2022-08-09 07:54:32 +02:00
}
//pandora box with creatures
const std : : vector < int > & tierValues = generator . getConfig ( ) . pandoraCreatureValues ;
auto creatureToCount = [ tierValues ] ( CCreature * creature ) - > int
{
2023-04-05 02:26:29 +02:00
if ( ! creature - > getAIValue ( ) | | tierValues . empty ( ) ) //bug #2681
2022-08-09 07:54:32 +02:00
return 0 ; //this box won't be generated
2023-04-07 21:23:34 +02:00
//Follow the rules from https://heroes.thelazy.net/index.php/Pandora%27s_Box
2023-04-05 02:26:29 +02:00
int actualTier = creature - > getLevel ( ) > tierValues . size ( ) ?
2023-04-11 18:37:35 +02:00
tierValues . size ( ) - 1 :
creature - > getLevel ( ) - 1 ;
float creaturesAmount = std : : floor ( ( static_cast < float > ( tierValues [ actualTier ] ) ) / creature - > getAIValue ( ) ) ;
2023-04-07 21:23:34 +02:00
if ( creaturesAmount < 1 )
{
return 0 ;
}
else if ( creaturesAmount < = 5 )
2022-08-09 07:54:32 +02:00
{
2023-04-07 21:23:34 +02:00
//No change
2022-08-09 07:54:32 +02:00
}
else if ( creaturesAmount < = 12 )
{
2023-04-07 21:23:34 +02:00
creaturesAmount = std : : ceil ( creaturesAmount / 2 ) * 2 ;
2022-08-09 07:54:32 +02:00
}
else if ( creaturesAmount < = 50 )
{
2023-04-11 00:29:36 +02:00
creaturesAmount = std : : round ( creaturesAmount / 5 ) * 5 ;
2022-08-09 07:54:32 +02:00
}
else
{
2023-04-11 00:29:36 +02:00
creaturesAmount = std : : round ( creaturesAmount / 10 ) * 10 ;
2022-08-09 07:54:32 +02:00
}
return static_cast < int > ( creaturesAmount ) ;
} ;
2023-02-11 18:05:02 +02:00
for ( auto * creature : creatures )
2022-08-09 07:54:32 +02:00
{
int creaturesAmount = creatureToCount ( creature ) ;
if ( ! creaturesAmount )
continue ;
oi . generateObject = [ creature , creaturesAmount ] ( ) - > CGObjectInstance *
{
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : PANDORAS_BOX , 0 ) ;
2023-02-11 18:05:02 +02:00
auto * obj = dynamic_cast < CGPandoraBox * > ( factory - > create ( ) ) ;
auto * stack = new CStackInstance ( creature , creaturesAmount ) ;
2022-08-09 07:54:32 +02:00
obj - > creatures . putStack ( SlotID ( 0 ) , stack ) ;
return obj ;
} ;
oi . setTemplate ( Obj : : PANDORAS_BOX , 0 , zone . getTerrainType ( ) ) ;
2023-04-09 17:26:32 +02:00
oi . value = static_cast < ui32 > ( ( 2 * ( creature - > getAIValue ( ) ) * creaturesAmount * ( 1 + static_cast < float > ( map . getZoneCount ( creature - > getFaction ( ) ) ) / map . getTotalZoneCount ( ) ) ) / 3 ) ;
2022-08-09 07:54:32 +02:00
oi . probability = 3 ;
2023-03-27 09:09:58 +02:00
addObjectToRandomPool ( oi ) ;
2022-08-09 07:54:32 +02:00
}
//Pandora with 12 spells of certain level
for ( int i = 1 ; i < = GameConstants : : SPELL_LEVELS ; i + + )
{
oi . generateObject = [ i , this ] ( ) - > CGObjectInstance *
{
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : PANDORAS_BOX , 0 ) ;
2023-02-11 18:05:02 +02:00
auto * obj = dynamic_cast < CGPandoraBox * > ( factory - > create ( ) ) ;
2022-08-09 07:54:32 +02:00
std : : vector < CSpell * > spells ;
for ( auto spell : VLC - > spellh - > objects )
{
2023-08-19 20:13:57 +02:00
if ( map . isAllowedSpell ( spell - > id ) & & spell - > getLevel ( ) = = i )
2022-08-09 07:54:32 +02:00
spells . push_back ( spell ) ;
}
2023-05-20 11:46:32 +02:00
RandomGeneratorUtil : : randomShuffle ( spells , zone . getRand ( ) ) ;
2023-02-11 18:05:02 +02:00
for ( int j = 0 ; j < std : : min ( 12 , static_cast < int > ( spells . size ( ) ) ) ; j + + )
2022-08-09 07:54:32 +02:00
{
obj - > spells . push_back ( spells [ j ] - > id ) ;
}
return obj ;
} ;
oi . setTemplate ( Obj : : PANDORAS_BOX , 0 , zone . getTerrainType ( ) ) ;
oi . value = ( i + 1 ) * generator . getConfig ( ) . pandoraMultiplierSpells ; //5000 - 15000
oi . probability = 2 ;
2023-03-27 09:09:58 +02:00
addObjectToRandomPool ( oi ) ;
2022-08-09 07:54:32 +02:00
}
//Pandora with 15 spells of certain school
for ( int i = 0 ; i < 4 ; i + + )
{
oi . generateObject = [ i , this ] ( ) - > CGObjectInstance *
{
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : PANDORAS_BOX , 0 ) ;
2023-02-11 18:05:02 +02:00
auto * obj = dynamic_cast < CGPandoraBox * > ( factory - > create ( ) ) ;
2022-08-09 07:54:32 +02:00
std : : vector < CSpell * > spells ;
for ( auto spell : VLC - > spellh - > objects )
{
2023-05-05 20:28:07 +02:00
if ( map . isAllowedSpell ( spell - > id ) & & spell - > school [ SpellSchool ( i ) ] )
2022-08-09 07:54:32 +02:00
spells . push_back ( spell ) ;
}
2023-05-20 11:46:32 +02:00
RandomGeneratorUtil : : randomShuffle ( spells , zone . getRand ( ) ) ;
2023-02-11 18:05:02 +02:00
for ( int j = 0 ; j < std : : min ( 15 , static_cast < int > ( spells . size ( ) ) ) ; j + + )
2022-08-09 07:54:32 +02:00
{
obj - > spells . push_back ( spells [ j ] - > id ) ;
}
return obj ;
} ;
oi . setTemplate ( Obj : : PANDORAS_BOX , 0 , zone . getTerrainType ( ) ) ;
oi . value = generator . getConfig ( ) . pandoraSpellSchool ;
oi . probability = 2 ;
2023-03-27 09:09:58 +02:00
addObjectToRandomPool ( oi ) ;
2022-08-09 07:54:32 +02:00
}
// Pandora box with 60 random spells
oi . generateObject = [ this ] ( ) - > CGObjectInstance *
{
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : PANDORAS_BOX , 0 ) ;
2023-02-11 18:05:02 +02:00
auto * obj = dynamic_cast < CGPandoraBox * > ( factory - > create ( ) ) ;
2022-08-09 07:54:32 +02:00
std : : vector < CSpell * > spells ;
for ( auto spell : VLC - > spellh - > objects )
{
if ( map . isAllowedSpell ( spell - > id ) )
spells . push_back ( spell ) ;
}
2023-05-20 11:46:32 +02:00
RandomGeneratorUtil : : randomShuffle ( spells , zone . getRand ( ) ) ;
2023-02-11 18:05:02 +02:00
for ( int j = 0 ; j < std : : min ( 60 , static_cast < int > ( spells . size ( ) ) ) ; j + + )
2022-08-09 07:54:32 +02:00
{
obj - > spells . push_back ( spells [ j ] - > id ) ;
}
return obj ;
} ;
oi . setTemplate ( Obj : : PANDORAS_BOX , 0 , zone . getTerrainType ( ) ) ;
oi . value = generator . getConfig ( ) . pandoraSpell60 ;
oi . probability = 2 ;
2023-03-27 09:09:58 +02:00
addObjectToRandomPool ( oi ) ;
2022-08-09 07:54:32 +02:00
2023-04-29 11:46:03 +02:00
//Seer huts with creatures or generic rewards
2023-04-23 10:08:16 +02:00
2023-06-17 19:09:38 +02:00
if ( zone . getConnectedZoneIds ( ) . size ( ) ) //Unlikely, but...
2022-08-09 07:54:32 +02:00
{
2023-04-29 11:46:03 +02:00
auto * qap = zone . getModificator < QuestArtifactPlacer > ( ) ;
if ( ! qap )
2022-08-09 07:54:32 +02:00
{
2023-04-29 11:46:03 +02:00
return ; //TODO: throw?
2022-08-09 07:54:32 +02:00
}
2023-04-29 11:46:03 +02:00
const int questArtsRemaining = qap - > getMaxQuestArtifactCount ( ) ;
2023-06-14 20:50:14 +02:00
if ( ! questArtsRemaining )
{
return ;
}
2023-04-29 11:46:03 +02:00
//Generate Seer Hut one by one. Duplicated oi possible and should work fine.
oi . maxPerZone = 1 ;
2023-02-11 18:05:02 +02:00
2023-04-29 11:46:03 +02:00
std : : vector < ObjectInfo > possibleSeerHuts ;
//14 creatures per town + 4 for each of gold / exp reward
possibleSeerHuts . reserve ( 14 + 4 + 4 ) ;
2023-05-20 11:46:32 +02:00
RandomGeneratorUtil : : randomShuffle ( creatures , zone . getRand ( ) ) ;
2023-02-11 18:05:02 +02:00
2023-04-29 11:46:03 +02:00
for ( int i = 0 ; i < static_cast < int > ( creatures . size ( ) ) ; i + + )
2022-08-09 07:54:32 +02:00
{
2023-02-11 18:05:02 +02:00
auto * creature = creatures [ i ] ;
2022-08-09 07:54:32 +02:00
int creaturesAmount = creatureToCount ( creature ) ;
if ( ! creaturesAmount )
continue ;
2023-05-20 11:46:32 +02:00
int randomAppearance = chooseRandomAppearance ( zone . getRand ( ) , Obj : : SEER_HUT , zone . getTerrainType ( ) ) ;
2022-08-09 07:54:32 +02:00
2023-04-29 11:46:03 +02:00
oi . generateObject = [ creature , creaturesAmount , randomAppearance , this , qap ] ( ) - > CGObjectInstance *
2022-08-09 07:54:32 +02:00
{
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : SEER_HUT , randomAppearance ) ;
2023-02-11 18:05:02 +02:00
auto * obj = dynamic_cast < CGSeerHut * > ( factory - > create ( ) ) ;
2023-09-13 01:40:07 +02:00
Rewardable : : Reward reward ;
reward . creatures . emplace_back ( creature - > getId ( ) , creaturesAmount ) ;
obj - > configuration . info . push_back ( { } ) ;
obj - > configuration . info . back ( ) . reward = reward ;
obj - > configuration . info . back ( ) . visitType = Rewardable : : EEventType : : EVENT_FIRST_VISIT ;
2022-08-09 07:54:32 +02:00
obj - > quest - > missionType = CQuest : : MISSION_ART ;
2023-04-29 11:46:03 +02:00
ArtifactID artid = qap - > drawRandomArtifact ( ) ;
2022-10-01 08:28:32 +02:00
obj - > quest - > addArtifactID ( artid ) ;
2022-08-09 07:54:32 +02:00
obj - > quest - > lastDay = - 1 ;
obj - > quest - > isCustomFirst = obj - > quest - > isCustomNext = obj - > quest - > isCustomComplete = false ;
generator . banQuestArt ( artid ) ;
2023-04-23 10:08:16 +02:00
zone . getModificator < QuestArtifactPlacer > ( ) - > addQuestArtifact ( artid ) ;
2022-08-09 07:54:32 +02:00
return obj ;
} ;
2023-04-29 11:46:03 +02:00
oi . probability = 3 ;
2022-08-09 07:54:32 +02:00
oi . setTemplate ( Obj : : SEER_HUT , randomAppearance , zone . getTerrainType ( ) ) ;
2023-04-09 17:26:32 +02:00
oi . value = static_cast < ui32 > ( ( ( 2 * ( creature - > getAIValue ( ) ) * creaturesAmount * ( 1 + static_cast < float > ( map . getZoneCount ( creature - > getFaction ( ) ) ) / map . getTotalZoneCount ( ) ) ) - 4000 ) / 3 ) ;
2023-04-29 11:46:03 +02:00
if ( oi . value > zone . getMaxTreasureValue ( ) )
{
continue ;
}
else
{
possibleSeerHuts . push_back ( oi ) ;
}
2022-08-09 07:54:32 +02:00
}
static int seerLevels = std : : min ( generator . getConfig ( ) . questValues . size ( ) , generator . getConfig ( ) . questRewardValues . size ( ) ) ;
for ( int i = 0 ; i < seerLevels ; i + + ) //seems that code for exp and gold reward is similiar
{
2023-05-20 11:46:32 +02:00
int randomAppearance = chooseRandomAppearance ( zone . getRand ( ) , Obj : : SEER_HUT , zone . getTerrainType ( ) ) ;
2022-08-09 07:54:32 +02:00
oi . setTemplate ( Obj : : SEER_HUT , randomAppearance , zone . getTerrainType ( ) ) ;
oi . value = generator . getConfig ( ) . questValues [ i ] ;
2023-04-29 11:46:03 +02:00
if ( oi . value > zone . getMaxTreasureValue ( ) )
{
//Both variants have same value
continue ;
}
2022-08-09 07:54:32 +02:00
oi . probability = 10 ;
2023-06-17 08:45:10 +02:00
oi . maxPerZone = 1 ;
2022-08-09 07:54:32 +02:00
2023-04-29 11:46:03 +02:00
oi . generateObject = [ i , randomAppearance , this , qap ] ( ) - > CGObjectInstance *
2022-08-09 07:54:32 +02:00
{
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : SEER_HUT , randomAppearance ) ;
2023-02-11 18:05:02 +02:00
auto * obj = dynamic_cast < CGSeerHut * > ( factory - > create ( ) ) ;
2023-09-13 01:40:07 +02:00
Rewardable : : Reward reward ;
reward . heroExperience = generator . getConfig ( ) . questRewardValues [ i ] ;
obj - > configuration . info . push_back ( { } ) ;
obj - > configuration . info . back ( ) . reward = reward ;
obj - > configuration . info . back ( ) . visitType = Rewardable : : EEventType : : EVENT_FIRST_VISIT ;
2022-08-09 07:54:32 +02:00
obj - > quest - > missionType = CQuest : : MISSION_ART ;
2023-04-29 11:46:03 +02:00
ArtifactID artid = qap - > drawRandomArtifact ( ) ;
2022-10-01 08:28:32 +02:00
obj - > quest - > addArtifactID ( artid ) ;
2022-08-09 07:54:32 +02:00
obj - > quest - > lastDay = - 1 ;
obj - > quest - > isCustomFirst = obj - > quest - > isCustomNext = obj - > quest - > isCustomComplete = false ;
generator . banQuestArt ( artid ) ;
2023-04-23 10:08:16 +02:00
zone . getModificator < QuestArtifactPlacer > ( ) - > addQuestArtifact ( artid ) ;
2022-08-09 07:54:32 +02:00
return obj ;
} ;
2023-04-29 11:46:03 +02:00
possibleSeerHuts . push_back ( oi ) ;
2022-08-09 07:54:32 +02:00
2023-04-29 11:46:03 +02:00
oi . generateObject = [ i , randomAppearance , this , qap ] ( ) - > CGObjectInstance *
2022-08-09 07:54:32 +02:00
{
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : SEER_HUT , randomAppearance ) ;
2023-02-11 18:05:02 +02:00
auto * obj = dynamic_cast < CGSeerHut * > ( factory - > create ( ) ) ;
2023-09-13 01:40:07 +02:00
Rewardable : : Reward reward ;
reward . resources [ EGameResID : : GOLD ] = generator . getConfig ( ) . questRewardValues [ i ] ;
obj - > configuration . info . push_back ( { } ) ;
obj - > configuration . info . back ( ) . reward = reward ;
obj - > configuration . info . back ( ) . visitType = Rewardable : : EEventType : : EVENT_FIRST_VISIT ;
2022-08-09 07:54:32 +02:00
obj - > quest - > missionType = CQuest : : MISSION_ART ;
2023-04-29 11:46:03 +02:00
ArtifactID artid = qap - > drawRandomArtifact ( ) ;
2022-10-01 08:28:32 +02:00
obj - > quest - > addArtifactID ( artid ) ;
2022-08-09 07:54:32 +02:00
obj - > quest - > lastDay = - 1 ;
obj - > quest - > isCustomFirst = obj - > quest - > isCustomNext = obj - > quest - > isCustomComplete = false ;
generator . banQuestArt ( artid ) ;
2023-04-23 10:08:16 +02:00
zone . getModificator < QuestArtifactPlacer > ( ) - > addQuestArtifact ( artid ) ;
2022-08-09 07:54:32 +02:00
return obj ;
} ;
2023-04-29 11:46:03 +02:00
possibleSeerHuts . push_back ( oi ) ;
}
2023-06-14 20:50:14 +02:00
if ( possibleSeerHuts . empty ( ) )
{
return ;
}
2023-04-29 11:46:03 +02:00
for ( size_t i = 0 ; i < questArtsRemaining ; i + + )
{
2023-05-20 11:46:32 +02:00
addObjectToRandomPool ( * RandomGeneratorUtil : : nextItem ( possibleSeerHuts , zone . getRand ( ) ) ) ;
2022-08-09 07:54:32 +02:00
}
}
}
2023-03-27 09:09:58 +02:00
size_t TreasurePlacer : : getPossibleObjectsSize ( ) const
{
2023-05-19 20:30:15 +02:00
RecursiveLock lock ( externalAccessMutex ) ;
2023-03-27 09:09:58 +02:00
return possibleObjects . size ( ) ;
}
2023-05-05 10:30:36 +02:00
void TreasurePlacer : : setMaxPrisons ( size_t count )
{
2023-05-19 20:30:15 +02:00
RecursiveLock lock ( externalAccessMutex ) ;
2023-05-05 10:30:36 +02:00
maxPrisons = count ;
}
size_t TreasurePlacer : : getMaxPrisons ( ) const
{
2023-05-19 20:30:15 +02:00
RecursiveLock lock ( externalAccessMutex ) ;
2023-05-05 10:30:36 +02:00
return maxPrisons ;
}
2022-08-09 07:54:32 +02:00
bool TreasurePlacer : : isGuardNeededForTreasure ( int value )
2023-05-31 00:24:52 +02:00
{ // no guard in a zone with "monsters: none" and for small treasures; water zones cen get monster strength ZONE_NONE elsewhere if needed
return zone . monsterStrength ! = EMonsterStrength : : ZONE_NONE & & value > minGuardedValue ;
2022-08-09 07:54:32 +02:00
}
std : : vector < ObjectInfo * > TreasurePlacer : : prepareTreasurePile ( const CTreasureInfo & treasureInfo )
{
std : : vector < ObjectInfo * > objectInfos ;
int maxValue = treasureInfo . max ;
int minValue = treasureInfo . min ;
2023-05-20 11:46:32 +02:00
const ui32 desiredValue = zone . getRand ( ) . nextInt ( minValue , maxValue ) ;
2022-08-09 07:54:32 +02:00
int currentValue = 0 ;
bool hasLargeObject = false ;
2023-02-11 18:05:02 +02:00
while ( currentValue < = static_cast < int > ( desiredValue ) - 100 ) //no objects with value below 100 are available
2022-08-09 07:54:32 +02:00
{
2023-06-08 19:23:23 +02:00
auto * oi = getRandomObject ( desiredValue , currentValue , ! hasLargeObject ) ;
2022-08-09 07:54:32 +02:00
if ( ! oi ) //fail
break ;
2022-09-11 15:12:35 +02:00
if ( oi - > templ - > isVisitableFromTop ( ) )
2022-08-09 07:54:32 +02:00
{
objectInfos . push_back ( oi ) ;
}
else
{
objectInfos . insert ( objectInfos . begin ( ) , oi ) ; //large object shall at first place
hasLargeObject = true ;
}
//remove from possible objects
assert ( oi - > maxPerZone ) ;
oi - > maxPerZone - - ;
currentValue + = oi - > value ;
}
return objectInfos ;
}
2022-08-20 12:17:27 +02:00
rmg : : Object TreasurePlacer : : constructTreasurePile ( const std : : vector < ObjectInfo * > & treasureInfos , bool densePlacement )
2022-08-09 07:54:32 +02:00
{
rmg : : Object rmgObject ;
2023-02-11 18:05:02 +02:00
for ( const auto & oi : treasureInfos )
2022-08-09 07:54:32 +02:00
{
auto blockedArea = rmgObject . getArea ( ) ;
auto accessibleArea = rmgObject . getAccessibleArea ( ) ;
if ( rmgObject . instances ( ) . empty ( ) )
accessibleArea . add ( int3 ( ) ) ;
auto * object = oi - > generateObject ( ) ;
object - > appearance = oi - > templ ;
auto & instance = rmgObject . addInstance ( * object ) ;
do
{
if ( accessibleArea . empty ( ) )
{
//fail - fallback
rmgObject . clear ( ) ;
return rmgObject ;
}
2022-08-20 12:17:27 +02:00
std : : vector < int3 > bestPositions ;
if ( densePlacement )
{
int bestPositionsWeight = std : : numeric_limits < int > : : max ( ) ;
2023-02-11 18:05:02 +02:00
for ( const auto & t : accessibleArea . getTilesVector ( ) )
2022-08-20 12:17:27 +02:00
{
instance . setPosition ( t ) ;
int w = rmgObject . getAccessibleArea ( ) . getTilesVector ( ) . size ( ) ;
if ( w < bestPositionsWeight )
{
bestPositions . clear ( ) ;
bestPositions . push_back ( t ) ;
bestPositionsWeight = w ;
}
else if ( w = = bestPositionsWeight )
{
bestPositions . push_back ( t ) ;
}
}
}
else
{
bestPositions = accessibleArea . getTilesVector ( ) ;
}
2023-05-20 11:46:32 +02:00
int3 nextPos = * RandomGeneratorUtil : : nextItem ( bestPositions , zone . getRand ( ) ) ;
2022-08-09 07:54:32 +02:00
instance . setPosition ( nextPos - rmgObject . getPosition ( ) ) ;
auto instanceAccessibleArea = instance . getAccessibleArea ( ) ;
if ( instance . getBlockedArea ( ) . getTilesVector ( ) . size ( ) = = 1 )
{
2022-09-11 15:12:35 +02:00
if ( instance . object ( ) . appearance - > isVisitableFromTop ( ) & & instance . object ( ) . ID ! = Obj : : CORPSE )
2022-08-09 07:54:32 +02:00
instanceAccessibleArea . add ( instance . getVisitablePosition ( ) ) ;
}
//first object is good
if ( rmgObject . instances ( ) . size ( ) = = 1 )
break ;
//condition for good position
if ( ! blockedArea . overlap ( instance . getBlockedArea ( ) ) & & accessibleArea . overlap ( instanceAccessibleArea ) )
break ;
//fail - new position
accessibleArea . erase ( nextPos ) ;
} while ( true ) ;
}
return rmgObject ;
}
2023-06-08 19:23:23 +02:00
ObjectInfo * TreasurePlacer : : getRandomObject ( ui32 desiredValue , ui32 currentValue , bool allowLargeObjects )
2022-08-09 07:54:32 +02:00
{
std : : vector < std : : pair < ui32 , ObjectInfo * > > thresholds ; //handle complex object via pointer
ui32 total = 0 ;
//calculate actual treasure value range based on remaining value
2023-06-08 19:23:23 +02:00
ui32 maxVal = desiredValue - currentValue ;
2022-08-09 07:54:32 +02:00
ui32 minValue = static_cast < ui32 > ( 0.25f * ( desiredValue - currentValue ) ) ;
for ( ObjectInfo & oi : possibleObjects ) //copy constructor turned out to be costly
{
if ( oi . value > maxVal )
break ; //this assumes values are sorted in ascending order
2022-09-11 15:12:35 +02:00
if ( ! oi . templ - > isVisitableFromTop ( ) & & ! allowLargeObjects )
2022-08-09 07:54:32 +02:00
continue ;
if ( oi . value > = minValue & & oi . maxPerZone > 0 )
{
total + = oi . probability ;
2023-02-11 18:05:02 +02:00
thresholds . emplace_back ( total , & oi ) ;
2022-08-09 07:54:32 +02:00
}
}
if ( thresholds . empty ( ) )
{
return nullptr ;
}
else
{
2023-05-20 11:46:32 +02:00
int r = zone . getRand ( ) . nextInt ( 1 , total ) ;
2023-02-11 18:05:02 +02:00
auto sorter = [ ] ( const std : : pair < ui32 , ObjectInfo * > & rhs , const ui32 lhs ) - > bool
{
return static_cast < int > ( rhs . first ) < lhs ;
} ;
2022-08-09 07:54:32 +02:00
//binary search = fastest
2023-02-11 18:05:02 +02:00
auto it = std : : lower_bound ( thresholds . begin ( ) , thresholds . end ( ) , r , sorter ) ;
2022-08-09 07:54:32 +02:00
return it - > second ;
}
}
2023-06-08 19:51:21 +02:00
void TreasurePlacer : : createTreasures ( ObjectManager & manager )
2022-08-09 07:54:32 +02:00
{
2022-08-20 12:17:27 +02:00
const int maxAttempts = 2 ;
2023-06-08 19:51:21 +02:00
2022-08-09 07:54:32 +02:00
int mapMonsterStrength = map . getMapGenOptions ( ) . getMonsterStrength ( ) ;
2023-06-08 19:51:21 +02:00
int monsterStrength = ( zone . monsterStrength = = EMonsterStrength : : ZONE_NONE ? 0 : zone . monsterStrength + mapMonsterStrength - 1 ) ; //array index from 0 to 4; pick any correct value for ZONE_NONE, minGuardedValue won't be used in this case anyway
2022-08-09 07:54:32 +02:00
static int minGuardedValues [ ] = { 6500 , 4167 , 3000 , 1833 , 1333 } ;
minGuardedValue = minGuardedValues [ monsterStrength ] ;
2023-06-08 19:51:21 +02:00
auto valueComparator = [ ] ( const CTreasureInfo & lhs , const CTreasureInfo & rhs ) - > bool
2022-08-09 07:54:32 +02:00
{
return lhs . max > rhs . max ;
} ;
2023-06-08 19:51:21 +02:00
auto restoreZoneLimits = [ ] ( const std : : vector < ObjectInfo * > & treasurePile )
2022-08-09 07:54:32 +02:00
{
2023-06-08 19:51:21 +02:00
for ( auto * oi : treasurePile )
2022-08-09 07:54:32 +02:00
{
oi - > maxPerZone + + ;
}
} ;
2023-06-08 19:51:21 +02:00
2022-08-09 07:54:32 +02:00
//place biggest treasures first at large distance, place smaller ones inbetween
auto treasureInfo = zone . getTreasureInfo ( ) ;
boost : : sort ( treasureInfo , valueComparator ) ;
2023-06-08 19:51:21 +02:00
2022-08-09 07:54:32 +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 ;
} ) ;
2023-06-08 19:51:21 +02:00
size_t size = 0 ;
{
Zone : : Lock lock ( zone . areaMutex ) ;
2023-06-10 14:57:25 +02:00
size = zone . getArea ( ) . getTiles ( ) . size ( ) ;
2023-06-08 19:51:21 +02:00
}
2022-08-09 07:54:32 +02:00
int totalDensity = 0 ;
2023-06-08 19:51:21 +02:00
2023-06-12 22:15:59 +02:00
for ( auto t = treasureInfo . begin ( ) ; t ! = treasureInfo . end ( ) ; t + + )
2022-08-09 07:54:32 +02:00
{
2023-06-08 19:51:21 +02:00
std : : vector < rmg : : Object > treasures ;
2022-08-09 07:54:32 +02:00
//discard objects with too high value to be ever placed
vstd : : erase_if ( possibleObjects , [ t ] ( const ObjectInfo & oi ) - > bool
{
2023-06-12 22:15:59 +02:00
return oi . value > t - > max ;
2022-08-09 07:54:32 +02:00
} ) ;
2023-06-08 19:51:21 +02:00
2023-06-12 22:15:59 +02:00
totalDensity + = t - > density ;
2023-06-08 19:51:21 +02:00
2023-06-12 22:15:59 +02:00
size_t count = size * t - > density / 500 ;
//Assure space for lesser treasures, if there are any left
if ( t ! = ( treasureInfo . end ( ) - 1 ) )
2023-06-10 14:57:25 +02:00
{
2023-06-12 22:15:59 +02:00
const int averageValue = ( t - > min + t - > max ) / 2 ;
if ( averageValue > 10000 )
{
//Will surely be guarded => larger piles => less space inbetween
vstd : : amin ( count , size * ( 10.f / 500 ) / ( std : : sqrt ( ( float ) averageValue / 10000 ) ) ) ;
}
2023-06-10 14:57:25 +02:00
}
2022-08-09 07:54:32 +02:00
//this is squared distance for optimization purposes
2023-06-08 19:51:21 +02:00
const float minDistance = std : : max < float > ( ( 125.f / totalDensity ) , 1.0f ) ;
2023-06-12 22:15:59 +02:00
size_t emergencyLoopFinish = 0 ;
while ( treasures . size ( ) < count & & emergencyLoopFinish < count )
2022-08-09 07:54:32 +02:00
{
2023-06-12 22:15:59 +02:00
auto treasurePileInfos = prepareTreasurePile ( * t ) ;
2023-06-08 19:51:21 +02:00
if ( treasurePileInfos . empty ( ) )
2022-08-28 10:54:06 +02:00
{
2023-06-12 22:15:59 +02:00
emergencyLoopFinish + + ; //Exit potentially infinite loop for bad settings
2022-08-28 10:54:06 +02:00
continue ;
}
2023-06-08 19:51:21 +02:00
int value = std : : accumulate ( treasurePileInfos . begin ( ) , treasurePileInfos . end ( ) , 0 , [ ] ( int v , const ObjectInfo * oi ) { return v + oi - > value ; } ) ;
for ( ui32 attempt = 0 ; attempt < = 2 ; attempt + + )
2022-08-09 07:54:32 +02:00
{
2023-06-08 19:51:21 +02:00
auto rmgObject = constructTreasurePile ( treasurePileInfos , attempt = = maxAttempts ) ;
if ( rmgObject . instances ( ) . empty ( ) ) //handle incorrect placement
2022-08-09 07:54:32 +02:00
{
2023-06-08 19:51:21 +02:00
restoreZoneLimits ( treasurePileInfos ) ;
continue ;
}
2023-02-11 18:05:02 +02:00
2023-06-08 19:51:21 +02:00
//guard treasure pile
bool guarded = isGuardNeededForTreasure ( value ) ;
if ( guarded )
guarded = manager . addGuard ( rmgObject , value ) ;
treasures . push_back ( rmgObject ) ;
break ;
2022-08-09 07:54:32 +02:00
}
2023-06-08 19:51:21 +02:00
}
for ( auto & rmgObject : treasures )
{
const bool guarded = rmgObject . isGuarded ( ) ;
for ( int attempt = 0 ; attempt < = maxAttempts ; )
2022-08-09 07:54:32 +02:00
{
2023-06-08 19:51:21 +02:00
auto path = rmg : : Path : : invalid ( ) ;
Zone : : Lock lock ( zone . areaMutex ) ; //We are going to subtract this area
auto possibleArea = zone . areaPossible ( ) ;
if ( guarded )
2022-08-09 07:54:32 +02:00
{
2023-06-08 19:51:21 +02:00
path = manager . placeAndConnectObject ( possibleArea , rmgObject , [ this , & rmgObject , & minDistance , & manager ] ( const int3 & tile )
{
auto ti = map . getTileInfo ( tile ) ;
if ( ti . getNearestObjectDistance ( ) < minDistance )
return - 1.f ;
for ( const auto & t : rmgObject . getArea ( ) . getTilesVector ( ) )
{
if ( map . getTileInfo ( t ) . getNearestObjectDistance ( ) < minDistance )
return - 1.f ;
}
auto guardedArea = rmgObject . instances ( ) . back ( ) - > getAccessibleArea ( ) ;
auto areaToBlock = rmgObject . getAccessibleArea ( true ) ;
areaToBlock . subtract ( guardedArea ) ;
if ( areaToBlock . overlap ( zone . freePaths ( ) ) | | areaToBlock . overlap ( manager . getVisitableArea ( ) ) )
return - 1.f ;
return ti . getNearestObjectDistance ( ) ;
} , guarded , false , ObjectManager : : OptimizeType : : DISTANCE ) ;
}
else
{
path = manager . placeAndConnectObject ( possibleArea , rmgObject , minDistance , guarded , false , ObjectManager : : OptimizeType : : DISTANCE ) ;
}
if ( path . valid ( ) )
{
//debug purposes
treasureArea . unite ( rmgObject . getArea ( ) ) ;
if ( guarded )
{
guards . unite ( rmgObject . instances ( ) . back ( ) - > getBlockedArea ( ) ) ;
auto guardedArea = rmgObject . instances ( ) . back ( ) - > getAccessibleArea ( ) ;
auto areaToBlock = rmgObject . getAccessibleArea ( true ) ;
areaToBlock . subtract ( guardedArea ) ;
treasureBlockArea . unite ( areaToBlock ) ;
}
zone . connectPath ( path ) ;
manager . placeObject ( rmgObject , guarded , true ) ;
break ;
}
else
{
+ + attempt ;
2022-08-09 07:54:32 +02:00
}
}
}
}
}
char TreasurePlacer : : dump ( const int3 & t )
{
if ( guards . contains ( t ) )
return ' ! ' ;
if ( treasureArea . contains ( t ) )
return ' $ ' ;
if ( treasureBlockArea . contains ( t ) )
return ' * ' ;
return Modificator : : dump ( t ) ;
}
2022-09-29 11:44:46 +02:00
void ObjectInfo : : setTemplate ( si32 type , si32 subtype , TerrainId terrainType )
2022-08-09 07:54:32 +02:00
{
auto templHandler = VLC - > objtypeh - > getHandlerFor ( type , subtype ) ;
if ( ! templHandler )
return ;
auto templates = templHandler - > getTemplates ( terrainType ) ;
if ( templates . empty ( ) )
return ;
templ = templates . front ( ) ;
}
2022-07-26 15:07:42 +02:00
VCMI_LIB_NAMESPACE_END