2023-10-19 16:19:09 +02:00
/*
* CHeroHandler . 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 "CHeroHandler.h"
# include "CGeneralTextHandler.h"
# include "filesystem/Filesystem.h"
# include "VCMI_Lib.h"
# include "constants/StringConstants.h"
# include "battle/BattleHex.h"
# include "CCreatureHandler.h"
# include "GameSettings.h"
2024-01-31 00:17:40 +02:00
# include "CRandomGenerator.h"
2023-10-19 16:19:09 +02:00
# include "CTownHandler.h"
# include "CSkillHandler.h"
# include "BattleFieldHandler.h"
# include "bonuses/Limiters.h"
# include "bonuses/Updaters.h"
2024-02-14 15:48:06 +02:00
# include "json/JsonBonus.h"
2024-02-11 23:09:01 +02:00
# include "json/JsonUtils.h"
2023-10-19 16:19:09 +02:00
# include "mapObjectConstructors/AObjectTypeHandler.h"
# include "mapObjectConstructors/CObjectClassesHandler.h"
# include "modding/IdentifierStorage.h"
VCMI_LIB_NAMESPACE_BEGIN
CHero : : CHero ( ) = default ;
CHero : : ~ CHero ( ) = default ;
int32_t CHero : : getIndex ( ) const
{
return ID . getNum ( ) ;
}
int32_t CHero : : getIconIndex ( ) const
{
return imageIndex ;
}
std : : string CHero : : getJsonKey ( ) const
{
2024-01-16 21:02:39 +02:00
return modScope + ' : ' + identifier ;
2023-10-19 16:19:09 +02:00
}
HeroTypeID CHero : : getId ( ) const
{
return ID ;
}
std : : string CHero : : getNameTranslated ( ) const
{
return VLC - > generaltexth - > translate ( getNameTextID ( ) ) ;
}
std : : string CHero : : getBiographyTranslated ( ) const
{
return VLC - > generaltexth - > translate ( getBiographyTextID ( ) ) ;
}
std : : string CHero : : getSpecialtyNameTranslated ( ) const
{
return VLC - > generaltexth - > translate ( getSpecialtyNameTextID ( ) ) ;
}
std : : string CHero : : getSpecialtyDescriptionTranslated ( ) const
{
return VLC - > generaltexth - > translate ( getSpecialtyDescriptionTextID ( ) ) ;
}
std : : string CHero : : getSpecialtyTooltipTranslated ( ) const
{
return VLC - > generaltexth - > translate ( getSpecialtyTooltipTextID ( ) ) ;
}
std : : string CHero : : getNameTextID ( ) const
{
return TextIdentifier ( " hero " , modScope , identifier , " name " ) . get ( ) ;
}
std : : string CHero : : getBiographyTextID ( ) const
{
return TextIdentifier ( " hero " , modScope , identifier , " biography " ) . get ( ) ;
}
std : : string CHero : : getSpecialtyNameTextID ( ) const
{
return TextIdentifier ( " hero " , modScope , identifier , " specialty " , " name " ) . get ( ) ;
}
std : : string CHero : : getSpecialtyDescriptionTextID ( ) const
{
return TextIdentifier ( " hero " , modScope , identifier , " specialty " , " description " ) . get ( ) ;
}
std : : string CHero : : getSpecialtyTooltipTextID ( ) const
{
return TextIdentifier ( " hero " , modScope , identifier , " specialty " , " tooltip " ) . get ( ) ;
}
void CHero : : registerIcons ( const IconRegistar & cb ) const
{
cb ( getIconIndex ( ) , 0 , " UN32 " , iconSpecSmall ) ;
cb ( getIconIndex ( ) , 0 , " UN44 " , iconSpecLarge ) ;
cb ( getIconIndex ( ) , 0 , " PORTRAITSLARGE " , portraitLarge ) ;
cb ( getIconIndex ( ) , 0 , " PORTRAITSSMALL " , portraitSmall ) ;
}
void CHero : : updateFrom ( const JsonNode & data )
{
//todo: CHero::updateFrom
}
void CHero : : serializeJson ( JsonSerializeFormat & handler )
{
}
SecondarySkill CHeroClass : : chooseSecSkill ( const std : : set < SecondarySkill > & possibles , CRandomGenerator & rand ) const //picks secondary skill out from given possibilities
{
2023-11-20 18:44:27 +02:00
assert ( ! possibles . empty ( ) ) ;
if ( possibles . size ( ) = = 1 )
return * possibles . begin ( ) ;
2023-10-19 16:19:09 +02:00
int totalProb = 0 ;
for ( const auto & possible : possibles )
2023-11-02 18:45:46 +02:00
if ( secSkillProbability . count ( possible ) ! = 0 )
totalProb + = secSkillProbability . at ( possible ) ;
2023-11-20 18:44:27 +02:00
if ( totalProb = = 0 ) // may trigger if set contains only banned skills (0 probability)
return * RandomGeneratorUtil : : nextItem ( possibles , rand ) ;
auto ran = rand . nextInt ( totalProb - 1 ) ;
for ( const auto & possible : possibles )
2023-10-19 16:19:09 +02:00
{
2023-11-20 18:44:27 +02:00
if ( secSkillProbability . count ( possible ) ! = 0 )
ran - = secSkillProbability . at ( possible ) ;
if ( ran < 0 )
return possible ;
2023-10-19 16:19:09 +02:00
}
2023-11-20 18:44:27 +02:00
assert ( 0 ) ; // should not be possible
2023-10-19 16:19:09 +02:00
return * possibles . begin ( ) ;
}
bool CHeroClass : : isMagicHero ( ) const
{
return affinity = = MAGIC ;
}
2024-01-19 23:02:00 +02:00
int CHeroClass : : tavernProbability ( FactionID targetFaction ) const
2023-12-31 23:43:35 +02:00
{
2024-01-19 23:02:00 +02:00
auto it = selectionProbability . find ( targetFaction ) ;
2024-01-10 21:30:12 +02:00
if ( it ! = selectionProbability . end ( ) )
return it - > second ;
2023-12-31 23:43:35 +02:00
return 0 ;
}
2023-10-19 16:19:09 +02:00
EAlignment CHeroClass : : getAlignment ( ) const
{
2023-11-02 17:48:48 +02:00
return VLC - > factions ( ) - > getById ( faction ) - > getAlignment ( ) ;
2023-10-19 16:19:09 +02:00
}
int32_t CHeroClass : : getIndex ( ) const
{
return id . getNum ( ) ;
}
int32_t CHeroClass : : getIconIndex ( ) const
{
return getIndex ( ) ;
}
std : : string CHeroClass : : getJsonKey ( ) const
{
2024-01-16 21:02:39 +02:00
return modScope + ' : ' + identifier ;
2023-10-19 16:19:09 +02:00
}
HeroClassID CHeroClass : : getId ( ) const
{
return id ;
}
void CHeroClass : : registerIcons ( const IconRegistar & cb ) const
{
}
std : : string CHeroClass : : getNameTranslated ( ) const
{
return VLC - > generaltexth - > translate ( getNameTextID ( ) ) ;
}
std : : string CHeroClass : : getNameTextID ( ) const
{
return TextIdentifier ( " heroClass " , modScope , identifier , " name " ) . get ( ) ;
}
void CHeroClass : : updateFrom ( const JsonNode & data )
{
//TODO: CHeroClass::updateFrom
}
void CHeroClass : : serializeJson ( JsonSerializeFormat & handler )
{
}
CHeroClass : : CHeroClass ( ) :
faction ( 0 ) ,
affinity ( 0 ) ,
defaultTavernChance ( 0 ) ,
commander ( nullptr )
{
}
void CHeroClassHandler : : fillPrimarySkillData ( const JsonNode & node , CHeroClass * heroClass , PrimarySkill pSkill ) const
{
2023-11-02 16:56:02 +02:00
const auto & skillName = NPrimarySkill : : names [ pSkill . getNum ( ) ] ;
2023-10-19 16:19:09 +02:00
auto currentPrimarySkillValue = static_cast < int > ( node [ " primarySkills " ] [ skillName ] . Integer ( ) ) ;
//minimal value is 0 for attack and defense and 1 for spell power and knowledge
auto primarySkillLegalMinimum = ( pSkill = = PrimarySkill : : ATTACK | | pSkill = = PrimarySkill : : DEFENSE ) ? 0 : 1 ;
if ( currentPrimarySkillValue < primarySkillLegalMinimum )
{
logMod - > error ( " Hero class '%s' has incorrect initial value '%d' for skill '%s'. Value '%d' will be used instead. " ,
heroClass - > getNameTranslated ( ) , currentPrimarySkillValue , skillName , primarySkillLegalMinimum ) ;
currentPrimarySkillValue = primarySkillLegalMinimum ;
}
heroClass - > primarySkillInitial . push_back ( currentPrimarySkillValue ) ;
heroClass - > primarySkillLowLevel . push_back ( static_cast < int > ( node [ " lowLevelChance " ] [ skillName ] . Float ( ) ) ) ;
heroClass - > primarySkillHighLevel . push_back ( static_cast < int > ( node [ " highLevelChance " ] [ skillName ] . Float ( ) ) ) ;
}
const std : : vector < std : : string > & CHeroClassHandler : : getTypeNames ( ) const
{
static const std : : vector < std : : string > typeNames = { " heroClass " } ;
return typeNames ;
}
CHeroClass * CHeroClassHandler : : loadFromJson ( const std : : string & scope , const JsonNode & node , const std : : string & identifier , size_t index )
{
assert ( identifier . find ( ' : ' ) = = std : : string : : npos ) ;
assert ( ! scope . empty ( ) ) ;
std : : string affinityStr [ 2 ] = { " might " , " magic " } ;
auto * heroClass = new CHeroClass ( ) ;
heroClass - > id = HeroClassID ( index ) ;
heroClass - > identifier = identifier ;
heroClass - > modScope = scope ;
heroClass - > imageBattleFemale = AnimationPath : : fromJson ( node [ " animation " ] [ " battle " ] [ " female " ] ) ;
heroClass - > imageBattleMale = AnimationPath : : fromJson ( node [ " animation " ] [ " battle " ] [ " male " ] ) ;
//MODS COMPATIBILITY FOR 0.96
heroClass - > imageMapFemale = node [ " animation " ] [ " map " ] [ " female " ] . String ( ) ;
heroClass - > imageMapMale = node [ " animation " ] [ " map " ] [ " male " ] . String ( ) ;
VLC - > generaltexth - > registerString ( scope , heroClass - > getNameTextID ( ) , node [ " name " ] . String ( ) ) ;
2024-01-07 12:45:02 +02:00
if ( vstd : : contains ( affinityStr , node [ " affinity " ] . String ( ) ) )
{
heroClass - > affinity = vstd : : find_pos ( affinityStr , node [ " affinity " ] . String ( ) ) ;
}
else
{
logGlobal - > error ( " Mod '%s', hero class '%s': invalid affinity '%s'! Expected 'might' or 'magic'! " , scope , identifier , node [ " affinity " ] . String ( ) ) ;
heroClass - > affinity = CHeroClass : : MIGHT ;
}
2023-10-19 16:19:09 +02:00
fillPrimarySkillData ( node , heroClass , PrimarySkill : : ATTACK ) ;
fillPrimarySkillData ( node , heroClass , PrimarySkill : : DEFENSE ) ;
fillPrimarySkillData ( node , heroClass , PrimarySkill : : SPELL_POWER ) ;
fillPrimarySkillData ( node , heroClass , PrimarySkill : : KNOWLEDGE ) ;
auto percentSumm = std : : accumulate ( heroClass - > primarySkillLowLevel . begin ( ) , heroClass - > primarySkillLowLevel . end ( ) , 0 ) ;
2024-01-31 00:18:10 +02:00
if ( percentSumm < = 0 )
logMod - > error ( " Hero class %s has wrong lowLevelChance values: must be above zero! " , heroClass - > identifier , percentSumm ) ;
2023-10-19 16:19:09 +02:00
percentSumm = std : : accumulate ( heroClass - > primarySkillHighLevel . begin ( ) , heroClass - > primarySkillHighLevel . end ( ) , 0 ) ;
2024-01-31 00:18:10 +02:00
if ( percentSumm < = 0 )
logMod - > error ( " Hero class %s has wrong highLevelChance values: must be above zero! " , heroClass - > identifier , percentSumm ) ;
2023-10-19 16:19:09 +02:00
for ( auto skillPair : node [ " secondarySkills " ] . Struct ( ) )
{
int probability = static_cast < int > ( skillPair . second . Integer ( ) ) ;
2024-02-13 14:34:16 +02:00
VLC - > identifiers ( ) - > requestIdentifier ( skillPair . second . getModScope ( ) , " skill " , skillPair . first , [ heroClass , probability ] ( si32 skillID )
2023-10-19 16:19:09 +02:00
{
heroClass - > secSkillProbability [ skillID ] = probability ;
} ) ;
}
VLC - > identifiers ( ) - > requestIdentifier ( " creature " , node [ " commander " ] ,
[ = ] ( si32 commanderID )
{
2023-12-31 23:43:35 +02:00
heroClass - > commander = CreatureID ( commanderID ) . toCreature ( ) ;
2023-10-19 16:19:09 +02:00
} ) ;
heroClass - > defaultTavernChance = static_cast < ui32 > ( node [ " defaultTavern " ] . Float ( ) ) ;
for ( const auto & tavern : node [ " tavern " ] . Struct ( ) )
{
int value = static_cast < int > ( tavern . second . Float ( ) ) ;
2024-02-13 14:34:16 +02:00
VLC - > identifiers ( ) - > requestIdentifier ( tavern . second . getModScope ( ) , " faction " , tavern . first ,
2023-10-19 16:19:09 +02:00
[ = ] ( si32 factionID )
{
heroClass - > selectionProbability [ FactionID ( factionID ) ] = value ;
} ) ;
}
VLC - > identifiers ( ) - > requestIdentifier ( " faction " , node [ " faction " ] ,
[ = ] ( si32 factionID )
{
heroClass - > faction . setNum ( factionID ) ;
} ) ;
VLC - > identifiers ( ) - > requestIdentifier ( scope , " object " , " hero " , [ = ] ( si32 index )
{
JsonNode classConf = node [ " mapObject " ] ;
classConf [ " heroClass " ] . String ( ) = identifier ;
2024-02-05 21:56:06 +02:00
if ( ! node [ " compatibilityIdentifiers " ] . isNull ( ) )
classConf [ " compatibilityIdentifiers " ] = node [ " compatibilityIdentifiers " ] ;
2024-02-13 14:34:16 +02:00
classConf . setModScope ( scope ) ;
2023-10-19 16:19:09 +02:00
VLC - > objtypeh - > loadSubObject ( identifier , classConf , index , heroClass - > getIndex ( ) ) ;
} ) ;
return heroClass ;
}
std : : vector < JsonNode > CHeroClassHandler : : loadLegacyData ( )
{
size_t dataSize = VLC - > settings ( ) - > getInteger ( EGameSettings : : TEXTS_HERO_CLASS ) ;
objects . resize ( dataSize ) ;
std : : vector < JsonNode > h3Data ;
h3Data . reserve ( dataSize ) ;
CLegacyConfigParser parser ( TextPath : : builtin ( " DATA/HCTRAITS.TXT " ) ) ;
parser . endLine ( ) ; // header
parser . endLine ( ) ;
for ( size_t i = 0 ; i < dataSize ; i + + )
{
JsonNode entry ;
entry [ " name " ] . String ( ) = parser . readString ( ) ;
parser . readNumber ( ) ; // unused aggression
for ( const auto & name : NPrimarySkill : : names )
entry [ " primarySkills " ] [ name ] . Float ( ) = parser . readNumber ( ) ;
for ( const auto & name : NPrimarySkill : : names )
entry [ " lowLevelChance " ] [ name ] . Float ( ) = parser . readNumber ( ) ;
for ( const auto & name : NPrimarySkill : : names )
entry [ " highLevelChance " ] [ name ] . Float ( ) = parser . readNumber ( ) ;
for ( const auto & name : NSecondarySkill : : names )
entry [ " secondarySkills " ] [ name ] . Float ( ) = parser . readNumber ( ) ;
for ( const auto & name : NFaction : : names )
entry [ " tavern " ] [ name ] . Float ( ) = parser . readNumber ( ) ;
parser . endLine ( ) ;
h3Data . push_back ( entry ) ;
}
return h3Data ;
}
void CHeroClassHandler : : afterLoadFinalization ( )
{
// for each pair <class, town> set selection probability if it was not set before in tavern entries
2023-12-31 23:43:35 +02:00
for ( auto & heroClass : objects )
2023-10-19 16:19:09 +02:00
{
2023-12-31 23:43:35 +02:00
for ( auto & faction : VLC - > townh - > objects )
2023-10-19 16:19:09 +02:00
{
if ( ! faction - > town )
continue ;
if ( heroClass - > selectionProbability . count ( faction - > getId ( ) ) )
continue ;
auto chance = static_cast < float > ( heroClass - > defaultTavernChance * faction - > town - > defaultTavernChance ) ;
heroClass - > selectionProbability [ faction - > getId ( ) ] = static_cast < int > ( sqrt ( chance ) + 0.5 ) ; //FIXME: replace with std::round once MVS supports it
}
2023-11-02 18:45:46 +02:00
2023-10-19 16:19:09 +02:00
// set default probabilities for gaining secondary skills where not loaded previously
for ( int skillID = 0 ; skillID < VLC - > skillh - > size ( ) ; skillID + + )
{
2023-11-02 18:45:46 +02:00
if ( heroClass - > secSkillProbability . count ( skillID ) = = 0 )
2023-10-19 16:19:09 +02:00
{
const CSkill * skill = ( * VLC - > skillh ) [ SecondarySkill ( skillID ) ] ;
logMod - > trace ( " %s: no probability for %s, using default " , heroClass - > identifier , skill - > getJsonKey ( ) ) ;
heroClass - > secSkillProbability [ skillID ] = skill - > gainChance [ heroClass - > affinity ] ;
}
}
}
2024-01-10 21:30:12 +02:00
for ( const auto & hc : objects )
2023-10-19 16:19:09 +02:00
{
2024-01-10 21:30:12 +02:00
if ( ! hc - > imageMapMale . empty ( ) )
2023-10-19 16:19:09 +02:00
{
JsonNode templ ;
templ [ " animation " ] . String ( ) = hc - > imageMapMale ;
VLC - > objtypeh - > getHandlerFor ( Obj : : HERO , hc - > getIndex ( ) ) - > addTemplate ( templ ) ;
}
}
}
CHeroClassHandler : : ~ CHeroClassHandler ( ) = default ;
CHeroHandler : : ~ CHeroHandler ( ) = default ;
CHeroHandler : : CHeroHandler ( )
{
loadExperience ( ) ;
}
const std : : vector < std : : string > & CHeroHandler : : getTypeNames ( ) const
{
static const std : : vector < std : : string > typeNames = { " hero " } ;
return typeNames ;
}
CHero * CHeroHandler : : loadFromJson ( const std : : string & scope , const JsonNode & node , const std : : string & identifier , size_t index )
{
assert ( identifier . find ( ' : ' ) = = std : : string : : npos ) ;
assert ( ! scope . empty ( ) ) ;
auto * hero = new CHero ( ) ;
hero - > ID = HeroTypeID ( index ) ;
hero - > identifier = identifier ;
hero - > modScope = scope ;
hero - > gender = node [ " female " ] . Bool ( ) ? EHeroGender : : FEMALE : EHeroGender : : MALE ;
hero - > special = node [ " special " ] . Bool ( ) ;
//Default - both false
hero - > onlyOnWaterMap = node [ " onlyOnWaterMap " ] . Bool ( ) ;
hero - > onlyOnMapWithoutWater = node [ " onlyOnMapWithoutWater " ] . Bool ( ) ;
VLC - > generaltexth - > registerString ( scope , hero - > getNameTextID ( ) , node [ " texts " ] [ " name " ] . String ( ) ) ;
VLC - > generaltexth - > registerString ( scope , hero - > getBiographyTextID ( ) , node [ " texts " ] [ " biography " ] . String ( ) ) ;
VLC - > generaltexth - > registerString ( scope , hero - > getSpecialtyNameTextID ( ) , node [ " texts " ] [ " specialty " ] [ " name " ] . String ( ) ) ;
VLC - > generaltexth - > registerString ( scope , hero - > getSpecialtyTooltipTextID ( ) , node [ " texts " ] [ " specialty " ] [ " tooltip " ] . String ( ) ) ;
VLC - > generaltexth - > registerString ( scope , hero - > getSpecialtyDescriptionTextID ( ) , node [ " texts " ] [ " specialty " ] [ " description " ] . String ( ) ) ;
hero - > iconSpecSmall = node [ " images " ] [ " specialtySmall " ] . String ( ) ;
hero - > iconSpecLarge = node [ " images " ] [ " specialtyLarge " ] . String ( ) ;
hero - > portraitSmall = node [ " images " ] [ " small " ] . String ( ) ;
hero - > portraitLarge = node [ " images " ] [ " large " ] . String ( ) ;
hero - > battleImage = AnimationPath : : fromJson ( node [ " battleImage " ] ) ;
loadHeroArmy ( hero , node ) ;
loadHeroSkills ( hero , node ) ;
loadHeroSpecialty ( hero , node ) ;
VLC - > identifiers ( ) - > requestIdentifier ( " heroClass " , node [ " class " ] ,
[ = ] ( si32 classID )
{
2023-12-31 23:43:35 +02:00
hero - > heroClass = HeroClassID ( classID ) . toHeroClass ( ) ;
2023-10-19 16:19:09 +02:00
} ) ;
return hero ;
}
void CHeroHandler : : loadHeroArmy ( CHero * hero , const JsonNode & node ) const
{
assert ( node [ " army " ] . Vector ( ) . size ( ) < = 3 ) ; // anything bigger is useless - army initialization uses up to 3 slots
hero - > initialArmy . resize ( node [ " army " ] . Vector ( ) . size ( ) ) ;
for ( size_t i = 0 ; i < hero - > initialArmy . size ( ) ; i + + )
{
const JsonNode & source = node [ " army " ] . Vector ( ) [ i ] ;
hero - > initialArmy [ i ] . minAmount = static_cast < ui32 > ( source [ " min " ] . Float ( ) ) ;
hero - > initialArmy [ i ] . maxAmount = static_cast < ui32 > ( source [ " max " ] . Float ( ) ) ;
2024-01-15 18:19:21 +02:00
if ( hero - > initialArmy [ i ] . minAmount > hero - > initialArmy [ i ] . maxAmount )
{
logMod - > error ( " Hero %s has minimal army size (%d) greater than maximal size (%d)! " , hero - > getJsonKey ( ) , hero - > initialArmy [ i ] . minAmount , hero - > initialArmy [ i ] . maxAmount ) ;
std : : swap ( hero - > initialArmy [ i ] . minAmount , hero - > initialArmy [ i ] . maxAmount ) ;
}
2023-10-19 16:19:09 +02:00
VLC - > identifiers ( ) - > requestIdentifier ( " creature " , source [ " creature " ] , [ = ] ( si32 creature )
{
hero - > initialArmy [ i ] . creature = CreatureID ( creature ) ;
} ) ;
}
}
void CHeroHandler : : loadHeroSkills ( CHero * hero , const JsonNode & node ) const
{
for ( const JsonNode & set : node [ " skills " ] . Vector ( ) )
{
int skillLevel = static_cast < int > ( boost : : range : : find ( NSecondarySkill : : levels , set [ " level " ] . String ( ) ) - std : : begin ( NSecondarySkill : : levels ) ) ;
2023-10-22 17:36:41 +02:00
if ( skillLevel < MasteryLevel : : LEVELS_SIZE )
2023-10-19 16:19:09 +02:00
{
size_t currentIndex = hero - > secSkillsInit . size ( ) ;
hero - > secSkillsInit . emplace_back ( SecondarySkill ( - 1 ) , skillLevel ) ;
VLC - > identifiers ( ) - > requestIdentifier ( " skill " , set [ " skill " ] , [ = ] ( si32 id )
{
hero - > secSkillsInit [ currentIndex ] . first = SecondarySkill ( id ) ;
} ) ;
}
else
{
logMod - > error ( " Unknown skill level: %s " , set [ " level " ] . String ( ) ) ;
}
}
// spellbook is considered present if hero have "spellbook" entry even when this is an empty set (0 spells)
hero - > haveSpellBook = ! node [ " spellbook " ] . isNull ( ) ;
for ( const JsonNode & spell : node [ " spellbook " ] . Vector ( ) )
{
VLC - > identifiers ( ) - > requestIdentifier ( " spell " , spell ,
[ = ] ( si32 spellID )
{
hero - > spells . insert ( SpellID ( spellID ) ) ;
} ) ;
}
}
/// creates standard H3 hero specialty for creatures
static std : : vector < std : : shared_ptr < Bonus > > createCreatureSpecialty ( CreatureID baseCreatureID )
{
std : : vector < std : : shared_ptr < Bonus > > result ;
std : : set < CreatureID > targets ;
targets . insert ( baseCreatureID ) ;
// go through entire upgrade chain and collect all creatures to which baseCreatureID can be upgraded
for ( ; ; )
{
std : : set < CreatureID > oldTargets = targets ;
2024-01-10 21:30:12 +02:00
for ( const auto & upgradeSourceID : oldTargets )
2023-10-19 16:19:09 +02:00
{
2023-11-05 18:58:07 +02:00
const CCreature * upgradeSource = upgradeSourceID . toCreature ( ) ;
2023-10-19 16:19:09 +02:00
targets . insert ( upgradeSource - > upgrades . begin ( ) , upgradeSource - > upgrades . end ( ) ) ;
}
if ( oldTargets . size ( ) = = targets . size ( ) )
break ;
}
for ( CreatureID cid : targets )
{
2024-01-10 21:30:12 +02:00
const auto & specCreature = * cid . toCreature ( ) ;
2023-10-19 16:19:09 +02:00
int stepSize = specCreature . getLevel ( ) ? specCreature . getLevel ( ) : 5 ;
{
2024-01-17 14:50:00 +02:00
auto bonus = std : : make_shared < Bonus > ( ) ;
2023-10-19 16:19:09 +02:00
bonus - > limiter . reset ( new CCreatureTypeLimiter ( specCreature , false ) ) ;
bonus - > type = BonusType : : STACKS_SPEED ;
bonus - > val = 1 ;
result . push_back ( bonus ) ;
}
{
2024-01-17 14:50:00 +02:00
auto bonus = std : : make_shared < Bonus > ( ) ;
2023-10-19 16:19:09 +02:00
bonus - > type = BonusType : : PRIMARY_SKILL ;
2023-10-22 17:36:41 +02:00
bonus - > subtype = BonusSubtypeID ( PrimarySkill : : ATTACK ) ;
2023-10-19 16:19:09 +02:00
bonus - > val = 0 ;
bonus - > limiter . reset ( new CCreatureTypeLimiter ( specCreature , false ) ) ;
bonus - > updater . reset ( new GrowsWithLevelUpdater ( specCreature . getAttack ( false ) , stepSize ) ) ;
result . push_back ( bonus ) ;
}
{
2024-01-17 14:50:00 +02:00
auto bonus = std : : make_shared < Bonus > ( ) ;
2023-10-19 16:19:09 +02:00
bonus - > type = BonusType : : PRIMARY_SKILL ;
2023-10-22 17:36:41 +02:00
bonus - > subtype = BonusSubtypeID ( PrimarySkill : : DEFENSE ) ;
2023-10-19 16:19:09 +02:00
bonus - > val = 0 ;
bonus - > limiter . reset ( new CCreatureTypeLimiter ( specCreature , false ) ) ;
bonus - > updater . reset ( new GrowsWithLevelUpdater ( specCreature . getDefense ( false ) , stepSize ) ) ;
result . push_back ( bonus ) ;
}
}
return result ;
}
void CHeroHandler : : beforeValidate ( JsonNode & object )
{
//handle "base" specialty info
JsonNode & specialtyNode = object [ " specialty " ] ;
if ( specialtyNode . getType ( ) = = JsonNode : : JsonType : : DATA_STRUCT )
{
const JsonNode & base = specialtyNode [ " base " ] ;
if ( ! base . isNull ( ) )
{
if ( specialtyNode [ " bonuses " ] . isNull ( ) )
{
logMod - > warn ( " specialty has base without bonuses " ) ;
}
else
{
JsonMap & bonuses = specialtyNode [ " bonuses " ] . Struct ( ) ;
for ( std : : pair < std : : string , JsonNode > keyValue : bonuses )
JsonUtils : : inherit ( bonuses [ keyValue . first ] , base ) ;
}
}
}
}
void CHeroHandler : : afterLoadFinalization ( )
{
2024-01-10 21:30:12 +02:00
for ( const auto & functor : callAfterLoadFinalization )
2023-10-19 16:19:09 +02:00
functor ( ) ;
callAfterLoadFinalization . clear ( ) ;
}
void CHeroHandler : : loadHeroSpecialty ( CHero * hero , const JsonNode & node )
{
auto prepSpec = [ = ] ( std : : shared_ptr < Bonus > bonus )
{
bonus - > duration = BonusDuration : : PERMANENT ;
bonus - > source = BonusSource : : HERO_SPECIAL ;
2023-10-22 17:36:41 +02:00
bonus - > sid = BonusSourceID ( hero - > getId ( ) ) ;
2023-10-19 16:19:09 +02:00
return bonus ;
} ;
//new format, using bonus system
const JsonNode & specialtyNode = node [ " specialty " ] ;
if ( specialtyNode . getType ( ) ! = JsonNode : : JsonType : : DATA_STRUCT )
{
logMod - > error ( " Unsupported speciality format for hero %s! " , hero - > getNameTranslated ( ) ) ;
return ;
}
//creature specialty - alias for simplicity
if ( ! specialtyNode [ " creature " ] . isNull ( ) )
{
JsonNode creatureNode = specialtyNode [ " creature " ] ;
std : : function < void ( ) > specialtyLoader = [ creatureNode , hero , prepSpec ]
{
VLC - > identifiers ( ) - > requestIdentifier ( " creature " , creatureNode , [ hero , prepSpec ] ( si32 creature )
{
for ( const auto & bonus : createCreatureSpecialty ( CreatureID ( creature ) ) )
hero - > specialty . push_back ( prepSpec ( bonus ) ) ;
} ) ;
} ;
callAfterLoadFinalization . push_back ( specialtyLoader ) ;
}
for ( const auto & keyValue : specialtyNode [ " bonuses " ] . Struct ( ) )
hero - > specialty . push_back ( prepSpec ( JsonUtils : : parseBonus ( keyValue . second ) ) ) ;
}
void CHeroHandler : : loadExperience ( )
{
expPerLevel . push_back ( 0 ) ;
expPerLevel . push_back ( 1000 ) ;
expPerLevel . push_back ( 2000 ) ;
expPerLevel . push_back ( 3200 ) ;
expPerLevel . push_back ( 4600 ) ;
expPerLevel . push_back ( 6200 ) ;
expPerLevel . push_back ( 8000 ) ;
expPerLevel . push_back ( 10000 ) ;
expPerLevel . push_back ( 12200 ) ;
expPerLevel . push_back ( 14700 ) ;
expPerLevel . push_back ( 17500 ) ;
expPerLevel . push_back ( 20600 ) ;
expPerLevel . push_back ( 24320 ) ;
expPerLevel . push_back ( 28784 ) ;
expPerLevel . push_back ( 34140 ) ;
2024-01-04 23:57:36 +02:00
for ( ; ; )
2023-10-19 16:19:09 +02:00
{
auto i = expPerLevel . size ( ) - 1 ;
2024-01-04 23:57:36 +02:00
auto currExp = expPerLevel [ i ] ;
auto prevExp = expPerLevel [ i - 1 ] ;
auto prevDiff = currExp - prevExp ;
auto nextDiff = prevDiff + prevDiff / 5 ;
auto maxExp = std : : numeric_limits < decltype ( currExp ) > : : max ( ) ;
if ( currExp > maxExp - nextDiff )
break ; // overflow point reached
expPerLevel . push_back ( currExp + nextDiff ) ;
2023-10-19 16:19:09 +02:00
}
}
/// convert h3-style ID (e.g. Gobin Wolf Rider) to vcmi (e.g. goblinWolfRider)
static std : : string genRefName ( std : : string input )
{
boost : : algorithm : : replace_all ( input , " " , " " ) ; //remove spaces
input [ 0 ] = std : : tolower ( input [ 0 ] ) ; // to camelCase
return input ;
}
std : : vector < JsonNode > CHeroHandler : : loadLegacyData ( )
{
size_t dataSize = VLC - > settings ( ) - > getInteger ( EGameSettings : : TEXTS_HERO ) ;
objects . resize ( dataSize ) ;
std : : vector < JsonNode > h3Data ;
h3Data . reserve ( dataSize ) ;
CLegacyConfigParser specParser ( TextPath : : builtin ( " DATA/HEROSPEC.TXT " ) ) ;
CLegacyConfigParser bioParser ( TextPath : : builtin ( " DATA/HEROBIOS.TXT " ) ) ;
CLegacyConfigParser parser ( TextPath : : builtin ( " DATA/HOTRAITS.TXT " ) ) ;
parser . endLine ( ) ; //ignore header
parser . endLine ( ) ;
specParser . endLine ( ) ; //ignore header
specParser . endLine ( ) ;
for ( int i = 0 ; i < GameConstants : : HEROES_QUANTITY ; i + + )
{
JsonNode heroData ;
heroData [ " texts " ] [ " name " ] . String ( ) = parser . readString ( ) ;
heroData [ " texts " ] [ " biography " ] . String ( ) = bioParser . readString ( ) ;
heroData [ " texts " ] [ " specialty " ] [ " name " ] . String ( ) = specParser . readString ( ) ;
heroData [ " texts " ] [ " specialty " ] [ " tooltip " ] . String ( ) = specParser . readString ( ) ;
heroData [ " texts " ] [ " specialty " ] [ " description " ] . String ( ) = specParser . readString ( ) ;
for ( int x = 0 ; x < 3 ; x + + )
{
JsonNode armySlot ;
armySlot [ " min " ] . Float ( ) = parser . readNumber ( ) ;
armySlot [ " max " ] . Float ( ) = parser . readNumber ( ) ;
armySlot [ " creature " ] . String ( ) = genRefName ( parser . readString ( ) ) ;
heroData [ " army " ] . Vector ( ) . push_back ( armySlot ) ;
}
parser . endLine ( ) ;
specParser . endLine ( ) ;
bioParser . endLine ( ) ;
h3Data . push_back ( heroData ) ;
}
return h3Data ;
}
void CHeroHandler : : loadObject ( std : : string scope , std : : string name , const JsonNode & data )
{
size_t index = objects . size ( ) ;
static const int specialFramesCount = 2 ; // reserved for 2 special frames
auto * object = loadFromJson ( scope , data , name , index ) ;
object - > imageIndex = static_cast < si32 > ( index ) + specialFramesCount ;
objects . emplace_back ( object ) ;
registerObject ( scope , " hero " , name , object - > getIndex ( ) ) ;
2024-02-05 21:56:06 +02:00
for ( const auto & compatID : data [ " compatibilityIdentifiers " ] . Vector ( ) )
registerObject ( scope , " hero " , compatID . String ( ) , object - > getIndex ( ) ) ;
2023-10-19 16:19:09 +02:00
}
void CHeroHandler : : loadObject ( std : : string scope , std : : string name , const JsonNode & data , size_t index )
{
auto * object = loadFromJson ( scope , data , name , index ) ;
object - > imageIndex = static_cast < si32 > ( index ) ;
assert ( objects [ index ] = = nullptr ) ; // ensure that this id was not loaded before
objects [ index ] = object ;
registerObject ( scope , " hero " , name , object - > getIndex ( ) ) ;
2024-02-05 21:56:06 +02:00
for ( const auto & compatID : data [ " compatibilityIdentifiers " ] . Vector ( ) )
registerObject ( scope , " hero " , compatID . String ( ) , object - > getIndex ( ) ) ;
2023-10-19 16:19:09 +02:00
}
2024-01-04 23:57:36 +02:00
ui32 CHeroHandler : : level ( TExpType experience ) const
2023-10-19 16:19:09 +02:00
{
return static_cast < ui32 > ( boost : : range : : upper_bound ( expPerLevel , experience ) - std : : begin ( expPerLevel ) ) ;
}
2024-01-04 23:57:36 +02:00
TExpType CHeroHandler : : reqExp ( ui32 level ) const
2023-10-19 16:19:09 +02:00
{
if ( ! level )
return 0 ;
if ( level < = expPerLevel . size ( ) )
{
return expPerLevel [ level - 1 ] ;
}
else
{
logGlobal - > warn ( " A hero has reached unsupported amount of experience " ) ;
return expPerLevel [ expPerLevel . size ( ) - 1 ] ;
}
}
2024-01-04 23:57:36 +02:00
ui32 CHeroHandler : : maxSupportedLevel ( ) const
{
return expPerLevel . size ( ) ;
}
2023-11-05 15:24:26 +02:00
std : : set < HeroTypeID > CHeroHandler : : getDefaultAllowed ( ) const
2023-10-19 16:19:09 +02:00
{
2023-11-05 15:24:26 +02:00
std : : set < HeroTypeID > result ;
2023-10-19 16:19:09 +02:00
2023-12-31 23:43:35 +02:00
for ( auto & hero : objects )
2023-11-05 15:24:26 +02:00
if ( hero & & ! hero - > special )
result . insert ( hero - > getId ( ) ) ;
2023-10-19 16:19:09 +02:00
2023-11-05 15:24:26 +02:00
return result ;
2023-10-19 16:19:09 +02:00
}
VCMI_LIB_NAMESPACE_END