2023-10-19 16:19:09 +02:00
/*
* CCreatureHandler . 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 "CCreatureHandler.h"
# include "CGeneralTextHandler.h"
# include "ResourceSet.h"
# include "filesystem/Filesystem.h"
# include "VCMI_Lib.h"
2024-01-31 00:17:40 +02:00
# include "CRandomGenerator.h"
2023-10-19 16:19:09 +02:00
# include "CTownHandler.h"
# include "GameSettings.h"
# include "constants/StringConstants.h"
# include "bonuses/Limiters.h"
# include "bonuses/Updaters.h"
2024-02-14 15:48:06 +02:00
# include "json/JsonBonus.h"
2023-10-19 16:19:09 +02:00
# include "serializer/JsonDeserializer.h"
# include "serializer/JsonUpdater.h"
# include "mapObjectConstructors/AObjectTypeHandler.h"
# include "mapObjectConstructors/CObjectClassesHandler.h"
# include "modding/CModHandler.h"
2024-05-23 14:46:19 +02:00
# include "ExceptionsCommon.h"
2023-10-19 16:19:09 +02:00
VCMI_LIB_NAMESPACE_BEGIN
const std : : map < CCreature : : CreatureQuantityId , std : : string > CCreature : : creatureQuantityRanges =
{
{ CCreature : : CreatureQuantityId : : FEW , " 1-4 " } ,
{ CCreature : : CreatureQuantityId : : SEVERAL , " 5-9 " } ,
{ CCreature : : CreatureQuantityId : : PACK , " 10-19 " } ,
{ CCreature : : CreatureQuantityId : : LOTS , " 20-49 " } ,
{ CCreature : : CreatureQuantityId : : HORDE , " 50-99 " } ,
{ CCreature : : CreatureQuantityId : : THRONG , " 100-249 " } ,
{ CCreature : : CreatureQuantityId : : SWARM , " 250-499 " } ,
{ CCreature : : CreatureQuantityId : : ZOUNDS , " 500-999 " } ,
{ CCreature : : CreatureQuantityId : : LEGION , " 1000+ " }
} ;
int32_t CCreature : : getIndex ( ) const
{
return idNumber . toEnum ( ) ;
}
int32_t CCreature : : getIconIndex ( ) const
{
return iconIndex ;
}
std : : string CCreature : : getJsonKey ( ) const
{
return modScope + ' : ' + identifier ;
}
void CCreature : : registerIcons ( const IconRegistar & cb ) const
{
cb ( getIconIndex ( ) , 0 , " CPRSMALL " , smallIconName ) ;
cb ( getIconIndex ( ) , 0 , " TWCRPORT " , largeIconName ) ;
}
CreatureID CCreature : : getId ( ) const
{
return idNumber ;
}
const IBonusBearer * CCreature : : getBonusBearer ( ) const
{
return this ;
}
int32_t CCreature : : getAdvMapAmountMin ( ) const
{
return ammMin ;
}
int32_t CCreature : : getAdvMapAmountMax ( ) const
{
return ammMax ;
}
int32_t CCreature : : getAIValue ( ) const
{
return AIValue ;
}
int32_t CCreature : : getFightValue ( ) const
{
return fightValue ;
}
int32_t CCreature : : getLevel ( ) const
{
return level ;
}
int32_t CCreature : : getGrowth ( ) const
{
return growth ;
}
int32_t CCreature : : getHorde ( ) const
{
return hordeGrowth ;
}
FactionID CCreature : : getFaction ( ) const
{
return FactionID ( faction ) ;
}
int32_t CCreature : : getBaseAttack ( ) const
{
2023-10-22 17:36:41 +02:00
static const auto SELECTOR = Selector : : typeSubtype ( BonusType : : PRIMARY_SKILL , BonusSubtypeID ( PrimarySkill : : ATTACK ) ) . And ( Selector : : sourceTypeSel ( BonusSource : : CREATURE_ABILITY ) ) ;
2023-10-19 16:19:09 +02:00
return getExportedBonusList ( ) . valOfBonuses ( SELECTOR ) ;
}
int32_t CCreature : : getBaseDefense ( ) const
{
2023-10-22 17:36:41 +02:00
static const auto SELECTOR = Selector : : typeSubtype ( BonusType : : PRIMARY_SKILL , BonusSubtypeID ( PrimarySkill : : DEFENSE ) ) . And ( Selector : : sourceTypeSel ( BonusSource : : CREATURE_ABILITY ) ) ;
2023-10-19 16:19:09 +02:00
return getExportedBonusList ( ) . valOfBonuses ( SELECTOR ) ;
}
int32_t CCreature : : getBaseDamageMin ( ) const
{
2023-10-22 17:36:41 +02:00
static const auto SELECTOR = Selector : : typeSubtype ( BonusType : : CREATURE_DAMAGE , BonusCustomSubtype : : creatureDamageMin ) . And ( Selector : : sourceTypeSel ( BonusSource : : CREATURE_ABILITY ) ) ;
2023-10-19 16:19:09 +02:00
return getExportedBonusList ( ) . valOfBonuses ( SELECTOR ) ;
}
int32_t CCreature : : getBaseDamageMax ( ) const
{
2023-10-22 17:36:41 +02:00
static const auto SELECTOR = Selector : : typeSubtype ( BonusType : : CREATURE_DAMAGE , BonusCustomSubtype : : creatureDamageMax ) . And ( Selector : : sourceTypeSel ( BonusSource : : CREATURE_ABILITY ) ) ;
2023-10-19 16:19:09 +02:00
return getExportedBonusList ( ) . valOfBonuses ( SELECTOR ) ;
}
int32_t CCreature : : getBaseHitPoints ( ) const
{
static const auto SELECTOR = Selector : : type ( ) ( BonusType : : STACK_HEALTH ) . And ( Selector : : sourceTypeSel ( BonusSource : : CREATURE_ABILITY ) ) ;
return getExportedBonusList ( ) . valOfBonuses ( SELECTOR ) ;
}
int32_t CCreature : : getBaseSpellPoints ( ) const
{
static const auto SELECTOR = Selector : : type ( ) ( BonusType : : CASTS ) . And ( Selector : : sourceTypeSel ( BonusSource : : CREATURE_ABILITY ) ) ;
return getExportedBonusList ( ) . valOfBonuses ( SELECTOR ) ;
}
int32_t CCreature : : getBaseSpeed ( ) const
{
static const auto SELECTOR = Selector : : type ( ) ( BonusType : : STACKS_SPEED ) . And ( Selector : : sourceTypeSel ( BonusSource : : CREATURE_ABILITY ) ) ;
return getExportedBonusList ( ) . valOfBonuses ( SELECTOR ) ;
}
int32_t CCreature : : getBaseShots ( ) const
{
static const auto SELECTOR = Selector : : type ( ) ( BonusType : : SHOTS ) . And ( Selector : : sourceTypeSel ( BonusSource : : CREATURE_ABILITY ) ) ;
return getExportedBonusList ( ) . valOfBonuses ( SELECTOR ) ;
}
int32_t CCreature : : getRecruitCost ( GameResID resIndex ) const
{
if ( resIndex . getNum ( ) > = 0 & & resIndex . getNum ( ) < cost . size ( ) )
return cost [ resIndex ] ;
else
return 0 ;
}
TResources CCreature : : getFullRecruitCost ( ) const
{
return cost ;
}
bool CCreature : : hasUpgrades ( ) const
{
return ! upgrades . empty ( ) ;
}
std : : string CCreature : : getNameTranslated ( ) const
{
return getNameSingularTranslated ( ) ;
}
std : : string CCreature : : getNamePluralTranslated ( ) const
{
return VLC - > generaltexth - > translate ( getNamePluralTextID ( ) ) ;
}
std : : string CCreature : : getNameSingularTranslated ( ) const
{
return VLC - > generaltexth - > translate ( getNameSingularTextID ( ) ) ;
}
std : : string CCreature : : getNameTextID ( ) const
{
return getNameSingularTextID ( ) ;
}
2024-04-27 18:41:21 +02:00
std : : string CCreature : : getDescriptionTranslated ( ) const
2024-04-12 23:35:39 +02:00
{
2024-04-27 18:41:21 +02:00
return VLC - > generaltexth - > translate ( getDescriptionTextID ( ) ) ;
2024-04-12 23:35:39 +02:00
}
2023-10-19 16:19:09 +02:00
std : : string CCreature : : getNamePluralTextID ( ) const
{
return TextIdentifier ( " creatures " , modScope , identifier , " name " , " plural " ) . get ( ) ;
}
std : : string CCreature : : getNameSingularTextID ( ) const
{
return TextIdentifier ( " creatures " , modScope , identifier , " name " , " singular " ) . get ( ) ;
}
2024-04-27 18:41:21 +02:00
std : : string CCreature : : getDescriptionTextID ( ) const
2024-04-12 23:35:39 +02:00
{
2024-04-27 18:41:21 +02:00
return TextIdentifier ( " creatures " , modScope , identifier , " description " ) . get ( ) ;
2024-04-12 23:35:39 +02:00
}
2023-10-19 16:19:09 +02:00
CCreature : : CreatureQuantityId CCreature : : getQuantityID ( const int & quantity )
{
if ( quantity < 5 )
return CCreature : : CreatureQuantityId : : FEW ;
if ( quantity < 10 )
return CCreature : : CreatureQuantityId : : SEVERAL ;
if ( quantity < 20 )
return CCreature : : CreatureQuantityId : : PACK ;
if ( quantity < 50 )
return CCreature : : CreatureQuantityId : : LOTS ;
if ( quantity < 100 )
return CCreature : : CreatureQuantityId : : HORDE ;
if ( quantity < 250 )
return CCreature : : CreatureQuantityId : : THRONG ;
if ( quantity < 500 )
return CCreature : : CreatureQuantityId : : SWARM ;
if ( quantity < 1000 )
return CCreature : : CreatureQuantityId : : ZOUNDS ;
return CCreature : : CreatureQuantityId : : LEGION ;
}
std : : string CCreature : : getQuantityRangeStringForId ( const CCreature : : CreatureQuantityId & quantityId )
{
if ( creatureQuantityRanges . find ( quantityId ) ! = creatureQuantityRanges . end ( ) )
return creatureQuantityRanges . at ( quantityId ) ;
logGlobal - > error ( " Wrong quantityId: %d " , ( int ) quantityId ) ;
assert ( 0 ) ;
return " [ERROR] " ;
}
int CCreature : : estimateCreatureCount ( ui32 countID )
{
static const int creature_count [ ] = { 0 , 3 , 8 , 15 , 35 , 75 , 175 , 375 , 750 , 2500 } ;
if ( countID > 9 )
{
logGlobal - > error ( " Wrong countID %d! " , countID ) ;
return 0 ;
}
else
return creature_count [ countID ] ;
}
bool CCreature : : isDoubleWide ( ) const
{
return doubleWide ;
}
/**
* Determines if the creature is of a good alignment .
* @ return true if the creture is good , false otherwise .
*/
bool CCreature : : isGood ( ) const
{
2023-11-02 17:48:48 +02:00
return VLC - > factions ( ) - > getById ( faction ) - > getAlignment ( ) = = EAlignment : : GOOD ;
2023-10-19 16:19:09 +02:00
}
/**
* Determines if the creature is of an evil alignment .
* @ return true if the creature is evil , false otherwise .
*/
bool CCreature : : isEvil ( ) const
{
2023-11-02 17:48:48 +02:00
return VLC - > factions ( ) - > getById ( faction ) - > getAlignment ( ) = = EAlignment : : EVIL ;
2023-10-19 16:19:09 +02:00
}
si32 CCreature : : maxAmount ( const TResources & res ) const //how many creatures can be bought
{
int ret = 2147483645 ;
int resAmnt = static_cast < int > ( std : : min ( res . size ( ) , cost . size ( ) ) ) ;
for ( int i = 0 ; i < resAmnt ; i + + )
if ( cost [ i ] )
ret = std : : min ( ret , ( res [ i ] / cost [ i ] ) ) ;
return ret ;
}
CCreature : : CCreature ( )
{
setNodeType ( CBonusSystemNode : : CREATURE ) ;
fightValue = AIValue = growth = hordeGrowth = ammMin = ammMax = 0 ;
}
2023-10-22 17:36:41 +02:00
void CCreature : : addBonus ( int val , BonusType type )
2023-10-19 16:19:09 +02:00
{
2023-10-22 17:36:41 +02:00
addBonus ( val , type , BonusSubtypeID ( ) ) ;
}
void CCreature : : addBonus ( int val , BonusType type , BonusSubtypeID subtype )
{
auto selector = Selector : : typeSubtype ( type , subtype ) . And ( Selector : : source ( BonusSource : : CREATURE_ABILITY , BonusSourceID ( getId ( ) ) ) ) ;
2023-10-19 16:19:09 +02:00
BonusList & exported = getExportedBonusList ( ) ;
BonusList existing ;
exported . getBonuses ( existing , selector , Selector : : all ) ;
if ( existing . empty ( ) )
{
2023-10-22 17:36:41 +02:00
auto added = std : : make_shared < Bonus > ( BonusDuration : : PERMANENT , type , BonusSource : : CREATURE_ABILITY , val , BonusSourceID ( getId ( ) ) , subtype , BonusValueType : : BASE_NUMBER ) ;
2023-10-19 16:19:09 +02:00
addNewBonus ( added ) ;
}
else
{
std : : shared_ptr < Bonus > b = existing [ 0 ] ;
b - > val = val ;
}
}
bool CCreature : : isMyUpgrade ( const CCreature * anotherCre ) const
{
//TODO upgrade of upgrade?
return vstd : : contains ( upgrades , anotherCre - > getId ( ) ) ;
}
bool CCreature : : valid ( ) const
{
2023-11-05 18:58:07 +02:00
return this = = ( * VLC - > creh ) [ idNumber ] ;
2023-10-19 16:19:09 +02:00
}
std : : string CCreature : : nodeName ( ) const
{
return " \" " + getNamePluralTextID ( ) + " \" " ;
}
void CCreature : : updateFrom ( const JsonNode & data )
{
JsonUpdater handler ( nullptr , data ) ;
{
auto configScope = handler . enterStruct ( " config " ) ;
const JsonNode & configNode = handler . getCurrent ( ) ;
serializeJson ( handler ) ;
if ( ! configNode [ " hitPoints " ] . isNull ( ) )
addBonus ( configNode [ " hitPoints " ] . Integer ( ) , BonusType : : STACK_HEALTH ) ;
if ( ! configNode [ " speed " ] . isNull ( ) )
addBonus ( configNode [ " speed " ] . Integer ( ) , BonusType : : STACKS_SPEED ) ;
if ( ! configNode [ " attack " ] . isNull ( ) )
2023-10-22 17:36:41 +02:00
addBonus ( configNode [ " attack " ] . Integer ( ) , BonusType : : PRIMARY_SKILL , BonusSubtypeID ( PrimarySkill : : ATTACK ) ) ;
2023-10-19 16:19:09 +02:00
if ( ! configNode [ " defense " ] . isNull ( ) )
2023-10-22 17:36:41 +02:00
addBonus ( configNode [ " defense " ] . Integer ( ) , BonusType : : PRIMARY_SKILL , BonusSubtypeID ( PrimarySkill : : DEFENSE ) ) ;
2023-10-19 16:19:09 +02:00
if ( ! configNode [ " damage " ] [ " min " ] . isNull ( ) )
2023-10-22 17:36:41 +02:00
addBonus ( configNode [ " damage " ] [ " min " ] . Integer ( ) , BonusType : : CREATURE_DAMAGE , BonusCustomSubtype : : creatureDamageMin ) ;
2023-10-19 16:19:09 +02:00
if ( ! configNode [ " damage " ] [ " max " ] . isNull ( ) )
2023-10-22 17:36:41 +02:00
addBonus ( configNode [ " damage " ] [ " max " ] . Integer ( ) , BonusType : : CREATURE_DAMAGE , BonusCustomSubtype : : creatureDamageMax ) ;
2023-10-19 16:19:09 +02:00
if ( ! configNode [ " shots " ] . isNull ( ) )
addBonus ( configNode [ " shots " ] . Integer ( ) , BonusType : : SHOTS ) ;
if ( ! configNode [ " spellPoints " ] . isNull ( ) )
addBonus ( configNode [ " spellPoints " ] . Integer ( ) , BonusType : : CASTS ) ;
}
handler . serializeBonuses ( " bonuses " , this ) ;
}
void CCreature : : serializeJson ( JsonSerializeFormat & handler )
{
handler . serializeInt ( " fightValue " , fightValue ) ;
handler . serializeInt ( " aiValue " , AIValue ) ;
handler . serializeInt ( " growth " , growth ) ;
handler . serializeInt ( " horde " , hordeGrowth ) ; // Needed at least until configurable buildings
{
auto advMapNode = handler . enterStruct ( " advMapAmount " ) ;
handler . serializeInt ( " min " , ammMin ) ;
handler . serializeInt ( " max " , ammMax ) ;
}
if ( handler . updating )
{
cost . serializeJson ( handler , " cost " ) ;
2023-11-02 16:31:12 +02:00
handler . serializeId ( " faction " , faction ) ;
2023-10-19 16:19:09 +02:00
}
handler . serializeInt ( " level " , level ) ;
handler . serializeBool ( " doubleWide " , doubleWide ) ;
if ( ! handler . saving )
{
if ( ammMin > ammMax )
2024-01-15 18:19:21 +02:00
{
2023-10-19 16:19:09 +02:00
logMod - > error ( " Invalid creature '%s' configuration, advMapAmount.min > advMapAmount.max " , identifier ) ;
2024-01-15 18:19:21 +02:00
std : : swap ( ammMin , ammMax ) ;
}
2023-10-19 16:19:09 +02:00
}
}
CCreatureHandler : : CCreatureHandler ( )
: expAfterUpgrade ( 0 )
{
loadCommanders ( ) ;
}
void CCreatureHandler : : loadCommanders ( )
{
auto configResource = JsonPath : : builtin ( " config/commanders.json " ) ;
std : : string modSource = VLC - > modh - > findResourceOrigin ( configResource ) ;
JsonNode data ( configResource ) ;
2024-02-13 14:34:16 +02:00
data . setModScope ( modSource ) ;
2023-10-19 16:19:09 +02:00
const JsonNode & config = data ; // switch to const data accessors
for ( auto bonus : config [ " bonusPerLevel " ] . Vector ( ) )
{
commanderLevelPremy . push_back ( JsonUtils : : parseBonus ( bonus . Vector ( ) ) ) ;
}
int i = 0 ;
for ( auto skill : config [ " skillLevels " ] . Vector ( ) )
{
skillLevels . emplace_back ( ) ;
for ( auto skillLevel : skill [ " levels " ] . Vector ( ) )
{
skillLevels [ i ] . push_back ( static_cast < ui8 > ( skillLevel . Float ( ) ) ) ;
}
+ + i ;
}
for ( auto ability : config [ " abilityRequirements " ] . Vector ( ) )
{
std : : pair < std : : shared_ptr < Bonus > , std : : pair < ui8 , ui8 > > a ;
a . first = JsonUtils : : parseBonus ( ability [ " ability " ] . Vector ( ) ) ;
a . second . first = static_cast < ui8 > ( ability [ " skills " ] . Vector ( ) [ 0 ] . Float ( ) ) ;
a . second . second = static_cast < ui8 > ( ability [ " skills " ] . Vector ( ) [ 1 ] . Float ( ) ) ;
skillRequirements . push_back ( a ) ;
}
}
void CCreatureHandler : : loadBonuses ( JsonNode & creature , std : : string bonuses ) const
{
auto makeBonusNode = [ & ] ( const std : : string & type , double val = 0 ) - > JsonNode
{
JsonNode ret ;
ret [ " type " ] . String ( ) = type ;
ret [ " val " ] . Float ( ) = val ;
return ret ;
} ;
static const std : : map < std : : string , JsonNode > abilityMap =
{
{ " FLYING_ARMY " , makeBonusNode ( " FLYING " ) } ,
{ " SHOOTING_ARMY " , makeBonusNode ( " SHOOTER " ) } ,
{ " SIEGE_WEAPON " , makeBonusNode ( " SIEGE_WEAPON " ) } ,
{ " const_free_attack " , makeBonusNode ( " BLOCKS_RETALIATION " ) } ,
{ " IS_UNDEAD " , makeBonusNode ( " UNDEAD " ) } ,
{ " const_no_melee_penalty " , makeBonusNode ( " NO_MELEE_PENALTY " ) } ,
{ " const_jousting " , makeBonusNode ( " JOUSTING " , 5 ) } ,
{ " KING_1 " , makeBonusNode ( " KING " ) } , // Slayer with no expertise
{ " KING_2 " , makeBonusNode ( " KING " , 2 ) } , // Advanced Slayer or better
{ " KING_3 " , makeBonusNode ( " KING " , 3 ) } , // Expert Slayer only
{ " const_no_wall_penalty " , makeBonusNode ( " NO_WALL_PENALTY " ) } ,
{ " MULTI_HEADED " , makeBonusNode ( " ATTACKS_ALL_ADJACENT " ) } ,
{ " IMMUNE_TO_MIND_SPELLS " , makeBonusNode ( " MIND_IMMUNITY " ) } ,
{ " HAS_EXTENDED_ATTACK " , makeBonusNode ( " TWO_HEX_ATTACK_BREATH " ) }
} ;
auto hasAbility = [ & ] ( const std : : string & name ) - > bool
{
return boost : : algorithm : : find_first ( bonuses , name ) ;
} ;
for ( const auto & a : abilityMap )
{
if ( hasAbility ( a . first ) )
creature [ " abilities " ] [ a . first ] = a . second ;
}
if ( hasAbility ( " DOUBLE_WIDE " ) )
creature [ " doubleWide " ] . Bool ( ) = true ;
if ( hasAbility ( " const_raises_morale " ) )
{
JsonNode node = makeBonusNode ( " MORALE " ) ;
node [ " val " ] . Float ( ) = 1 ;
node [ " propagator " ] . String ( ) = " HERO " ;
creature [ " abilities " ] [ " const_raises_morale " ] = node ;
}
}
std : : vector < JsonNode > CCreatureHandler : : loadLegacyData ( )
{
size_t dataSize = VLC - > settings ( ) - > getInteger ( EGameSettings : : TEXTS_CREATURE ) ;
objects . resize ( dataSize ) ;
std : : vector < JsonNode > h3Data ;
h3Data . reserve ( dataSize ) ;
CLegacyConfigParser parser ( TextPath : : builtin ( " DATA/CRTRAITS.TXT " ) ) ;
parser . endLine ( ) ; // header
// this file is a bit different in some of Russian localisations:
//ENG: Singular Plural Wood ...
//RUS: Singular Plural Plural2 Wood ...
// Try to detect which version this is by header
// TODO: use 3rd name? Stand for "whose", e.g. pikemans'
size_t namesCount = 2 ;
{
if ( parser . readString ( ) ! = " Singular " | | parser . readString ( ) ! = " Plural " )
throw std : : runtime_error ( " Incorrect format of CrTraits.txt " ) ;
if ( parser . readString ( ) = = " Plural2 " )
namesCount = 3 ;
parser . endLine ( ) ;
}
for ( size_t i = 0 ; i < dataSize ; i + + )
{
//loop till non-empty line
while ( parser . isNextEntryEmpty ( ) )
parser . endLine ( ) ;
JsonNode data ;
data [ " name " ] [ " singular " ] . String ( ) = parser . readString ( ) ;
if ( namesCount = = 3 )
parser . readString ( ) ;
data [ " name " ] [ " plural " ] . String ( ) = parser . readString ( ) ;
for ( int v = 0 ; v < 7 ; + + v )
data [ " cost " ] [ GameConstants : : RESOURCE_NAMES [ v ] ] . Float ( ) = parser . readNumber ( ) ;
data [ " fightValue " ] . Float ( ) = parser . readNumber ( ) ;
data [ " aiValue " ] . Float ( ) = parser . readNumber ( ) ;
data [ " growth " ] . Float ( ) = parser . readNumber ( ) ;
data [ " horde " ] . Float ( ) = parser . readNumber ( ) ;
data [ " hitPoints " ] . Float ( ) = parser . readNumber ( ) ;
data [ " speed " ] . Float ( ) = parser . readNumber ( ) ;
data [ " attack " ] . Float ( ) = parser . readNumber ( ) ;
data [ " defense " ] . Float ( ) = parser . readNumber ( ) ;
data [ " damage " ] [ " min " ] . Float ( ) = parser . readNumber ( ) ;
data [ " damage " ] [ " max " ] . Float ( ) = parser . readNumber ( ) ;
if ( float shots = parser . readNumber ( ) )
data [ " shots " ] . Float ( ) = shots ;
if ( float spells = parser . readNumber ( ) )
data [ " spellPoints " ] . Float ( ) = spells ;
data [ " advMapAmount " ] [ " min " ] . Float ( ) = parser . readNumber ( ) ;
data [ " advMapAmount " ] [ " max " ] . Float ( ) = parser . readNumber ( ) ;
// unused - ability text, not used since we no longer have original creature window
parser . readString ( ) ;
loadBonuses ( data , parser . readString ( ) ) ; //Attributes
h3Data . push_back ( data ) ;
}
loadAnimationInfo ( h3Data ) ;
return h3Data ;
}
CCreature * CCreatureHandler : : 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 * cre = new CCreature ( ) ;
if ( node [ " hasDoubleWeek " ] . Bool ( ) )
{
doubledCreatures . insert ( CreatureID ( index ) ) ;
}
cre - > idNumber = CreatureID ( index ) ;
cre - > iconIndex = cre - > getIndex ( ) + 2 ;
cre - > identifier = identifier ;
cre - > modScope = scope ;
JsonDeserializer handler ( nullptr , node ) ;
cre - > serializeJson ( handler ) ;
cre - > cost = ResourceSet ( node [ " cost " ] ) ;
VLC - > generaltexth - > registerString ( scope , cre - > getNameSingularTextID ( ) , node [ " name " ] [ " singular " ] . String ( ) ) ;
VLC - > generaltexth - > registerString ( scope , cre - > getNamePluralTextID ( ) , node [ " name " ] [ " plural " ] . String ( ) ) ;
2024-04-27 18:41:21 +02:00
VLC - > generaltexth - > registerString ( scope , cre - > getDescriptionTextID ( ) , node [ " description " ] . String ( ) ) ;
2023-10-19 16:19:09 +02:00
cre - > addBonus ( node [ " hitPoints " ] . Integer ( ) , BonusType : : STACK_HEALTH ) ;
cre - > addBonus ( node [ " speed " ] . Integer ( ) , BonusType : : STACKS_SPEED ) ;
2023-10-22 17:36:41 +02:00
cre - > addBonus ( node [ " attack " ] . Integer ( ) , BonusType : : PRIMARY_SKILL , BonusSubtypeID ( PrimarySkill : : ATTACK ) ) ;
cre - > addBonus ( node [ " defense " ] . Integer ( ) , BonusType : : PRIMARY_SKILL , BonusSubtypeID ( PrimarySkill : : DEFENSE ) ) ;
2023-10-19 16:19:09 +02:00
2024-01-05 16:12:02 +02:00
int minDamage = node [ " damage " ] [ " min " ] . Integer ( ) ;
int maxDamage = node [ " damage " ] [ " max " ] . Integer ( ) ;
2023-10-19 16:19:09 +02:00
2024-01-05 16:12:02 +02:00
if ( minDamage < = maxDamage )
{
cre - > addBonus ( minDamage , BonusType : : CREATURE_DAMAGE , BonusCustomSubtype : : creatureDamageMin ) ;
cre - > addBonus ( maxDamage , BonusType : : CREATURE_DAMAGE , BonusCustomSubtype : : creatureDamageMax ) ;
}
else
{
2024-01-15 18:19:21 +02:00
logMod - > error ( " Mod %s: creature %s has minimal damage (%d) greater than maximal damage (%d)! " , scope , identifier , minDamage , maxDamage ) ;
2024-01-05 16:12:02 +02:00
cre - > addBonus ( maxDamage , BonusType : : CREATURE_DAMAGE , BonusCustomSubtype : : creatureDamageMin ) ;
cre - > addBonus ( minDamage , BonusType : : CREATURE_DAMAGE , BonusCustomSubtype : : creatureDamageMax ) ;
}
2023-10-19 16:19:09 +02:00
if ( ! node [ " shots " ] . isNull ( ) )
cre - > addBonus ( node [ " shots " ] . Integer ( ) , BonusType : : SHOTS ) ;
loadStackExperience ( cre , node [ " stackExperience " ] ) ;
loadJsonAnimation ( cre , node [ " graphics " ] ) ;
loadCreatureJson ( cre , node ) ;
for ( const auto & extraName : node [ " extraNames " ] . Vector ( ) )
{
for ( const auto & type_name : getTypeNames ( ) )
registerObject ( scope , type_name , extraName . String ( ) , cre - > getIndex ( ) ) ;
}
JsonNode advMapFile = node [ " graphics " ] [ " map " ] ;
JsonNode advMapMask = node [ " graphics " ] [ " mapMask " ] ;
2024-05-20 13:00:35 +02:00
VLC - > identifiers ( ) - > requestIdentifier ( scope , " object " , " monster " , [ cre , scope , advMapFile , advMapMask ] ( si32 index )
2023-10-19 16:19:09 +02:00
{
JsonNode conf ;
2024-02-13 14:34:16 +02:00
conf . setModScope ( scope ) ;
2023-10-19 16:19:09 +02:00
VLC - > objtypeh - > loadSubObject ( cre - > identifier , conf , Obj : : MONSTER , cre - > getId ( ) . num ) ;
if ( ! advMapFile . isNull ( ) )
{
JsonNode templ ;
templ [ " animation " ] = advMapFile ;
if ( ! advMapMask . isNull ( ) )
templ [ " mask " ] = advMapMask ;
2024-02-13 14:34:16 +02:00
templ . setModScope ( scope ) ;
2023-10-19 16:19:09 +02:00
// if creature has custom advMapFile, reset any potentially imported H3M templates and use provided file instead
VLC - > objtypeh - > getHandlerFor ( Obj : : MONSTER , cre - > getId ( ) . num ) - > clearTemplates ( ) ;
VLC - > objtypeh - > getHandlerFor ( Obj : : MONSTER , cre - > getId ( ) . num ) - > addTemplate ( templ ) ;
}
// object does not have any templates - this is not usable object (e.g. pseudo-creature like Arrow Tower)
if ( VLC - > objtypeh - > getHandlerFor ( Obj : : MONSTER , cre - > getId ( ) . num ) - > getTemplates ( ) . empty ( ) )
2024-05-20 13:00:35 +02:00
{
if ( ! cre - > special )
2024-05-23 14:46:19 +02:00
throw DataLoadingException ( " Mod " + scope + " is corrupted! Please disable or reinstall this mod. Reason: creature " + cre - > getJsonKey ( ) + " has no adventure map animation but is not marked as special! " ) ;
2023-10-19 16:19:09 +02:00
VLC - > objtypeh - > removeSubObject ( Obj : : MONSTER , cre - > getId ( ) . num ) ;
2024-05-20 13:00:35 +02:00
}
2023-10-19 16:19:09 +02:00
} ) ;
return cre ;
}
const std : : vector < std : : string > & CCreatureHandler : : getTypeNames ( ) const
{
static const std : : vector < std : : string > typeNames = { " creature " } ;
return typeNames ;
}
void CCreatureHandler : : loadCrExpMod ( )
{
if ( VLC - > settings ( ) - > getBoolean ( EGameSettings : : MODULE_STACK_EXPERIENCE ) ) //reading default stack experience values
{
//Calculate rank exp values, formula appears complicated bu no parsing needed
expRanks . resize ( 8 ) ;
int dif = 0 ;
int it = 8000 ; //ignore name of this variable
expRanks [ 0 ] . push_back ( it ) ;
for ( int j = 1 ; j < 10 ; + + j ) //used for tiers 8-10, and all other probably
{
expRanks [ 0 ] . push_back ( expRanks [ 0 ] [ j - 1 ] + it + dif ) ;
dif + = it / 5 ;
}
for ( int i = 1 ; i < 8 ; + + i ) //used for tiers 1-7
{
dif = 0 ;
it = 1000 * i ;
expRanks [ i ] . push_back ( it ) ;
for ( int j = 1 ; j < 10 ; + + j )
{
expRanks [ i ] . push_back ( expRanks [ i ] [ j - 1 ] + it + dif ) ;
dif + = it / 5 ;
}
}
CLegacyConfigParser expBonParser ( TextPath : : builtin ( " DATA/CREXPMOD.TXT " ) ) ;
expBonParser . endLine ( ) ; //header
maxExpPerBattle . resize ( 8 ) ;
for ( int i = 1 ; i < 8 ; + + i )
{
expBonParser . readString ( ) ; //index
expBonParser . readString ( ) ; //float multiplier -> hardcoded
expBonParser . readString ( ) ; //ignore upgrade mod? ->hardcoded
expBonParser . readString ( ) ; //already calculated
maxExpPerBattle [ i ] = static_cast < ui32 > ( expBonParser . readNumber ( ) ) ;
expRanks [ i ] . push_back ( expRanks [ i ] . back ( ) + static_cast < ui32 > ( expBonParser . readNumber ( ) ) ) ;
expBonParser . endLine ( ) ;
}
//exp for tier >7, rank 11
expRanks [ 0 ] . push_back ( 147000 ) ;
expAfterUpgrade = 75 ; //percent
maxExpPerBattle [ 0 ] = maxExpPerBattle [ 7 ] ;
}
}
void CCreatureHandler : : loadCrExpBon ( CBonusSystemNode & globalEffects )
{
if ( VLC - > settings ( ) - > getBoolean ( EGameSettings : : MODULE_STACK_EXPERIENCE ) ) //reading default stack experience bonuses
{
logGlobal - > debug ( " \t Loading stack experience bonuses " ) ;
auto addBonusForAllCreatures = [ & ] ( std : : shared_ptr < Bonus > b ) {
auto limiter = std : : make_shared < CreatureLevelLimiter > ( ) ;
b - > addLimiter ( limiter ) ;
globalEffects . addNewBonus ( b ) ;
} ;
auto addBonusForTier = [ & ] ( int tier , std : : shared_ptr < Bonus > b ) {
assert ( vstd : : iswithin ( tier , 1 , 7 ) ) ;
//bonuses from level 7 are given to high-level creatures too
auto max = tier = = GameConstants : : CREATURES_PER_TOWN ? std : : numeric_limits < int > : : max ( ) : tier + 1 ;
auto limiter = std : : make_shared < CreatureLevelLimiter > ( tier , max ) ;
b - > addLimiter ( limiter ) ;
globalEffects . addNewBonus ( b ) ;
} ;
CLegacyConfigParser parser ( TextPath : : builtin ( " DATA/CREXPBON.TXT " ) ) ;
Bonus b ; //prototype with some default properties
b . source = BonusSource : : STACK_EXPERIENCE ;
b . duration = BonusDuration : : PERMANENT ;
b . valType = BonusValueType : : ADDITIVE_VALUE ;
b . effectRange = BonusLimitEffect : : NO_LIMIT ;
b . additionalInfo = 0 ;
b . turnsRemain = 0 ;
BonusList bl ;
parser . endLine ( ) ;
parser . readString ( ) ; //ignore index
loadStackExp ( b , bl , parser ) ;
for ( const auto & b : bl )
addBonusForAllCreatures ( b ) ; //health bonus is common for all
parser . endLine ( ) ;
for ( int i = 1 ; i < 7 ; + + i )
{
for ( int j = 0 ; j < 4 ; + + j ) //four modifiers common for tiers
{
parser . readString ( ) ; //ignore index
bl . clear ( ) ;
loadStackExp ( b , bl , parser ) ;
for ( const auto & b : bl )
addBonusForTier ( i , b ) ;
parser . endLine ( ) ;
}
}
for ( int j = 0 ; j < 4 ; + + j ) //tier 7
{
parser . readString ( ) ; //ignore index
bl . clear ( ) ;
loadStackExp ( b , bl , parser ) ;
for ( const auto & b : bl )
addBonusForTier ( 7 , b ) ;
parser . endLine ( ) ;
}
do //parse everything that's left
{
2023-11-05 18:58:07 +02:00
CreatureID sid = parser . readNumber ( ) ; //id = this particular creature ID
2023-10-19 16:19:09 +02:00
2023-10-22 17:36:41 +02:00
b . sid = BonusSourceID ( sid ) ;
2023-10-19 16:19:09 +02:00
bl . clear ( ) ;
loadStackExp ( b , bl , parser ) ;
for ( const auto & b : bl )
2023-12-31 23:43:35 +02:00
objects [ sid . getNum ( ) ] - > addNewBonus ( b ) ; //add directly to CCreature Node
2023-10-19 16:19:09 +02:00
}
while ( parser . endLine ( ) ) ;
} //end of Stack Experience
}
void CCreatureHandler : : loadAnimationInfo ( std : : vector < JsonNode > & h3Data ) const
{
CLegacyConfigParser parser ( TextPath : : builtin ( " DATA/CRANIM.TXT " ) ) ;
parser . endLine ( ) ; // header
parser . endLine ( ) ;
for ( int dd = 0 ; dd < VLC - > settings ( ) - > getInteger ( EGameSettings : : TEXTS_CREATURE ) ; + + dd )
{
while ( parser . isNextEntryEmpty ( ) & & parser . endLine ( ) ) // skip empty lines
;
loadUnitAnimInfo ( h3Data [ dd ] [ " graphics " ] , parser ) ;
parser . endLine ( ) ;
}
}
void CCreatureHandler : : loadUnitAnimInfo ( JsonNode & graphics , CLegacyConfigParser & parser ) const
{
graphics [ " timeBetweenFidgets " ] . Float ( ) = parser . readNumber ( ) ;
JsonNode & animationTime = graphics [ " animationTime " ] ;
animationTime [ " walk " ] . Float ( ) = parser . readNumber ( ) ;
animationTime [ " attack " ] . Float ( ) = parser . readNumber ( ) ;
parser . readNumber ( ) ; // unused value "Flight animation time" - H3 actually uses "Walk animation time" even for flying creatures
animationTime [ " idle " ] . Float ( ) = 10.0 ;
JsonNode & missile = graphics [ " missile " ] ;
JsonNode & offsets = missile [ " offset " ] ;
offsets [ " upperX " ] . Float ( ) = parser . readNumber ( ) ;
offsets [ " upperY " ] . Float ( ) = parser . readNumber ( ) ;
offsets [ " middleX " ] . Float ( ) = parser . readNumber ( ) ;
offsets [ " middleY " ] . Float ( ) = parser . readNumber ( ) ;
offsets [ " lowerX " ] . Float ( ) = parser . readNumber ( ) ;
offsets [ " lowerY " ] . Float ( ) = parser . readNumber ( ) ;
for ( int i = 0 ; i < 12 ; i + + )
{
JsonNode entry ;
entry . Float ( ) = parser . readNumber ( ) ;
missile [ " frameAngles " ] . Vector ( ) . push_back ( entry ) ;
}
// Unused property "troopCountLocationOffset"
parser . readNumber ( ) ;
missile [ " attackClimaxFrame " ] . Float ( ) = parser . readNumber ( ) ;
// assume that creature is not a shooter and should not have whole missile field
2024-02-13 23:42:05 +02:00
if ( missile [ " frameAngles " ] . Vector ( ) [ 0 ] . Integer ( ) = = 0 & &
missile [ " attackClimaxFrame " ] . Integer ( ) = = 0 )
2023-10-19 16:19:09 +02:00
graphics . Struct ( ) . erase ( " missile " ) ;
}
void CCreatureHandler : : loadJsonAnimation ( CCreature * cre , const JsonNode & graphics ) const
{
cre - > animation . timeBetweenFidgets = graphics [ " timeBetweenFidgets " ] . Float ( ) ;
const JsonNode & animationTime = graphics [ " animationTime " ] ;
cre - > animation . walkAnimationTime = animationTime [ " walk " ] . Float ( ) ;
cre - > animation . idleAnimationTime = animationTime [ " idle " ] . Float ( ) ;
cre - > animation . attackAnimationTime = animationTime [ " attack " ] . Float ( ) ;
const JsonNode & missile = graphics [ " missile " ] ;
const JsonNode & offsets = missile [ " offset " ] ;
cre - > animation . upperRightMissleOffsetX = static_cast < int > ( offsets [ " upperX " ] . Float ( ) ) ;
cre - > animation . upperRightMissleOffsetY = static_cast < int > ( offsets [ " upperY " ] . Float ( ) ) ;
cre - > animation . rightMissleOffsetX = static_cast < int > ( offsets [ " middleX " ] . Float ( ) ) ;
cre - > animation . rightMissleOffsetY = static_cast < int > ( offsets [ " middleY " ] . Float ( ) ) ;
cre - > animation . lowerRightMissleOffsetX = static_cast < int > ( offsets [ " lowerX " ] . Float ( ) ) ;
cre - > animation . lowerRightMissleOffsetY = static_cast < int > ( offsets [ " lowerY " ] . Float ( ) ) ;
cre - > animation . attackClimaxFrame = static_cast < int > ( missile [ " attackClimaxFrame " ] . Float ( ) ) ;
cre - > animation . missleFrameAngles = missile [ " frameAngles " ] . convertTo < std : : vector < double > > ( ) ;
cre - > smallIconName = graphics [ " iconSmall " ] . String ( ) ;
cre - > largeIconName = graphics [ " iconLarge " ] . String ( ) ;
}
void CCreatureHandler : : loadCreatureJson ( CCreature * creature , const JsonNode & config ) const
{
creature - > animDefName = AnimationPath : : fromJson ( config [ " graphics " ] [ " animation " ] ) ;
//FIXME: MOD COMPATIBILITY
if ( config [ " abilities " ] . getType ( ) = = JsonNode : : JsonType : : DATA_STRUCT )
{
for ( const auto & ability : config [ " abilities " ] . Struct ( ) )
{
if ( ! ability . second . isNull ( ) )
{
auto b = JsonUtils : : parseBonus ( ability . second ) ;
b - > source = BonusSource : : CREATURE_ABILITY ;
2023-10-22 17:36:41 +02:00
b - > sid = BonusSourceID ( creature - > getId ( ) ) ;
2023-10-19 16:19:09 +02:00
b - > duration = BonusDuration : : PERMANENT ;
creature - > addNewBonus ( b ) ;
}
}
}
else
{
for ( const JsonNode & ability : config [ " abilities " ] . Vector ( ) )
{
if ( ability . getType ( ) = = JsonNode : : JsonType : : DATA_VECTOR )
{
logMod - > error ( " Ignored outdated creature ability format in %s " , creature - > getJsonKey ( ) ) ;
}
else
{
auto b = JsonUtils : : parseBonus ( ability ) ;
b - > source = BonusSource : : CREATURE_ABILITY ;
2023-10-22 17:36:41 +02:00
b - > sid = BonusSourceID ( creature - > getId ( ) ) ;
2023-10-19 16:19:09 +02:00
b - > duration = BonusDuration : : PERMANENT ;
creature - > addNewBonus ( b ) ;
}
}
}
VLC - > identifiers ( ) - > requestIdentifier ( " faction " , config [ " faction " ] , [ = ] ( si32 faction )
{
creature - > faction = FactionID ( faction ) ;
} ) ;
for ( const JsonNode & value : config [ " upgrades " ] . Vector ( ) )
{
VLC - > identifiers ( ) - > requestIdentifier ( " creature " , value , [ = ] ( si32 identifier )
{
creature - > upgrades . insert ( CreatureID ( identifier ) ) ;
} ) ;
}
creature - > animation . projectileImageName = AnimationPath : : fromJson ( config [ " graphics " ] [ " missile " ] [ " projectile " ] ) ;
for ( const JsonNode & value : config [ " graphics " ] [ " missile " ] [ " ray " ] . Vector ( ) )
{
CCreature : : CreatureAnimation : : RayColor color ;
color . start . r = value [ " start " ] . Vector ( ) [ 0 ] . Integer ( ) ;
color . start . g = value [ " start " ] . Vector ( ) [ 1 ] . Integer ( ) ;
color . start . b = value [ " start " ] . Vector ( ) [ 2 ] . Integer ( ) ;
color . start . a = value [ " start " ] . Vector ( ) [ 3 ] . Integer ( ) ;
color . end . r = value [ " end " ] . Vector ( ) [ 0 ] . Integer ( ) ;
color . end . g = value [ " end " ] . Vector ( ) [ 1 ] . Integer ( ) ;
color . end . b = value [ " end " ] . Vector ( ) [ 2 ] . Integer ( ) ;
color . end . a = value [ " end " ] . Vector ( ) [ 3 ] . Integer ( ) ;
creature - > animation . projectileRay . push_back ( color ) ;
}
creature - > special = config [ " special " ] . Bool ( ) | | config [ " disabled " ] . Bool ( ) ;
2024-05-16 11:53:37 +02:00
creature - > excludeFromRandomization = config [ " excludeFromRandomization " ] . Bool ( ) ;
2023-10-19 16:19:09 +02:00
const JsonNode & sounds = config [ " sound " ] ;
creature - > sounds . attack = AudioPath : : fromJson ( sounds [ " attack " ] ) ;
creature - > sounds . defend = AudioPath : : fromJson ( sounds [ " defend " ] ) ;
creature - > sounds . killed = AudioPath : : fromJson ( sounds [ " killed " ] ) ;
creature - > sounds . move = AudioPath : : fromJson ( sounds [ " move " ] ) ;
creature - > sounds . shoot = AudioPath : : fromJson ( sounds [ " shoot " ] ) ;
creature - > sounds . wince = AudioPath : : fromJson ( sounds [ " wince " ] ) ;
creature - > sounds . startMoving = AudioPath : : fromJson ( sounds [ " startMoving " ] ) ;
creature - > sounds . endMoving = AudioPath : : fromJson ( sounds [ " endMoving " ] ) ;
}
void CCreatureHandler : : loadStackExperience ( CCreature * creature , const JsonNode & input ) const
{
for ( const JsonNode & exp : input . Vector ( ) )
{
const JsonVector & values = exp [ " values " ] . Vector ( ) ;
int lowerLimit = 1 ; //, upperLimit = 255;
if ( values [ 0 ] . getType ( ) = = JsonNode : : JsonType : : DATA_BOOL )
{
for ( const JsonNode & val : values )
{
if ( val . Bool ( ) )
{
// parse each bonus separately
// we can not create copies since identifiers resolution does not tracks copies
// leading to unset identifier values in copies
auto bonus = JsonUtils : : parseBonus ( exp [ " bonus " ] ) ;
bonus - > source = BonusSource : : STACK_EXPERIENCE ;
bonus - > duration = BonusDuration : : PERMANENT ;
bonus - > limiter = std : : make_shared < RankRangeLimiter > ( RankRangeLimiter ( lowerLimit ) ) ;
creature - > addNewBonus ( bonus ) ;
break ; //TODO: allow bonuses to turn off?
}
+ + lowerLimit ;
}
}
else
{
int lastVal = 0 ;
for ( const JsonNode & val : values )
{
2024-02-13 23:42:05 +02:00
if ( val . Integer ( ) ! = lastVal )
2023-10-19 16:19:09 +02:00
{
JsonNode bonusInput = exp [ " bonus " ] ;
2024-02-13 23:42:05 +02:00
bonusInput [ " val " ] . Float ( ) = val . Integer ( ) - lastVal ;
2023-10-19 16:19:09 +02:00
auto bonus = JsonUtils : : parseBonus ( bonusInput ) ;
bonus - > source = BonusSource : : STACK_EXPERIENCE ;
bonus - > duration = BonusDuration : : PERMANENT ;
bonus - > limiter . reset ( new RankRangeLimiter ( lowerLimit ) ) ;
creature - > addNewBonus ( bonus ) ;
}
lastVal = static_cast < int > ( val . Float ( ) ) ;
+ + lowerLimit ;
}
}
}
}
void CCreatureHandler : : loadStackExp ( Bonus & b , BonusList & bl , CLegacyConfigParser & parser ) const //help function for parsing CREXPBON.txt
{
bool enable = false ; //some bonuses are activated with values 2 or 1
std : : string buf = parser . readString ( ) ;
std : : string mod = parser . readString ( ) ;
switch ( buf [ 0 ] )
{
case ' H ' :
b . type = BonusType : : STACK_HEALTH ;
b . valType = BonusValueType : : PERCENT_TO_BASE ;
break ;
case ' A ' :
b . type = BonusType : : PRIMARY_SKILL ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusSubtypeID ( PrimarySkill : : ATTACK ) ;
2023-10-19 16:19:09 +02:00
break ;
case ' D ' :
b . type = BonusType : : PRIMARY_SKILL ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusSubtypeID ( PrimarySkill : : DEFENSE ) ;
2023-10-19 16:19:09 +02:00
break ;
case ' M ' : //Max damage
b . type = BonusType : : CREATURE_DAMAGE ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusCustomSubtype : : creatureDamageMax ;
2023-10-19 16:19:09 +02:00
break ;
case ' m ' : //Min damage
b . type = BonusType : : CREATURE_DAMAGE ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusCustomSubtype : : creatureDamageMin ;
2023-10-19 16:19:09 +02:00
break ;
case ' S ' :
b . type = BonusType : : STACKS_SPEED ; break ;
case ' O ' :
b . type = BonusType : : SHOTS ; break ;
case ' b ' :
b . type = BonusType : : ENEMY_DEFENCE_REDUCTION ; break ;
case ' C ' :
b . type = BonusType : : CHANGES_SPELL_COST_FOR_ALLY ; break ;
case ' d ' :
b . type = BonusType : : DEFENSIVE_STANCE ; break ;
case ' e ' :
b . type = BonusType : : DOUBLE_DAMAGE_CHANCE ;
break ;
case ' E ' :
b . type = BonusType : : DEATH_STARE ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusCustomSubtype : : deathStareGorgon ;
2023-10-19 16:19:09 +02:00
break ;
case ' F ' :
b . type = BonusType : : FEAR ; break ;
case ' g ' :
b . type = BonusType : : SPELL_DAMAGE_REDUCTION ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusSubtypeID ( SpellSchool : : ANY ) ;
2023-10-19 16:19:09 +02:00
break ;
case ' P ' :
b . type = BonusType : : CASTS ; break ;
case ' R ' :
b . type = BonusType : : ADDITIONAL_RETALIATION ; break ;
case ' W ' :
b . type = BonusType : : MAGIC_RESISTANCE ;
break ;
case ' f ' : //on-off skill
enable = true ; //sometimes format is: 2 -> 0, 1 -> 1
switch ( mod [ 0 ] )
{
case ' A ' :
b . type = BonusType : : ATTACKS_ALL_ADJACENT ; break ;
case ' b ' :
b . type = BonusType : : RETURN_AFTER_STRIKE ; break ;
case ' B ' :
b . type = BonusType : : TWO_HEX_ATTACK_BREATH ; break ;
case ' c ' :
b . type = BonusType : : JOUSTING ;
b . val = 5 ;
break ;
case ' D ' :
b . type = BonusType : : ADDITIONAL_ATTACK ; break ;
case ' f ' :
b . type = BonusType : : FEARLESS ; break ;
case ' F ' :
b . type = BonusType : : FLYING ; break ;
case ' m ' :
b . type = BonusType : : MORALE ;
b . val = 1 ;
b . valType = BonusValueType : : INDEPENDENT_MAX ;
break ;
case ' M ' :
b . type = BonusType : : NO_MORALE ; break ;
case ' p ' : //Mind spells
case ' P ' :
b . type = BonusType : : MIND_IMMUNITY ; break ;
case ' r ' :
b . type = BonusType : : REBIRTH ; //on/off? makes sense?
2023-10-22 17:36:41 +02:00
b . subtype = BonusCustomSubtype : : rebirthRegular ;
2023-10-19 16:19:09 +02:00
b . val = 20 ; //arbitrary value
break ;
case ' R ' :
b . type = BonusType : : BLOCKS_RETALIATION ; break ;
case ' s ' :
b . type = BonusType : : FREE_SHOOTING ; break ;
case ' u ' :
b . type = BonusType : : SPELL_RESISTANCE_AURA ; break ;
case ' U ' :
b . type = BonusType : : UNDEAD ; break ;
default :
logGlobal - > trace ( " Not parsed bonus %s %s " , buf , mod ) ;
return ;
break ;
}
break ;
case ' w ' : //specific spell immunities, enabled/disabled
enable = true ;
switch ( mod [ 0 ] )
{
case ' B ' : //Blind
b . type = BonusType : : SPELL_IMMUNITY ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusSubtypeID ( SpellID ( SpellID : : BLIND ) ) ;
2023-10-19 16:19:09 +02:00
b . additionalInfo = 0 ; //normal immunity
break ;
case ' H ' : //Hypnotize
b . type = BonusType : : SPELL_IMMUNITY ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusSubtypeID ( SpellID ( SpellID : : HYPNOTIZE ) ) ;
2023-10-19 16:19:09 +02:00
b . additionalInfo = 0 ; //normal immunity
break ;
case ' I ' : //Implosion
b . type = BonusType : : SPELL_IMMUNITY ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusSubtypeID ( SpellID ( SpellID : : IMPLOSION ) ) ;
2023-10-19 16:19:09 +02:00
b . additionalInfo = 0 ; //normal immunity
break ;
case ' K ' : //Berserk
b . type = BonusType : : SPELL_IMMUNITY ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusSubtypeID ( SpellID ( SpellID : : BERSERK ) ) ;
2023-10-19 16:19:09 +02:00
b . additionalInfo = 0 ; //normal immunity
break ;
case ' M ' : //Meteor Shower
b . type = BonusType : : SPELL_IMMUNITY ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusSubtypeID ( SpellID ( SpellID : : METEOR_SHOWER ) ) ;
2023-10-19 16:19:09 +02:00
b . additionalInfo = 0 ; //normal immunity
break ;
case ' N ' : //dispell beneficial spells
b . type = BonusType : : SPELL_IMMUNITY ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusSubtypeID ( SpellID ( SpellID : : DISPEL_HELPFUL_SPELLS ) ) ;
2023-10-19 16:19:09 +02:00
b . additionalInfo = 0 ; //normal immunity
break ;
case ' R ' : //Armageddon
b . type = BonusType : : SPELL_IMMUNITY ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusSubtypeID ( SpellID ( SpellID : : ARMAGEDDON ) ) ;
2023-10-19 16:19:09 +02:00
b . additionalInfo = 0 ; //normal immunity
break ;
case ' S ' : //Slow
b . type = BonusType : : SPELL_IMMUNITY ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusSubtypeID ( SpellID ( SpellID : : SLOW ) ) ;
2023-10-19 16:19:09 +02:00
b . additionalInfo = 0 ; //normal immunity
break ;
case ' 6 ' :
case ' 7 ' :
case ' 8 ' :
case ' 9 ' :
b . type = BonusType : : LEVEL_SPELL_IMMUNITY ;
b . val = std : : atoi ( mod . c_str ( ) ) - 5 ;
break ;
case ' : ' :
b . type = BonusType : : LEVEL_SPELL_IMMUNITY ;
b . val = GameConstants : : SPELL_LEVELS ; //in case someone adds higher level spells?
break ;
case ' F ' :
b . type = BonusType : : NEGATIVE_EFFECTS_IMMUNITY ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusSubtypeID ( SpellSchool : : FIRE ) ;
2023-10-19 16:19:09 +02:00
break ;
case ' O ' :
b . type = BonusType : : SPELL_DAMAGE_REDUCTION ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusSubtypeID ( SpellSchool : : FIRE ) ;
2023-10-19 16:19:09 +02:00
b . val = 100 ; //Full damage immunity
break ;
case ' f ' :
b . type = BonusType : : SPELL_SCHOOL_IMMUNITY ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusSubtypeID ( SpellSchool : : FIRE ) ;
2023-10-19 16:19:09 +02:00
break ;
case ' C ' :
b . type = BonusType : : NEGATIVE_EFFECTS_IMMUNITY ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusSubtypeID ( SpellSchool : : WATER ) ;
2023-10-19 16:19:09 +02:00
break ;
case ' W ' :
b . type = BonusType : : SPELL_DAMAGE_REDUCTION ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusSubtypeID ( SpellSchool : : WATER ) ;
2023-10-19 16:19:09 +02:00
b . val = 100 ; //Full damage immunity
break ;
case ' w ' :
b . type = BonusType : : SPELL_SCHOOL_IMMUNITY ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusSubtypeID ( SpellSchool : : WATER ) ;
2023-10-19 16:19:09 +02:00
break ;
case ' E ' :
b . type = BonusType : : SPELL_DAMAGE_REDUCTION ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusSubtypeID ( SpellSchool : : EARTH ) ;
2023-10-19 16:19:09 +02:00
b . val = 100 ; //Full damage immunity
break ;
case ' e ' :
b . type = BonusType : : SPELL_SCHOOL_IMMUNITY ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusSubtypeID ( SpellSchool : : EARTH ) ;
2023-10-19 16:19:09 +02:00
break ;
case ' A ' :
b . type = BonusType : : SPELL_DAMAGE_REDUCTION ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusSubtypeID ( SpellSchool : : AIR ) ;
2023-10-19 16:19:09 +02:00
b . val = 100 ; //Full damage immunity
break ;
case ' a ' :
b . type = BonusType : : SPELL_SCHOOL_IMMUNITY ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusSubtypeID ( SpellSchool : : AIR ) ;
2023-10-19 16:19:09 +02:00
break ;
case ' D ' :
b . type = BonusType : : SPELL_DAMAGE_REDUCTION ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusSubtypeID ( SpellSchool : : ANY ) ;
2023-10-19 16:19:09 +02:00
b . val = 100 ; //Full damage immunity
break ;
case ' 0 ' :
b . type = BonusType : : RECEPTIVE ;
break ;
case ' m ' :
b . type = BonusType : : MIND_IMMUNITY ;
break ;
default :
logGlobal - > trace ( " Not parsed bonus %s %s " , buf , mod ) ;
return ;
}
break ;
case ' i ' :
enable = true ;
b . type = BonusType : : NO_DISTANCE_PENALTY ;
break ;
case ' o ' :
enable = true ;
b . type = BonusType : : NO_WALL_PENALTY ;
break ;
case ' a ' :
case ' c ' :
case ' K ' :
case ' k ' :
b . type = BonusType : : SPELL_AFTER_ATTACK ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusSubtypeID ( SpellID ( stringToNumber ( mod ) ) ) ;
2023-10-19 16:19:09 +02:00
break ;
case ' h ' :
b . type = BonusType : : HATE ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusSubtypeID ( CreatureID ( stringToNumber ( mod ) ) ) ;
2023-10-19 16:19:09 +02:00
break ;
case ' p ' :
case ' J ' :
b . type = BonusType : : SPELL_BEFORE_ATTACK ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusSubtypeID ( SpellID ( stringToNumber ( mod ) ) ) ;
2023-10-19 16:19:09 +02:00
b . additionalInfo = 3 ; //always expert?
break ;
case ' r ' :
b . type = BonusType : : HP_REGENERATION ;
b . val = stringToNumber ( mod ) ;
break ;
case ' s ' :
b . type = BonusType : : ENCHANTED ;
2023-10-22 17:36:41 +02:00
b . subtype = BonusSubtypeID ( SpellID ( stringToNumber ( mod ) ) ) ;
2023-10-19 16:19:09 +02:00
b . valType = BonusValueType : : INDEPENDENT_MAX ;
break ;
default :
logGlobal - > trace ( " Not parsed bonus %s %s " , buf , mod ) ;
return ;
break ;
}
switch ( mod [ 0 ] )
{
case ' + ' :
case ' = ' : //should we allow percent values to stack or pick highest?
b . valType = BonusValueType : : ADDITIVE_VALUE ;
break ;
}
//limiters, range
si32 lastVal ;
si32 curVal ;
si32 lastLev = 0 ;
if ( enable ) //0 and 2 means non-active, 1 - active
{
if ( b . type ! = BonusType : : REBIRTH )
b . val = 0 ; //on-off ability, no value specified
parser . readNumber ( ) ; // 0 level is never active
for ( int i = 1 ; i < 11 ; + + i )
{
curVal = static_cast < si32 > ( parser . readNumber ( ) ) ;
if ( curVal = = 1 )
{
b . limiter . reset ( new RankRangeLimiter ( i ) ) ;
bl . push_back ( std : : make_shared < Bonus > ( b ) ) ;
break ; //never turned off it seems
}
}
}
else
{
lastVal = static_cast < si32 > ( parser . readNumber ( ) ) ;
if ( b . type = = BonusType : : HATE )
lastVal * = 10 ; //odd fix
//FIXME: value for zero level should be stored in our config files (independent of stack exp)
for ( int i = 1 ; i < 11 ; + + i )
{
curVal = static_cast < si32 > ( parser . readNumber ( ) ) ;
if ( b . type = = BonusType : : HATE )
curVal * = 10 ; //odd fix
if ( curVal > lastVal ) //threshold, add new bonus
{
b . val = curVal - lastVal ;
lastVal = curVal ;
b . limiter . reset ( new RankRangeLimiter ( i ) ) ;
bl . push_back ( std : : make_shared < Bonus > ( b ) ) ;
lastLev = i ; //start new range from here, i = previous rank
}
else if ( curVal < lastVal )
{
b . val = lastVal ;
b . limiter . reset ( new RankRangeLimiter ( lastLev , i ) ) ;
}
}
}
}
int CCreatureHandler : : stringToNumber ( std : : string & s ) const
{
boost : : algorithm : : replace_first ( s , " # " , " " ) ; //drop hash character
return std : : atoi ( s . c_str ( ) ) ;
}
CCreatureHandler : : ~ CCreatureHandler ( )
{
for ( auto & p : skillRequirements )
p . first = nullptr ;
}
CreatureID CCreatureHandler : : pickRandomMonster ( CRandomGenerator & rand , int tier ) const
{
2023-11-02 17:48:48 +02:00
std : : vector < CreatureID > allowed ;
for ( const auto & creature : objects )
2023-10-19 16:19:09 +02:00
{
2023-11-02 17:48:48 +02:00
if ( creature - > special )
continue ;
2023-10-19 16:19:09 +02:00
2024-05-16 11:53:37 +02:00
if ( creature - > excludeFromRandomization )
continue ;
2023-11-02 17:48:48 +02:00
if ( creature - > level = = tier | | tier = = - 1 )
allowed . push_back ( creature - > getId ( ) ) ;
}
2023-10-19 16:19:09 +02:00
2023-11-02 17:48:48 +02:00
if ( allowed . empty ( ) )
{
logGlobal - > warn ( " Cannot pick a random creature of tier %d! " , tier ) ;
return CreatureID : : NONE ;
2023-10-19 16:19:09 +02:00
}
2023-11-02 17:48:48 +02:00
return * RandomGeneratorUtil : : nextItem ( allowed , rand ) ;
2023-10-19 16:19:09 +02:00
}
void CCreatureHandler : : afterLoadFinalization ( )
{
}
VCMI_LIB_NAMESPACE_END