2024-02-14 15:48:06 +02:00
/*
* JsonUtils.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 "JsonBonus.h"
#include "JsonValidator.h"
2024-02-13 22:19:24 +02:00
#include "../CGeneralTextHandler.h"
#include "../VCMI_Lib.h"
2024-02-14 15:48:06 +02:00
#include "../bonuses/BonusParams.h"
#include "../bonuses/Limiters.h"
#include "../bonuses/Propagators.h"
#include "../bonuses/Updaters.h"
#include "../constants/StringConstants.h"
2024-02-13 22:19:24 +02:00
#include "../modding/IdentifierStorage.h"
2024-02-14 15:48:06 +02:00
2024-02-14 20:45:14 +02:00
VCMI_LIB_USING_NAMESPACE
template < typename T >
const T parseByMap ( const std :: map < std :: string , T > & map , const JsonNode * val , const std :: string & err )
{
if ( ! val -> isNull ())
{
const std :: string & type = val -> String ();
auto it = map . find ( type );
if ( it == map . end ())
{
logMod -> error ( "Error: invalid %s%s." , err , type );
return {};
}
else
{
return it -> second ;
}
}
else
return {};
}
template < typename T >
const T parseByMapN ( const std :: map < std :: string , T > & map , const JsonNode * val , const std :: string & err )
{
if ( val -> isNumber ())
return static_cast < T > ( val -> Integer ());
else
return parseByMap < T > ( map , val , err );
}
2024-02-14 15:48:06 +02:00
static void loadBonusSubtype ( BonusSubtypeID & subtype , BonusType type , const JsonNode & node )
{
if ( node . isNull ())
{
subtype = BonusSubtypeID ();
return ;
}
if ( node . isNumber ()) // Compatibility code for 1.3 or older
{
2024-02-13 14:34:16 +02:00
logMod -> warn ( "Bonus subtype must be string! (%s)" , node . getModScope ());
2024-02-14 15:48:06 +02:00
subtype = BonusCustomSubtype ( node . Integer ());
return ;
}
if ( ! node . isString ())
{
2024-02-13 14:34:16 +02:00
logMod -> warn ( "Bonus subtype must be string! (%s)" , node . getModScope ());
2024-02-14 15:48:06 +02:00
subtype = BonusSubtypeID ();
return ;
}
switch ( type )
{
case BonusType :: MAGIC_SCHOOL_SKILL :
case BonusType :: SPELL_DAMAGE :
case BonusType :: SPELLS_OF_SCHOOL :
case BonusType :: SPELL_DAMAGE_REDUCTION :
case BonusType :: SPELL_SCHOOL_IMMUNITY :
case BonusType :: NEGATIVE_EFFECTS_IMMUNITY :
{
VLC -> identifiers () -> requestIdentifier ( "spellSchool" , node , [ & subtype ]( int32_t identifier )
{
subtype = SpellSchool ( identifier );
});
break ;
}
case BonusType :: NO_TERRAIN_PENALTY :
{
VLC -> identifiers () -> requestIdentifier ( "terrain" , node , [ & subtype ]( int32_t identifier )
{
subtype = TerrainId ( identifier );
});
break ;
}
case BonusType :: PRIMARY_SKILL :
{
VLC -> identifiers () -> requestIdentifier ( "primarySkill" , node , [ & subtype ]( int32_t identifier )
{
subtype = PrimarySkill ( identifier );
});
break ;
}
case BonusType :: IMPROVED_NECROMANCY :
case BonusType :: HERO_GRANTS_ATTACKS :
case BonusType :: BONUS_DAMAGE_CHANCE :
case BonusType :: BONUS_DAMAGE_PERCENTAGE :
case BonusType :: SPECIAL_UPGRADE :
case BonusType :: HATE :
case BonusType :: SUMMON_GUARDIANS :
case BonusType :: MANUAL_CONTROL :
{
VLC -> identifiers () -> requestIdentifier ( "creature" , node , [ & subtype ]( int32_t identifier )
{
subtype = CreatureID ( identifier );
});
break ;
}
case BonusType :: SPELL_IMMUNITY :
case BonusType :: SPELL_DURATION :
case BonusType :: SPECIAL_ADD_VALUE_ENCHANT :
case BonusType :: SPECIAL_FIXED_VALUE_ENCHANT :
case BonusType :: SPECIAL_PECULIAR_ENCHANT :
case BonusType :: SPECIAL_SPELL_LEV :
case BonusType :: SPECIFIC_SPELL_DAMAGE :
case BonusType :: SPELL :
case BonusType :: OPENING_BATTLE_SPELL :
case BonusType :: SPELL_LIKE_ATTACK :
case BonusType :: CATAPULT :
case BonusType :: CATAPULT_EXTRA_SHOTS :
case BonusType :: HEALER :
case BonusType :: SPELLCASTER :
case BonusType :: ENCHANTER :
case BonusType :: SPELL_AFTER_ATTACK :
case BonusType :: SPELL_BEFORE_ATTACK :
case BonusType :: SPECIFIC_SPELL_POWER :
case BonusType :: ENCHANTED :
case BonusType :: MORE_DAMAGE_FROM_SPELL :
case BonusType :: NOT_ACTIVE :
{
VLC -> identifiers () -> requestIdentifier ( "spell" , node , [ & subtype ]( int32_t identifier )
{
subtype = SpellID ( identifier );
});
break ;
}
case BonusType :: GENERATE_RESOURCE :
2024-04-15 21:18:45 +02:00
case BonusType :: RESOURCES_CONSTANT_BOOST :
case BonusType :: RESOURCES_TOWN_MULTIPLYING_BOOST :
2024-02-14 15:48:06 +02:00
{
VLC -> identifiers () -> requestIdentifier ( "resource" , node , [ & subtype ]( int32_t identifier )
{
subtype = GameResID ( identifier );
});
break ;
}
case BonusType :: MOVEMENT :
case BonusType :: WATER_WALKING :
case BonusType :: FLYING_MOVEMENT :
case BonusType :: NEGATE_ALL_NATURAL_IMMUNITIES :
case BonusType :: CREATURE_DAMAGE :
case BonusType :: FLYING :
case BonusType :: FIRST_STRIKE :
case BonusType :: GENERAL_DAMAGE_REDUCTION :
case BonusType :: PERCENTAGE_DAMAGE_BOOST :
case BonusType :: SOUL_STEAL :
case BonusType :: TRANSMUTATION :
case BonusType :: DESTRUCTION :
case BonusType :: DEATH_STARE :
case BonusType :: REBIRTH :
case BonusType :: VISIONS :
case BonusType :: SPELLS_OF_LEVEL : // spell level
case BonusType :: CREATURE_GROWTH : // creature level
{
VLC -> identifiers () -> requestIdentifier ( "bonusSubtype" , node , [ & subtype ]( int32_t identifier )
{
subtype = BonusCustomSubtype ( identifier );
});
break ;
}
default :
for ( const auto & i : bonusNameMap )
if ( i . second == type )
logMod -> warn ( "Bonus type %s does not supports subtypes!" , i . first );
subtype = BonusSubtypeID ();
}
}
static void loadBonusSourceInstance ( BonusSourceID & sourceInstance , BonusSource sourceType , const JsonNode & node )
{
if ( node . isNull ())
{
sourceInstance = BonusCustomSource ();
return ;
}
if ( node . isNumber ()) // Compatibility code for 1.3 or older
{
logMod -> warn ( "Bonus source must be string!" );
sourceInstance = BonusCustomSource ( node . Integer ());
return ;
}
if ( ! node . isString ())
{
logMod -> warn ( "Bonus source must be string!" );
sourceInstance = BonusCustomSource ();
return ;
}
switch ( sourceType )
{
case BonusSource :: ARTIFACT :
case BonusSource :: ARTIFACT_INSTANCE :
{
VLC -> identifiers () -> requestIdentifier ( "artifact" , node , [ & sourceInstance ]( int32_t identifier )
{
sourceInstance = ArtifactID ( identifier );
});
break ;
}
case BonusSource :: OBJECT_TYPE :
{
VLC -> identifiers () -> requestIdentifier ( "object" , node , [ & sourceInstance ]( int32_t identifier )
{
sourceInstance = Obj ( identifier );
});
break ;
}
case BonusSource :: OBJECT_INSTANCE :
case BonusSource :: HERO_BASE_SKILL :
sourceInstance = ObjectInstanceID ( ObjectInstanceID :: decode ( node . String ()));
break ;
case BonusSource :: CREATURE_ABILITY :
{
VLC -> identifiers () -> requestIdentifier ( "creature" , node , [ & sourceInstance ]( int32_t identifier )
{
sourceInstance = CreatureID ( identifier );
});
break ;
}
case BonusSource :: TERRAIN_OVERLAY :
{
VLC -> identifiers () -> requestIdentifier ( "spell" , node , [ & sourceInstance ]( int32_t identifier )
{
sourceInstance = BattleField ( identifier );
});
break ;
}
case BonusSource :: SPELL_EFFECT :
{
VLC -> identifiers () -> requestIdentifier ( "spell" , node , [ & sourceInstance ]( int32_t identifier )
{
sourceInstance = SpellID ( identifier );
});
break ;
}
case BonusSource :: TOWN_STRUCTURE :
assert ( 0 ); // TODO
sourceInstance = BuildingTypeUniqueID ();
break ;
case BonusSource :: SECONDARY_SKILL :
{
VLC -> identifiers () -> requestIdentifier ( "secondarySkill" , node , [ & sourceInstance ]( int32_t identifier )
{
sourceInstance = SecondarySkill ( identifier );
});
break ;
}
case BonusSource :: HERO_SPECIAL :
{
VLC -> identifiers () -> requestIdentifier ( "hero" , node , [ & sourceInstance ]( int32_t identifier )
{
sourceInstance = HeroTypeID ( identifier );
});
break ;
}
case BonusSource :: CAMPAIGN_BONUS :
sourceInstance = CampaignScenarioID ( CampaignScenarioID :: decode ( node . String ()));
break ;
case BonusSource :: ARMY :
case BonusSource :: STACK_EXPERIENCE :
case BonusSource :: COMMANDER :
case BonusSource :: GLOBAL :
case BonusSource :: TERRAIN_NATIVE :
case BonusSource :: OTHER :
default :
sourceInstance = BonusSourceID ();
break ;
}
}
2024-02-14 20:45:14 +02:00
static BonusParams convertDeprecatedBonus ( const JsonNode & ability )
{
if ( vstd :: contains ( deprecatedBonusSet , ability [ "type" ]. String ()))
{
logMod -> warn ( "There is deprecated bonus found: \n %s \n Trying to convert..." , ability . toString ());
auto params = BonusParams ( ability [ "type" ]. String (),
ability [ "subtype" ]. isString () ? ability [ "subtype" ]. String () : "" ,
ability [ "subtype" ]. isNumber () ? ability [ "subtype" ]. Integer () : - 1 );
if ( params . isConverted )
{
if ( ability [ "type" ]. String () == "SECONDARY_SKILL_PREMY" && bonusValueMap . find ( ability [ "valueType" ]. String ()) -> second == BonusValueType :: PERCENT_TO_BASE ) //assume secondary skill special
{
params . valueType = BonusValueType :: PERCENT_TO_TARGET_TYPE ;
params . targetType = BonusSource :: SECONDARY_SKILL ;
}
logMod -> warn ( "Please, use this bonus: \n %s \n Converted successfully!" , params . toJson (). toString ());
return params ;
}
else
logMod -> error ( "Cannot convert bonus! \n %s" , ability . toString ());
}
BonusParams ret ;
ret . isConverted = false ;
return ret ;
}
static TUpdaterPtr parseUpdater ( const JsonNode & updaterJson )
{
switch ( updaterJson . getType ())
{
case JsonNode :: JsonType :: DATA_STRING :
return parseByMap ( bonusUpdaterMap , & updaterJson , "updater type " );
break ;
case JsonNode :: JsonType :: DATA_STRUCT :
if ( updaterJson [ "type" ]. String () == "GROWS_WITH_LEVEL" )
{
auto updater = std :: make_shared < GrowsWithLevelUpdater > ();
const JsonVector param = updaterJson [ "parameters" ]. Vector ();
updater -> valPer20 = static_cast < int > ( param [ 0 ]. Integer ());
if ( param . size () > 1 )
updater -> stepSize = static_cast < int > ( param [ 1 ]. Integer ());
return updater ;
}
else if ( updaterJson [ "type" ]. String () == "ARMY_MOVEMENT" )
{
auto updater = std :: make_shared < ArmyMovementUpdater > ();
if ( updaterJson [ "parameters" ]. isVector ())
{
const auto & param = updaterJson [ "parameters" ]. Vector ();
if ( param . size () < 4 )
logMod -> warn ( "Invalid ARMY_MOVEMENT parameters, using default!" );
else
{
updater -> base = static_cast < si32 > ( param . at ( 0 ). Integer ());
updater -> divider = static_cast < si32 > ( param . at ( 1 ). Integer ());
updater -> multiplier = static_cast < si32 > ( param . at ( 2 ). Integer ());
updater -> max = static_cast < si32 > ( param . at ( 3 ). Integer ());
}
return updater ;
}
}
else
logMod -> warn ( "Unknown updater type \" %s \" " , updaterJson [ "type" ]. String ());
break ;
}
return nullptr ;
}
VCMI_LIB_NAMESPACE_BEGIN
2024-02-14 15:48:06 +02:00
std :: shared_ptr < Bonus > JsonUtils :: parseBonus ( const JsonVector & ability_vec )
{
auto b = std :: make_shared < Bonus > ();
std :: string type = ability_vec [ 0 ]. String ();
auto it = bonusNameMap . find ( type );
if ( it == bonusNameMap . end ())
{
logMod -> error ( "Error: invalid ability type %s." , type );
return b ;
}
b -> type = it -> second ;
b -> val = static_cast < si32 > ( ability_vec [ 1 ]. Float ());
loadBonusSubtype ( b -> subtype , b -> type , ability_vec [ 2 ]);
b -> additionalInfo = static_cast < si32 > ( ability_vec [ 3 ]. Float ());
b -> duration = BonusDuration :: PERMANENT ; //TODO: handle flags (as integer)
b -> turnsRemain = 0 ;
return b ;
}
void JsonUtils :: resolveAddInfo ( CAddInfo & var , const JsonNode & node )
{
const JsonNode & value = node [ "addInfo" ];
if ( ! value . isNull ())
{
switch ( value . getType ())
{
case JsonNode :: JsonType :: DATA_INTEGER :
var = static_cast < si32 > ( value . Integer ());
break ;
case JsonNode :: JsonType :: DATA_FLOAT :
var = static_cast < si32 > ( value . Float ());
break ;
case JsonNode :: JsonType :: DATA_STRING :
VLC -> identifiers () -> requestIdentifier ( value , [ & ]( si32 identifier )
{
var = identifier ;
});
break ;
case JsonNode :: JsonType :: DATA_VECTOR :
{
const JsonVector & vec = value . Vector ();
var . resize ( vec . size ());
for ( int i = 0 ; i < vec . size (); i ++ )
{
switch ( vec [ i ]. getType ())
{
case JsonNode :: JsonType :: DATA_INTEGER :
var [ i ] = static_cast < si32 > ( vec [ i ]. Integer ());
break ;
case JsonNode :: JsonType :: DATA_FLOAT :
var [ i ] = static_cast < si32 > ( vec [ i ]. Float ());
break ;
case JsonNode :: JsonType :: DATA_STRING :
VLC -> identifiers () -> requestIdentifier ( vec [ i ], [ & var , i ]( si32 identifier )
{
var [ i ] = identifier ;
});
break ;
default :
logMod -> error ( "Error! Wrong identifier used for value of addInfo[%d]." , i );
}
}
break ;
}
default :
logMod -> error ( "Error! Wrong identifier used for value of addInfo." );
}
}
}
std :: shared_ptr < ILimiter > JsonUtils :: parseLimiter ( const JsonNode & limiter )
{
switch ( limiter . getType ())
{
case JsonNode :: JsonType :: DATA_VECTOR :
{
const JsonVector & subLimiters = limiter . Vector ();
if ( subLimiters . empty ())
{
logMod -> warn ( "Warning: empty limiter list" );
return std :: make_shared < AllOfLimiter > ();
}
std :: shared_ptr < AggregateLimiter > result ;
int offset = 1 ;
// determine limiter type and offset for sub-limiters
if ( subLimiters [ 0 ]. getType () == JsonNode :: JsonType :: DATA_STRING )
{
const std :: string & aggregator = subLimiters [ 0 ]. String ();
if ( aggregator == AllOfLimiter :: aggregator )
result = std :: make_shared < AllOfLimiter > ();
else if ( aggregator == AnyOfLimiter :: aggregator )
result = std :: make_shared < AnyOfLimiter > ();
else if ( aggregator == NoneOfLimiter :: aggregator )
result = std :: make_shared < NoneOfLimiter > ();
}
if ( ! result )
{
// collapse for single limiter without explicit aggregate operator
if ( subLimiters . size () == 1 )
return parseLimiter ( subLimiters [ 0 ]);
// implicit aggregator must be allOf
result = std :: make_shared < AllOfLimiter > ();
offset = 0 ;
}
if ( subLimiters . size () == offset )
logMod -> warn ( "Warning: empty sub-limiter list" );
for ( int sl = offset ; sl < subLimiters . size (); ++ sl )
result -> add ( parseLimiter ( subLimiters [ sl ]));
return result ;
}
break ;
case JsonNode :: JsonType :: DATA_STRING : //pre-defined limiters
return parseByMap ( bonusLimiterMap , & limiter , "limiter type " );
break ;
case JsonNode :: JsonType :: DATA_STRUCT : //customizable limiters
{
std :: string limiterType = limiter [ "type" ]. String ();
const JsonVector & parameters = limiter [ "parameters" ]. Vector ();
if ( limiterType == "CREATURE_TYPE_LIMITER" )
{
auto creatureLimiter = std :: make_shared < CCreatureTypeLimiter > ();
VLC -> identifiers () -> requestIdentifier ( "creature" , parameters [ 0 ], [ = ]( si32 creature )
{
creatureLimiter -> setCreature ( CreatureID ( creature ));
});
auto includeUpgrades = false ;
if ( parameters . size () > 1 )
{
bool success = true ;
includeUpgrades = parameters [ 1 ]. TryBoolFromString ( success );
if ( ! success )
logMod -> error ( "Second parameter of '%s' limiter should be Bool" , limiterType );
}
creatureLimiter -> includeUpgrades = includeUpgrades ;
return creatureLimiter ;
}
else if ( limiterType == "HAS_ANOTHER_BONUS_LIMITER" )
{
std :: string anotherBonusType = parameters [ 0 ]. String ();
auto it = bonusNameMap . find ( anotherBonusType );
if ( it == bonusNameMap . end ())
{
logMod -> error ( "Error: invalid ability type %s." , anotherBonusType );
}
else
{
auto bonusLimiter = std :: make_shared < HasAnotherBonusLimiter > ();
bonusLimiter -> type = it -> second ;
auto findSource = [ & ]( const JsonNode & parameter )
{
if ( parameter . getType () == JsonNode :: JsonType :: DATA_STRUCT )
{
auto sourceIt = bonusSourceMap . find ( parameter [ "type" ]. String ());
if ( sourceIt != bonusSourceMap . end ())
{
bonusLimiter -> source = sourceIt -> second ;
bonusLimiter -> isSourceRelevant = true ;
if ( ! parameter [ "id" ]. isNull ()) {
loadBonusSourceInstance ( bonusLimiter -> sid , bonusLimiter -> source , parameter [ "id" ]);
bonusLimiter -> isSourceIDRelevant = true ;
}
}
}
return false ;
};
if ( parameters . size () > 1 )
{
if ( findSource ( parameters [ 1 ]) && parameters . size () == 2 )
return bonusLimiter ;
else
{
loadBonusSubtype ( bonusLimiter -> subtype , bonusLimiter -> type , parameters [ 1 ]);
bonusLimiter -> isSubtypeRelevant = true ;
if ( parameters . size () > 2 )
findSource ( parameters [ 2 ]);
}
}
return bonusLimiter ;
}
}
else if ( limiterType == "CREATURE_ALIGNMENT_LIMITER" )
{
int alignment = vstd :: find_pos ( GameConstants :: ALIGNMENT_NAMES , parameters [ 0 ]. String ());
if ( alignment == - 1 )
logMod -> error ( "Error: invalid alignment %s." , parameters [ 0 ]. String ());
else
return std :: make_shared < CreatureAlignmentLimiter > ( static_cast < EAlignment > ( alignment ));
}
else if ( limiterType == "FACTION_LIMITER" || limiterType == "CREATURE_FACTION_LIMITER" ) //Second name is deprecated, 1.2 compat
{
auto factionLimiter = std :: make_shared < FactionLimiter > ();
VLC -> identifiers () -> requestIdentifier ( "faction" , parameters [ 0 ], [ = ]( si32 faction )
{
factionLimiter -> faction = FactionID ( faction );
});
return factionLimiter ;
}
else if ( limiterType == "CREATURE_LEVEL_LIMITER" )
{
auto levelLimiter = std :: make_shared < CreatureLevelLimiter > ();
if ( ! parameters . empty ()) //If parameters is empty, level limiter works as CREATURES_ONLY limiter
{
levelLimiter -> minLevel = parameters [ 0 ]. Integer ();
if ( parameters [ 1 ]. isNumber ())
levelLimiter -> maxLevel = parameters [ 1 ]. Integer ();
}
return levelLimiter ;
}
else if ( limiterType == "CREATURE_TERRAIN_LIMITER" )
{
auto terrainLimiter = std :: make_shared < CreatureTerrainLimiter > ();
if ( ! parameters . empty ())
{
VLC -> identifiers () -> requestIdentifier ( "terrain" , parameters [ 0 ], [ = ]( si32 terrain )
{
//TODO: support limiters
//terrainLimiter->terrainType = terrain;
});
}
return terrainLimiter ;
}
else if ( limiterType == "UNIT_ON_HEXES" ) {
auto hexLimiter = std :: make_shared < UnitOnHexLimiter > ();
if ( ! parameters . empty ())
{
for ( const auto & parameter : parameters ){
if ( parameter . isNumber ())
hexLimiter -> applicableHexes . insert ( BattleHex ( parameter . Integer ()));
}
}
return hexLimiter ;
}
else
{
logMod -> error ( "Error: invalid customizable limiter type %s." , limiterType );
}
}
break ;
default :
break ;
}
return nullptr ;
}
std :: shared_ptr < Bonus > JsonUtils :: parseBonus ( const JsonNode & ability )
{
auto b = std :: make_shared < Bonus > ();
if ( ! parseBonus ( ability , b . get ()))
{
// caller code can not handle this case and presumes that returned bonus is always valid
2024-02-12 01:22:16 +02:00
logGlobal -> error ( "Failed to parse bonus! Json config was %S " , ability . toString ());
2024-02-14 15:48:06 +02:00
b -> type = BonusType :: NONE ;
return b ;
}
return b ;
}
bool JsonUtils :: parseBonus ( const JsonNode & ability , Bonus * b )
{
const JsonNode * value = nullptr ;
std :: string type = ability [ "type" ]. String ();
auto it = bonusNameMap . find ( type );
auto params = std :: make_unique < BonusParams > ( false );
if ( it == bonusNameMap . end ())
{
params = std :: make_unique < BonusParams > ( convertDeprecatedBonus ( ability ));
if ( ! params -> isConverted )
{
logMod -> error ( "Error: invalid ability type %s." , type );
return false ;
}
b -> type = params -> type ;
b -> val = params -> val . value_or ( 0 );
b -> valType = params -> valueType . value_or ( BonusValueType :: ADDITIVE_VALUE );
if ( params -> targetType )
b -> targetSourceType = params -> targetType . value ();
}
else
b -> type = it -> second ;
loadBonusSubtype ( b -> subtype , b -> type , params -> isConverted ? params -> toJson ()[ "subtype" ] : ability [ "subtype" ]);
if ( ! params -> isConverted )
{
b -> val = static_cast < si32 > ( ability [ "val" ]. Float ());
value = & ability [ "valueType" ];
if ( ! value -> isNull ())
b -> valType = static_cast < BonusValueType > ( parseByMapN ( bonusValueMap , value , "value type " ));
}
b -> stacking = ability [ "stacking" ]. String ();
resolveAddInfo ( b -> additionalInfo , ability );
b -> turnsRemain = static_cast < si32 > ( ability [ "turns" ]. Float ());
if ( ! ability [ "description" ]. isNull ())
{
if ( ability [ "description" ]. isString ())
2024-04-07 19:57:49 +03:00
b -> description . appendTextID ( ability [ "description" ]. String ());
2024-02-14 15:48:06 +02:00
if ( ability [ "description" ]. isNumber ())
2024-04-07 19:57:49 +03:00
b -> description . appendTextID ( "core.arraytxt." + std :: to_string ( ability [ "description" ]. Integer ()));
2024-02-14 15:48:06 +02:00
}
value = & ability [ "effectRange" ];
if ( ! value -> isNull ())
b -> effectRange = static_cast < BonusLimitEffect > ( parseByMapN ( bonusLimitEffect , value , "effect range " ));
value = & ability [ "duration" ];
if ( ! value -> isNull ())
{
switch ( value -> getType ())
{
case JsonNode :: JsonType :: DATA_STRING :
b -> duration = parseByMap ( bonusDurationMap , value , "duration type " );
break ;
case JsonNode :: JsonType :: DATA_VECTOR :
{
BonusDuration :: Type dur = 0 ;
for ( const JsonNode & d : value -> Vector ())
dur |= parseByMapN ( bonusDurationMap , & d , "duration type " );
b -> duration = dur ;
}
break ;
default :
logMod -> error ( "Error! Wrong bonus duration format." );
}
}
value = & ability [ "sourceType" ];
if ( ! value -> isNull ())
b -> source = static_cast < BonusSource > ( parseByMap ( bonusSourceMap , value , "source type " ));
if ( ! ability [ "sourceID" ]. isNull ())
loadBonusSourceInstance ( b -> sid , b -> source , ability [ "sourceID" ]);
value = & ability [ "targetSourceType" ];
if ( ! value -> isNull ())
b -> targetSourceType = static_cast < BonusSource > ( parseByMap ( bonusSourceMap , value , "target type " ));
value = & ability [ "limiters" ];
if ( ! value -> isNull ())
b -> limiter = parseLimiter ( * value );
value = & ability [ "propagator" ];
if ( ! value -> isNull ())
{
//ALL_CREATURES old propagator compatibility
if ( value -> String () == "ALL_CREATURES" )
{
logMod -> warn ( "ALL_CREATURES propagator is deprecated. Use GLOBAL_EFFECT propagator with CREATURES_ONLY limiter" );
b -> addLimiter ( std :: make_shared < CreatureLevelLimiter > ());
b -> propagator = bonusPropagatorMap . at ( "GLOBAL_EFFECT" );
}
else
b -> propagator = parseByMap ( bonusPropagatorMap , value , "propagator type " );
}
value = & ability [ "updater" ];
if ( ! value -> isNull ())
b -> addUpdater ( parseUpdater ( * value ));
value = & ability [ "propagationUpdater" ];
if ( ! value -> isNull ())
b -> propagationUpdater = parseUpdater ( * value );
return true ;
}
CSelector JsonUtils :: parseSelector ( const JsonNode & ability )
{
CSelector ret = Selector :: all ;
// Recursive parsers for anyOf, allOf, noneOf
const auto * value = & ability [ "allOf" ];
if ( value -> isVector ())
{
for ( const auto & andN : value -> Vector ())
ret = ret . And ( parseSelector ( andN ));
}
value = & ability [ "anyOf" ];
if ( value -> isVector ())
{
CSelector base = Selector :: none ;
for ( const auto & andN : value -> Vector ())
base = base . Or ( parseSelector ( andN ));
ret = ret . And ( base );
}
value = & ability [ "noneOf" ];
if ( value -> isVector ())
{
CSelector base = Selector :: none ;
for ( const auto & andN : value -> Vector ())
base = base . Or ( parseSelector ( andN ));
ret = ret . And ( base . Not ());
}
BonusType type = BonusType :: NONE ;
// Actual selector parser
value = & ability [ "type" ];
if ( value -> isString ())
{
auto it = bonusNameMap . find ( value -> String ());
if ( it != bonusNameMap . end ())
{
type = it -> second ;
ret = ret . And ( Selector :: type ()( it -> second ));
}
}
value = & ability [ "subtype" ];
if ( ! value -> isNull () && type != BonusType :: NONE )
{
BonusSubtypeID subtype ;
loadBonusSubtype ( subtype , type , ability );
ret = ret . And ( Selector :: subtype ()( subtype ));
}
value = & ability [ "sourceType" ];
std :: optional < BonusSource > src = std :: nullopt ; //Fixes for GCC false maybe-uninitialized
std :: optional < BonusSourceID > id = std :: nullopt ;
if ( value -> isString ())
{
auto it = bonusSourceMap . find ( value -> String ());
if ( it != bonusSourceMap . end ())
src = it -> second ;
}
value = & ability [ "sourceID" ];
if ( ! value -> isNull () && src . has_value ())
{
loadBonusSourceInstance ( * id , * src , ability );
}
if ( src && id )
ret = ret . And ( Selector :: source ( * src , * id ));
else if ( src )
ret = ret . And ( Selector :: sourceTypeSel ( * src ));
value = & ability [ "targetSourceType" ];
if ( value -> isString ())
{
auto it = bonusSourceMap . find ( value -> String ());
if ( it != bonusSourceMap . end ())
ret = ret . And ( Selector :: targetSourceType ()( it -> second ));
}
value = & ability [ "valueType" ];
if ( value -> isString ())
{
auto it = bonusValueMap . find ( value -> String ());
if ( it != bonusValueMap . end ())
ret = ret . And ( Selector :: valueType ( it -> second ));
}
CAddInfo info ;
value = & ability [ "addInfo" ];
if ( ! value -> isNull ())
{
resolveAddInfo ( info , ability [ "addInfo" ]);
ret = ret . And ( Selector :: info ()( info ));
}
value = & ability [ "effectRange" ];
if ( value -> isString ())
{
auto it = bonusLimitEffect . find ( value -> String ());
if ( it != bonusLimitEffect . end ())
ret = ret . And ( Selector :: effectRange ()( it -> second ));
}
value = & ability [ "lastsTurns" ];
if ( value -> isNumber ())
ret = ret . And ( Selector :: turns ( value -> Integer ()));
value = & ability [ "lastsDays" ];
if ( value -> isNumber ())
ret = ret . And ( Selector :: days ( value -> Integer ()));
return ret ;
}
VCMI_LIB_NAMESPACE_END