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-12-11 08:37:23 +02:00
# include "PrisonHeroPlacer.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"
2024-01-09 16:43:36 +02:00
# include "../../mapObjects/CQuest.h"
# include "../../mapObjects/MiscObjects.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
2023-12-24 10:36:26 +02:00
ObjectInfo : : ObjectInfo ( ) :
2024-01-19 16:17:59 +02:00
destroyObject ( [ ] ( CGObjectInstance * obj ) { } )
2023-12-24 10:36:26 +02:00
{
}
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 ) ;
2023-12-11 08:37:23 +02:00
DEPENDENCY_ALL ( PrisonHeroPlacer ) ;
2024-05-01 07:58:17 +02:00
DEPENDENCY ( RoadPlacer ) ;
2022-08-09 07:54:32 +02:00
}
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 ;
}
2024-01-01 16:37:48 +02:00
oi . generateObject = [ this , primaryID , secondaryID ] ( ) - > CGObjectInstance *
2022-08-09 07:54:32 +02:00
{
2024-01-01 16:37:48 +02:00
return VLC - > objtypeh - > getHandlerFor ( primaryID , secondaryID ) - > create ( map . mapInstance - > cb , nullptr ) ;
2023-03-28 17:53:08 +02:00
} ;
oi . value = rmgInfo . value ;
oi . probability = rmgInfo . rarity ;
2023-09-30 23:06:38 +02:00
oi . setTemplates ( primaryID , secondaryID , zone . getTerrainType ( ) ) ;
2023-03-28 17:53:08 +02:00
oi . maxPerZone = rmgInfo . zoneLimit ;
2023-09-30 23:06:38 +02:00
if ( ! oi . templates . empty ( ) )
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-12-11 08:37:23 +02:00
PrisonHeroPlacer * prisonHeroPlacer = nullptr ;
for ( auto & z : map . getZones ( ) )
{
2023-12-11 09:27:57 +02:00
prisonHeroPlacer = z . second - > getModificator < PrisonHeroPlacer > ( ) ;
if ( prisonHeroPlacer )
2023-12-11 08:37:23 +02:00
{
break ;
}
}
2023-06-07 13:00:24 +02:00
//prisons
//levels 1, 5, 10, 20, 30
2024-01-09 16:43:36 +02:00
static const 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-12-24 10:36:26 +02:00
ObjectInfo oi ; // Create new instance which will hold destructor operation
2023-06-07 13:00:24 +02:00
oi . value = generator . getConfig ( ) . prisonValues [ i ] ;
if ( oi . value > zone . getMaxTreasureValue ( ) )
{
continue ;
}
2024-01-19 16:17:59 +02:00
oi . generateObject = [ i , this , prisonHeroPlacer ] ( ) - > CGObjectInstance *
2023-06-07 13:00:24 +02:00
{
2023-12-11 08:37:23 +02:00
HeroTypeID hid = prisonHeroPlacer - > drawRandomHero ( ) ;
2023-06-07 13:00:24 +02:00
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : PRISON , 0 ) ;
2024-01-01 16:37:48 +02:00
auto * obj = dynamic_cast < CGHeroInstance * > ( factory - > create ( map . mapInstance - > cb , nullptr ) ) ;
2023-06-07 13:00:24 +02:00
2023-10-28 11:27:10 +02:00
obj - > setHeroType ( hid ) ; //will be initialized later
2023-06-07 13:00:24 +02:00
obj - > exp = generator . getConfig ( ) . prisonExperience [ i ] ;
obj - > setOwner ( PlayerColor : : NEUTRAL ) ;
return obj ;
} ;
2024-01-19 16:17:59 +02:00
oi . destroyObject = [ prisonHeroPlacer ] ( CGObjectInstance * obj )
{
// Hero can be used again
auto * hero = dynamic_cast < CGHeroInstance * > ( obj ) ;
prisonHeroPlacer - > restoreDrawnHero ( hero - > getHeroType ( ) ) ;
} ;
2023-09-30 23:06:38 +02:00
oi . setTemplates ( Obj : : PRISON , 0 , zone . getTerrainType ( ) ) ;
2023-06-07 13:00:24 +02:00
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 ;
2023-09-30 23:06:38 +02:00
if ( ! oi . templates . empty ( ) )
addObjectToRandomPool ( oi ) ;
2023-06-07 13:00:24 +02:00
}
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 ( ) ;
2023-12-31 23:43:35 +02:00
std : : vector < const CCreature * > creatures ; //native creatures for this zone
2024-05-17 00:05:51 +02:00
for ( auto const & cre : VLC - > creh - > objects )
2022-08-09 07:54:32 +02:00
{
2023-04-09 17:26:32 +02:00
if ( ! cre - > special & & cre - > getFaction ( ) = = zone . getTownType ( ) )
2022-08-09 07:54:32 +02:00
{
2023-12-31 23:43:35 +02:00
creatures . push_back ( cre . get ( ) ) ;
2022-08-09 07:54:32 +02:00
}
}
//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
2024-01-09 16:43:36 +02:00
static const MapObjectSubID elementalConfluxROE [ ] = { 7 , 13 , 16 , 47 } ;
2023-11-05 19:13:18 +02:00
for ( auto const & i : elementalConfluxROE )
2023-02-11 18:05:02 +02:00
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-09-30 23:06:38 +02:00
2024-01-01 16:37:48 +02:00
oi . generateObject = [ this , secondaryID , dwellingType ] ( ) - > CGObjectInstance *
2022-08-09 07:54:32 +02:00
{
2024-01-01 16:37:48 +02:00
auto * obj = VLC - > objtypeh - > getHandlerFor ( dwellingType , secondaryID ) - > create ( map . mapInstance - > cb , nullptr ) ;
2023-09-30 23:06:38 +02:00
obj - > tempOwner = PlayerColor : : NEUTRAL ;
return obj ;
} ;
oi . setTemplates ( dwellingType , secondaryID , zone . getTerrainType ( ) ) ;
if ( ! oi . templates . empty ( ) )
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 ) ;
2024-01-01 16:37:48 +02:00
auto * obj = dynamic_cast < CGArtifact * > ( factory - > create ( map . mapInstance - > cb , nullptr ) ) ;
2022-08-09 07:54:32 +02:00
std : : vector < SpellID > out ;
2024-05-17 00:05:51 +02:00
for ( auto spellID : VLC - > spellh - > getDefaultAllowed ( ) )
2022-08-09 07:54:32 +02:00
{
2024-05-17 00:05:51 +02:00
if ( map . isAllowedSpell ( spellID ) & & spellID . toSpell ( ) - > getLevel ( ) = = i + 1 )
out . push_back ( spellID ) ;
2022-08-09 07:54:32 +02:00
}
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 ;
} ;
2023-09-30 23:06:38 +02:00
oi . setTemplates ( Obj : : SPELL_SCROLL , 0 , zone . getTerrainType ( ) ) ;
2022-08-09 07:54:32 +02:00
oi . value = generator . getConfig ( ) . scrollValues [ i ] ;
oi . probability = 30 ;
2023-09-30 23:06:38 +02:00
if ( ! oi . templates . empty ( ) )
addObjectToRandomPool ( oi ) ;
2022-08-09 07:54:32 +02:00
}
//pandora box with gold
for ( int i = 1 ; i < 5 ; i + + )
{
2024-01-01 16:37:48 +02:00
oi . generateObject = [ this , i ] ( ) - > CGObjectInstance *
2022-08-09 07:54:32 +02:00
{
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : PANDORAS_BOX , 0 ) ;
2024-01-01 16:37:48 +02:00
auto * obj = dynamic_cast < CGPandoraBox * > ( factory - > create ( map . mapInstance - > cb , nullptr ) ) ;
2023-09-15 15:29:41 +02:00
Rewardable : : VisitInfo reward ;
reward . reward . resources [ EGameResID : : GOLD ] = i * 5000 ;
reward . visitType = Rewardable : : EEventType : : EVENT_FIRST_VISIT ;
obj - > configuration . info . push_back ( reward ) ;
2022-08-09 07:54:32 +02:00
return obj ;
} ;
2023-09-30 23:06:38 +02:00
oi . setTemplates ( Obj : : PANDORAS_BOX , 0 , zone . getTerrainType ( ) ) ;
2022-08-09 07:54:32 +02:00
oi . value = i * generator . getConfig ( ) . pandoraMultiplierGold ;
oi . probability = 5 ;
2023-09-30 23:06:38 +02:00
if ( ! oi . templates . empty ( ) )
addObjectToRandomPool ( oi ) ;
2022-08-09 07:54:32 +02:00
}
//pandora box with experience
for ( int i = 1 ; i < 5 ; i + + )
{
2024-01-01 16:37:48 +02:00
oi . generateObject = [ this , i ] ( ) - > CGObjectInstance *
2022-08-09 07:54:32 +02:00
{
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : PANDORAS_BOX , 0 ) ;
2024-01-01 16:37:48 +02:00
auto * obj = dynamic_cast < CGPandoraBox * > ( factory - > create ( map . mapInstance - > cb , nullptr ) ) ;
2023-09-15 15:29:41 +02:00
Rewardable : : VisitInfo reward ;
reward . reward . heroExperience = i * 5000 ;
reward . visitType = Rewardable : : EEventType : : EVENT_FIRST_VISIT ;
obj - > configuration . info . push_back ( reward ) ;
2022-08-09 07:54:32 +02:00
return obj ;
} ;
2023-09-30 23:06:38 +02:00
oi . setTemplates ( Obj : : PANDORAS_BOX , 0 , zone . getTerrainType ( ) ) ;
2022-08-09 07:54:32 +02:00
oi . value = i * generator . getConfig ( ) . pandoraMultiplierExperience ;
oi . probability = 20 ;
2023-09-30 23:06:38 +02:00
if ( ! oi . templates . empty ( ) )
addObjectToRandomPool ( oi ) ;
2022-08-09 07:54:32 +02:00
}
//pandora box with creatures
const std : : vector < int > & tierValues = generator . getConfig ( ) . pandoraCreatureValues ;
2023-12-31 23:43:35 +02:00
auto creatureToCount = [ tierValues ] ( const CCreature * creature ) - > int
2022-08-09 07:54:32 +02:00
{
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 ;
2024-01-01 16:37:48 +02:00
oi . generateObject = [ this , creature , creaturesAmount ] ( ) - > CGObjectInstance *
2022-08-09 07:54:32 +02:00
{
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : PANDORAS_BOX , 0 ) ;
2024-01-01 16:37:48 +02:00
auto * obj = dynamic_cast < CGPandoraBox * > ( factory - > create ( map . mapInstance - > cb , nullptr ) ) ;
2023-09-15 15:29:41 +02:00
Rewardable : : VisitInfo reward ;
reward . reward . creatures . emplace_back ( creature , creaturesAmount ) ;
reward . visitType = Rewardable : : EEventType : : EVENT_FIRST_VISIT ;
obj - > configuration . info . push_back ( reward ) ;
2022-08-09 07:54:32 +02:00
return obj ;
} ;
2023-09-30 23:06:38 +02:00
oi . setTemplates ( 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-09-30 23:06:38 +02:00
if ( ! oi . templates . empty ( ) )
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 ) ;
2024-01-01 16:37:48 +02:00
auto * obj = dynamic_cast < CGPandoraBox * > ( factory - > create ( map . mapInstance - > cb , nullptr ) ) ;
2023-02-11 18:05:02 +02:00
2023-12-31 23:43:35 +02:00
std : : vector < const CSpell * > spells ;
2024-05-17 00:05:51 +02:00
for ( auto spellID : VLC - > spellh - > getDefaultAllowed ( ) )
2022-08-09 07:54:32 +02:00
{
2024-05-17 00:05:51 +02:00
if ( map . isAllowedSpell ( spellID ) & & spellID . toSpell ( ) - > getLevel ( ) = = i )
spells . push_back ( spellID . toSpell ( ) ) ;
2022-08-09 07:54:32 +02:00
}
2023-05-20 11:46:32 +02:00
RandomGeneratorUtil : : randomShuffle ( spells , zone . getRand ( ) ) ;
2023-09-15 15:29:41 +02:00
Rewardable : : VisitInfo reward ;
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
{
2023-09-15 15:29:41 +02:00
reward . reward . spells . push_back ( spells [ j ] - > id ) ;
2022-08-09 07:54:32 +02:00
}
2023-09-15 15:29:41 +02:00
reward . visitType = Rewardable : : EEventType : : EVENT_FIRST_VISIT ;
obj - > configuration . info . push_back ( reward ) ;
2022-08-09 07:54:32 +02:00
return obj ;
} ;
2023-09-30 23:06:38 +02:00
oi . setTemplates ( Obj : : PANDORAS_BOX , 0 , zone . getTerrainType ( ) ) ;
2022-08-09 07:54:32 +02:00
oi . value = ( i + 1 ) * generator . getConfig ( ) . pandoraMultiplierSpells ; //5000 - 15000
oi . probability = 2 ;
2023-09-30 23:06:38 +02:00
if ( ! oi . templates . empty ( ) )
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 ) ;
2024-01-01 16:37:48 +02:00
auto * obj = dynamic_cast < CGPandoraBox * > ( factory - > create ( map . mapInstance - > cb , nullptr ) ) ;
2023-02-11 18:05:02 +02:00
2023-12-31 23:43:35 +02:00
std : : vector < const CSpell * > spells ;
2024-05-17 00:05:51 +02:00
for ( auto spellID : VLC - > spellh - > getDefaultAllowed ( ) )
2022-08-09 07:54:32 +02:00
{
2024-05-17 00:05:51 +02:00
if ( map . isAllowedSpell ( spellID ) & & spellID . toSpell ( ) - > hasSchool ( SpellSchool ( i ) ) )
spells . push_back ( spellID . toSpell ( ) ) ;
2022-08-09 07:54:32 +02:00
}
2023-05-20 11:46:32 +02:00
RandomGeneratorUtil : : randomShuffle ( spells , zone . getRand ( ) ) ;
2023-09-15 15:29:41 +02:00
Rewardable : : VisitInfo reward ;
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
{
2023-09-15 15:29:41 +02:00
reward . reward . spells . push_back ( spells [ j ] - > id ) ;
2022-08-09 07:54:32 +02:00
}
2023-09-15 15:29:41 +02:00
reward . visitType = Rewardable : : EEventType : : EVENT_FIRST_VISIT ;
obj - > configuration . info . push_back ( reward ) ;
2022-08-09 07:54:32 +02:00
return obj ;
} ;
2023-09-30 23:06:38 +02:00
oi . setTemplates ( Obj : : PANDORAS_BOX , 0 , zone . getTerrainType ( ) ) ;
2022-08-09 07:54:32 +02:00
oi . value = generator . getConfig ( ) . pandoraSpellSchool ;
oi . probability = 2 ;
2023-09-30 23:06:38 +02:00
if ( ! oi . templates . empty ( ) )
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 ) ;
2024-01-01 16:37:48 +02:00
auto * obj = dynamic_cast < CGPandoraBox * > ( factory - > create ( map . mapInstance - > cb , nullptr ) ) ;
2023-02-11 18:05:02 +02:00
2023-12-31 23:43:35 +02:00
std : : vector < const CSpell * > spells ;
2024-05-17 00:05:51 +02:00
for ( auto spellID : VLC - > spellh - > getDefaultAllowed ( ) )
2022-08-09 07:54:32 +02:00
{
2024-05-17 00:05:51 +02:00
if ( map . isAllowedSpell ( spellID ) )
spells . push_back ( spellID . toSpell ( ) ) ;
2022-08-09 07:54:32 +02:00
}
2023-05-20 11:46:32 +02:00
RandomGeneratorUtil : : randomShuffle ( spells , zone . getRand ( ) ) ;
2023-09-15 15:29:41 +02:00
Rewardable : : VisitInfo reward ;
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
{
2023-09-15 15:29:41 +02:00
reward . reward . spells . push_back ( spells [ j ] - > id ) ;
2022-08-09 07:54:32 +02:00
}
2023-09-15 15:29:41 +02:00
reward . visitType = Rewardable : : EEventType : : EVENT_FIRST_VISIT ;
obj - > configuration . info . push_back ( reward ) ;
2022-08-09 07:54:32 +02:00
return obj ;
} ;
2023-09-30 23:06:38 +02:00
oi . setTemplates ( Obj : : PANDORAS_BOX , 0 , zone . getTerrainType ( ) ) ;
2022-08-09 07:54:32 +02:00
oi . value = generator . getConfig ( ) . pandoraSpell60 ;
oi . probability = 2 ;
2023-09-30 23:06:38 +02:00
if ( ! oi . templates . empty ( ) )
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
2024-01-19 16:17:59 +02:00
auto setRandomArtifact = [ qap ] ( CGSeerHut * obj )
2024-01-17 08:18:14 +02:00
{
ArtifactID artid = qap - > drawRandomArtifact ( ) ;
obj - > quest - > mission . artifacts . push_back ( artid ) ;
qap - > addQuestArtifact ( artid ) ;
} ;
2024-01-19 16:17:59 +02:00
auto destroyObject = [ qap ] ( CGObjectInstance * obj )
{
auto * seer = dynamic_cast < CGSeerHut * > ( obj ) ;
// Artifact can be used again
ArtifactID artid = seer - > quest - > mission . artifacts . front ( ) ;
qap - > addRandomArtifact ( artid ) ;
qap - > removeQuestArtifact ( artid ) ;
} ;
2024-01-17 08:18:14 +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
2024-01-16 18:15:35 +02:00
// FIXME: Remove duplicated code for gold, exp and creaure reward
2024-01-16 20:47:09 +02:00
oi . generateObject = [ cb = map . mapInstance - > cb , creature , creaturesAmount , randomAppearance , setRandomArtifact ] ( ) - > CGObjectInstance *
2022-08-09 07:54:32 +02:00
{
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : SEER_HUT , randomAppearance ) ;
2024-01-16 20:47:09 +02:00
auto * obj = dynamic_cast < CGSeerHut * > ( factory - > create ( cb , nullptr ) ) ;
2023-09-13 01:40:07 +02:00
2023-09-15 15:29:41 +02:00
Rewardable : : VisitInfo reward ;
reward . reward . creatures . emplace_back ( creature - > getId ( ) , creaturesAmount ) ;
reward . visitType = Rewardable : : EEventType : : EVENT_FIRST_VISIT ;
obj - > configuration . info . push_back ( reward ) ;
2023-10-09 19:15:34 +02:00
2024-01-17 08:18:14 +02:00
setRandomArtifact ( obj ) ;
2022-08-09 07:54:32 +02:00
return obj ;
} ;
2024-01-19 16:17:59 +02:00
oi . destroyObject = destroyObject ;
2023-04-29 11:46:03 +02:00
oi . probability = 3 ;
2023-09-30 23:06:38 +02:00
oi . setTemplates ( 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
{
2023-09-30 23:06:38 +02:00
if ( ! oi . templates . empty ( ) )
possibleSeerHuts . push_back ( oi ) ;
2023-04-29 11:46:03 +02:00
}
2022-08-09 07:54:32 +02:00
}
2024-01-09 16:43:36 +02:00
static const int seerLevels = std : : min ( generator . getConfig ( ) . questValues . size ( ) , generator . getConfig ( ) . questRewardValues . size ( ) ) ;
2024-06-24 03:23:26 +02:00
for ( int i = 0 ; i < seerLevels ; i + + ) //seems that code for exp and gold reward is similar
2022-08-09 07:54:32 +02:00
{
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-09-30 23:06:38 +02:00
oi . setTemplates ( Obj : : SEER_HUT , randomAppearance , zone . getTerrainType ( ) ) ;
2022-08-09 07:54:32 +02:00
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
2024-01-17 08:45:53 +02:00
oi . generateObject = [ i , randomAppearance , this , setRandomArtifact ] ( ) - > CGObjectInstance *
2022-08-09 07:54:32 +02:00
{
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : SEER_HUT , randomAppearance ) ;
2024-01-01 16:37:48 +02:00
auto * obj = dynamic_cast < CGSeerHut * > ( factory - > create ( map . mapInstance - > cb , nullptr ) ) ;
2023-09-13 01:40:07 +02:00
2023-09-15 15:29:41 +02:00
Rewardable : : VisitInfo reward ;
reward . reward . heroExperience = generator . getConfig ( ) . questRewardValues [ i ] ;
reward . visitType = Rewardable : : EEventType : : EVENT_FIRST_VISIT ;
obj - > configuration . info . push_back ( reward ) ;
2024-01-17 08:18:14 +02:00
setRandomArtifact ( obj ) ;
2022-08-09 07:54:32 +02:00
return obj ;
} ;
2024-01-19 16:17:59 +02:00
oi . destroyObject = destroyObject ;
2022-08-09 07:54:32 +02:00
2023-09-30 23:06:38 +02:00
if ( ! oi . templates . empty ( ) )
possibleSeerHuts . push_back ( oi ) ;
2022-08-09 07:54:32 +02:00
2024-01-17 08:45:53 +02:00
oi . generateObject = [ i , randomAppearance , this , setRandomArtifact ] ( ) - > CGObjectInstance *
2022-08-09 07:54:32 +02:00
{
auto factory = VLC - > objtypeh - > getHandlerFor ( Obj : : SEER_HUT , randomAppearance ) ;
2024-01-01 16:37:48 +02:00
auto * obj = dynamic_cast < CGSeerHut * > ( factory - > create ( map . mapInstance - > cb , nullptr ) ) ;
2023-09-13 01:40:07 +02:00
2023-09-15 15:29:41 +02:00
Rewardable : : VisitInfo reward ;
reward . reward . resources [ EGameResID : : GOLD ] = generator . getConfig ( ) . questRewardValues [ i ] ;
reward . visitType = Rewardable : : EEventType : : EVENT_FIRST_VISIT ;
obj - > configuration . info . push_back ( reward ) ;
2022-08-09 07:54:32 +02:00
2024-01-17 08:18:14 +02:00
setRandomArtifact ( obj ) ;
2022-08-09 07:54:32 +02:00
return obj ;
} ;
2024-01-19 16:17:59 +02:00
oi . destroyObject = destroyObject ;
2022-08-09 07:54:32 +02:00
2023-09-30 23:06:38 +02:00
if ( ! oi . templates . empty ( ) )
possibleSeerHuts . push_back ( oi ) ;
2023-04-29 11:46:03 +02:00
}
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-12-19 10:24:43 +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 ;
2023-09-30 23:06:38 +02:00
bool visitableFromTop = true ;
for ( auto & t : oi - > templates )
if ( ! t - > isVisitableFromTop ( ) )
visitableFromTop = false ;
if ( visitableFromTop )
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 ;
2023-12-19 10:24:43 +02:00
if ( currentValue > = minValue )
{
// 50% chance to end right here
if ( zone . getRand ( ) . nextInt ( ) & 1 )
break ;
}
2022-08-09 07:54:32 +02:00
}
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 ( ) ;
2023-12-06 21:49:28 +02:00
auto entrableArea = rmgObject . getEntrableArea ( ) ;
2023-12-21 13:29:45 +02:00
auto accessibleArea = rmgObject . getAccessibleArea ( ) ;
2023-12-06 21:49:28 +02:00
2022-08-09 07:54:32 +02:00
if ( rmgObject . instances ( ) . empty ( ) )
2023-12-21 13:29:45 +02:00
{
2024-05-01 11:34:34 +02:00
rmgObject . setValue ( 0 ) ;
2023-12-21 13:29:45 +02:00
accessibleArea . add ( int3 ( ) ) ;
}
2022-08-09 07:54:32 +02:00
auto * object = oi - > generateObject ( ) ;
2023-09-30 23:06:38 +02:00
if ( oi - > templates . empty ( ) )
2023-12-11 08:37:23 +02:00
{
logGlobal - > warn ( " Deleting randomized object with no templates: %s " , object - > getObjectName ( ) ) ;
2024-01-19 16:17:59 +02:00
oi - > destroyObject ( object ) ;
2023-12-11 09:27:57 +02:00
delete object ;
2023-09-30 23:06:38 +02:00
continue ;
2023-12-11 08:37:23 +02:00
}
2023-09-30 23:06:38 +02:00
2023-12-21 10:58:39 +02:00
auto templates = object - > getObjectHandler ( ) - > getMostSpecificTemplates ( zone . getTerrainType ( ) ) ;
if ( templates . empty ( ) )
{
throw rmgException ( boost : : str ( boost : : format ( " Did not find template for object (%d,%d) at % s " ) % object->ID % object->subID % zone.getTerrainType().encode(zone.getTerrainType()))) ;
}
2023-12-06 21:49:28 +02:00
2023-12-21 10:58:39 +02:00
object - > appearance = * RandomGeneratorUtil : : nextItem ( templates , zone . getRand ( ) ) ;
2023-12-06 21:49:28 +02:00
2023-12-21 13:29:45 +02:00
//Put object in accessible area next to entrable area (excluding blockvis tiles)
if ( ! entrableArea . empty ( ) )
2023-12-06 21:49:28 +02:00
{
2023-12-21 13:29:45 +02:00
auto entrableBorder = entrableArea . getBorderOutside ( ) ;
accessibleArea . erase_if ( [ & ] ( const int3 & tile )
{
return ! entrableBorder . count ( tile ) ;
} ) ;
2023-12-06 21:49:28 +02:00
}
2022-08-09 07:54:32 +02:00
auto & instance = rmgObject . addInstance ( * object ) ;
2024-05-01 10:24:21 +02:00
rmgObject . setValue ( rmgObject . getValue ( ) + oi - > value ) ;
2024-01-19 17:26:51 +02:00
instance . onCleared = oi - > destroyObject ;
2022-08-09 07:54:32 +02:00
do
{
2023-12-21 13:29:45 +02:00
if ( accessibleArea . empty ( ) )
2022-08-09 07:54:32 +02:00
{
//fail - fallback
rmgObject . clear ( ) ;
return rmgObject ;
}
2022-08-20 12:17:27 +02:00
std : : vector < int3 > bestPositions ;
2023-12-21 13:29:45 +02:00
if ( densePlacement & & ! entrableArea . empty ( ) )
2022-08-20 12:17:27 +02:00
{
2023-12-21 13:29:45 +02:00
// Choose positon which has access to as many entrable tiles as possible
2022-08-20 12:17:27 +02:00
int bestPositionsWeight = std : : numeric_limits < int > : : max ( ) ;
2023-12-21 13:29:45 +02:00
for ( const auto & t : accessibleArea . getTilesVector ( ) )
2022-08-20 12:17:27 +02:00
{
instance . setPosition ( t ) ;
2023-12-06 21:49:28 +02:00
2023-12-21 13:29:45 +02:00
auto currentAccessibleArea = rmgObject . getAccessibleArea ( ) ;
auto currentEntrableBorder = rmgObject . getEntrableArea ( ) . getBorderOutside ( ) ;
currentAccessibleArea . erase_if ( [ & ] ( const int3 & tile )
{
return ! currentEntrableBorder . count ( tile ) ;
} ) ;
size_t w = currentAccessibleArea . getTilesVector ( ) . size ( ) ;
if ( w > bestPositionsWeight )
2022-08-20 12:17:27 +02:00
{
2023-12-06 21:49:28 +02:00
// Minimum 1 position must be entrable
2022-08-20 12:17:27 +02:00
bestPositions . clear ( ) ;
bestPositions . push_back ( t ) ;
bestPositionsWeight = w ;
}
else if ( w = = bestPositionsWeight )
{
bestPositions . push_back ( t ) ;
}
}
}
2023-12-06 21:49:28 +02:00
if ( bestPositions . empty ( ) )
2022-08-20 12:17:27 +02:00
{
2023-12-21 13:29:45 +02:00
bestPositions = accessibleArea . getTilesVector ( ) ;
2022-08-20 12:17:27 +02:00
}
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 )
{
2023-12-06 10:49:41 +02:00
if ( instance . object ( ) . appearance - > isVisitableFromTop ( ) & & ! instance . object ( ) . isBlockedVisitable ( ) )
2022-08-09 07:54:32 +02:00
instanceAccessibleArea . add ( instance . getVisitablePosition ( ) ) ;
}
2023-12-11 08:37:23 +02:00
//Do not clean up after first object
2022-08-09 07:54:32 +02:00
if ( rmgObject . instances ( ) . size ( ) = = 1 )
break ;
2023-12-06 10:49:41 +02:00
2023-12-21 13:29:45 +02:00
if ( ! blockedArea . overlap ( instance . getBlockedArea ( ) ) & & accessibleArea . overlap ( instanceAccessibleArea ) )
2022-08-09 07:54:32 +02:00
break ;
2023-12-06 21:49:28 +02:00
2022-08-09 07:54:32 +02:00
//fail - new position
2023-12-21 13:29:45 +02:00
accessibleArea . erase ( nextPos ) ;
2022-08-09 07:54:32 +02:00
} 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
2023-09-30 23:06:38 +02:00
bool visitableFromTop = true ;
for ( auto & t : oi . templates )
if ( ! t - > isVisitableFromTop ( ) )
visitableFromTop = false ;
if ( ! visitableFromTop & & ! 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
2024-01-09 16:43:36 +02:00
static const int minGuardedValues [ ] = { 6500 , 4167 , 3000 , 1833 , 1333 } ;
2022-08-09 07:54:32 +02:00
minGuardedValue = minGuardedValues [ monsterStrength ] ;
2024-05-01 10:24:21 +02:00
const auto blockingGuardMaxValue = zone . getMaxTreasureValue ( ) / 3 ;
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 + + ;
}
} ;
2024-05-01 07:58:17 +02:00
rmg : : Area roads ;
auto rp = zone . getModificator < RoadPlacer > ( ) ;
if ( rp )
{
roads = rp - > getRoads ( ) ;
}
rmg : : Area nextToRoad ( roads . getBorderOutside ( ) ) ;
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
2024-03-27 07:16:48 +02:00
const size_t size = zone . area ( ) - > getTilesVector ( ) . 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
2024-03-20 13:39:07 +02:00
const int DENSITY_CONSTANT = 400 ;
2023-12-12 13:50:27 +02:00
size_t count = ( size * t - > density ) / DENSITY_CONSTANT ;
2023-06-12 22:15:59 +02:00
2024-03-20 16:35:06 +02:00
const float minDistance = std : : max < float > ( std : : sqrt ( std : : min < ui32 > ( t - > min , 30000 ) / 10.0f / totalDensity ) , 1.0f ) ;
2023-06-08 19:51:21 +02:00
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 ; } ) ;
2024-06-24 03:23:26 +02:00
const ui32 maxPileGenerationAttempts = 2 ;
for ( ui32 attempt = 0 ; attempt < maxPileGenerationAttempts ; attempt + + )
2022-08-09 07:54:32 +02:00
{
2023-06-08 19:51:21 +02:00
auto rmgObject = constructTreasurePile ( treasurePileInfos , attempt = = maxAttempts ) ;
2023-12-07 18:41:09 +02:00
if ( rmgObject . instances ( ) . empty ( ) )
2022-08-09 07:54:32 +02:00
{
2024-06-24 03:23:26 +02:00
// Restore once if all attempts failed
if ( attempt = = ( maxPileGenerationAttempts - 1 ) )
2023-12-07 18:41:09 +02:00
{
restoreZoneLimits ( treasurePileInfos ) ;
}
2023-06-08 19:51:21 +02:00
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 ( ) ;
2023-12-12 08:40:54 +02:00
auto path = rmg : : Path : : invalid ( ) ;
2023-06-08 19:51:21 +02:00
2023-12-12 08:40:54 +02:00
{
2024-03-27 07:16:48 +02:00
Zone : : Lock lock ( zone . areaMutex ) ; //We are going to subtract this area
2024-03-27 09:03:19 +02:00
2024-03-27 07:16:48 +02:00
auto searchArea = zone . areaPossible ( ) . get ( ) ;
searchArea . erase_if ( [ this , & minDistance ] ( const int3 & tile ) - > bool
{
auto ti = map . getTileInfo ( tile ) ;
return ( ti . getNearestObjectDistance ( ) < minDistance ) ;
} ) ;
2023-06-08 19:51:21 +02:00
2024-03-27 07:16:48 +02:00
if ( guarded )
{
2024-05-01 07:58:17 +02:00
searchArea . subtract ( roads ) ;
2024-05-01 10:24:21 +02:00
path = manager . placeAndConnectObject ( searchArea , rmgObject , [ this , & rmgObject , & minDistance , & manager , blockingGuardMaxValue , & roads , & nextToRoad ] ( const int3 & tile )
2023-06-08 19:51:21 +02:00
{
2024-03-27 07:16:48 +02:00
float bestDistance = 10e9 ;
for ( const auto & t : rmgObject . getArea ( ) . getTilesVector ( ) )
{
float distance = map . getTileInfo ( t ) . getNearestObjectDistance ( ) ;
if ( distance < minDistance )
return - 1.f ;
else
vstd : : amin ( bestDistance , distance ) ;
}
2024-05-01 10:24:21 +02:00
// Guard cannot be adjacent to road, but blocked side of an object could be
if ( rmgObject . getValue ( ) > blockingGuardMaxValue & & nextToRoad . contains ( rmgObject . getGuardPos ( ) ) )
2024-05-01 07:58:17 +02:00
{
return - 1.f ;
}
2024-03-27 07:16:48 +02:00
const auto & guardedArea = rmgObject . instances ( ) . back ( ) - > getAccessibleArea ( ) ;
const auto areaToBlock = rmgObject . getAccessibleArea ( true ) - guardedArea ;
2024-05-01 10:24:21 +02:00
if ( zone . freePaths ( ) - > overlap ( areaToBlock ) | | roads . overlap ( areaToBlock ) | | manager . getVisitableArea ( ) . overlap ( areaToBlock ) )
2023-06-08 19:51:21 +02:00
return - 1.f ;
2024-05-01 12:15:07 +02:00
// Add huge penalty for objects hiding roads
if ( rmgObject . getBorderAbove ( ) . overlap ( roads ) )
bestDistance / = 10.0f ;
2024-03-27 07:16:48 +02:00
return bestDistance ;
} , guarded , false , ObjectManager : : OptimizeType : : BOTH ) ;
}
else
{
2024-05-01 09:16:10 +02:00
// Do not place non-removable objects on roads
if ( ! rmgObject . getRemovableArea ( ) . contains ( rmgObject . getArea ( ) ) )
{
searchArea . subtract ( roads ) ;
}
2024-03-27 07:16:48 +02:00
path = manager . placeAndConnectObject ( searchArea , rmgObject , minDistance , guarded , false , ObjectManager : : OptimizeType : : DISTANCE ) ;
}
2023-12-12 08:40:54 +02:00
}
2023-06-08 19:51:21 +02:00
2023-12-12 08:40:54 +02:00
if ( path . valid ( ) )
{
2024-03-20 14:51:16 +02:00
# ifdef TREASURE_PLACER_LOG
2023-12-12 08:40:54 +02:00
treasureArea . unite ( rmgObject . getArea ( ) ) ;
if ( guarded )
2023-06-08 19:51:21 +02:00
{
2023-12-12 08:40:54 +02:00
guards . unite ( rmgObject . instances ( ) . back ( ) - > getBlockedArea ( ) ) ;
auto guardedArea = rmgObject . instances ( ) . back ( ) - > getAccessibleArea ( ) ;
2023-12-18 14:52:03 +02:00
auto areaToBlock = rmgObject . getAccessibleArea ( true ) - guardedArea ;
2023-12-12 08:40:54 +02:00
treasureBlockArea . unite ( areaToBlock ) ;
2022-08-09 07:54:32 +02:00
}
2024-03-20 14:51:16 +02:00
# endif
2023-12-12 08:40:54 +02:00
zone . connectPath ( path ) ;
manager . placeObject ( rmgObject , guarded , true ) ;
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 ) ;
}
2023-11-05 19:13:18 +02:00
void ObjectInfo : : setTemplates ( MapObjectID type , MapObjectSubID subtype , TerrainId terrainType )
2022-08-09 07:54:32 +02:00
{
auto templHandler = VLC - > objtypeh - > getHandlerFor ( type , subtype ) ;
if ( ! templHandler )
return ;
2023-09-30 23:06:38 +02:00
templates = templHandler - > getTemplates ( terrainType ) ;
2022-08-09 07:54:32 +02:00
}
2022-07-26 15:07:42 +02:00
VCMI_LIB_NAMESPACE_END