diff --git a/doc/NPCs.md b/doc/NPCs.md
index 2d065f960869f..644b67cb0cf63 100644
--- a/doc/NPCs.md
+++ b/doc/NPCs.md
@@ -1360,6 +1360,7 @@ _some functions support array arguments or kwargs, denoted with square brackets
| spell_exp(`s`/`v`) | ✅ | ✅ | u, n | Return or set spell xp
Example:
`"condition": { "math": [ "u_spell_exp('SPELL_ID')", ">=", "5"] }`|
| spell_exp_for_level(`d`/`v`) | ✅ | ❌ | g | Return the amount of XP necessary for a spell level.
Example:
`"math": [ "spell_exp_for_level(u_spell_level('SPELL_ID')) * 5"] }`|
| spell_count() | ✅ | ❌ | u, n | Return number of spells the character knows.
Optional kwargs:
`school`: `s/v` - return number of spells known of that school.
Example:
`"condition": { "math": [ "u_spell_count('school': 'MAGUS')", ">=", "10"] }`|
+| spell_level_sum() | ✅ | ❌ | u, n | Return sum of all spell levels character has; having one spell of class A with level 5, and another with lvl 10 would return 15.
Optional kwargs:
`school`: `s/v` - return number of spells known of that school. Omitting return sum of all spells character has, no matter of the class.
`level`: `d/v` - count only spells that are higher or equal this field. Default 0.
Example:
`{ "math": [ "test_var1", "=", "u_spell_level_sum()" ] }`
`{ "math": [ "test_var2", "=", "u_spell_level_sum('school': 'MAGUS')" ] }`
`{ "math": [ "test_var3", "=", "u_spell_level_sum('school': 'MAGUS', 'level': '10')" ] }`|
| spell_level(`s`/`v`) | ✅ | ✅ | u, n | Return or set level of a given spell. -1 means the spell is not known when read and that the spell should be forgotten if written.
Argument is spell ID. If `"null"` is given, return the highest level of spells the character knows (read only).
Example:
`"condition": { "math": [ "u_spell_level('SPELL_ID')", "==", "-1"] }`|
| spell_level_adjustment(`s`/`v`) | ✅ | ✅ | u, n | Return or set temporary caster level adjustment. Only useable by EoCs that trigger on the event `opens_spellbook`. Old values will be reset to 0 before the event triggers. To avoid overwriting values from other EoCs, it is recommended to adjust the values here with `+=` or `-=` instead of setting it to an absolute value.
Argument is spell ID. If `"null"` is given, adjust all spell level.
Example:
`{ "math": [ "u_spell_level_adjustment('SPELL_ID')", "+=", "3"] }`|
| spellcasting_adjustment(`s`/`v`) | ❌ | ✅ | u | Temporary alters a property of spellcasting. Only useable by EoCs that trigger on the event `opens_spellbook`. Old values will be reset to default values before the event triggers. Multipliers have a default value of 1, while adjustments have a default value of 0. Assignment functions as an adjustment to the default value by the value assigned. So a `=` functions as you would expect a `+=` to function. Reading these values are not possible, and therefore using `+=` is not possible.
Possible argument values:
`caster_level` - Adjustment - alters the caster level of the given spell(s). Works much like spell_level_adjustment, but will not be readable by `math` functions.
`casting_time` - Multiplier - alters the casting time of the given spell(s).
`cost` - Multiplier - alters the cost of the given spells. Note that this does not change what items may be consumed by the spell(s).
`aoe` - Multiplier - alters the area of effect of the spell(s).
`range` - Multiplier - alters the range of the spell(s).
`duration` - Multiplier - alters the duration of the spell(s).
`difficulty` - Adjustment - alters the difficulty of the spell(s), thus altering the probability of failing spellcasting.
`somatic_difficulty` - Multiplier - alters how much encumbrance affects spellcasting time and difficulty. If set to 0, it will also remove the need to have your hands free while casting. Note that as a multiplier, it starts of at 1, and setting the value actually adjusts it. So setting the valute to -1 will result in a final value of 0. Alternatively, setting it to -0,5 twice would also do the trick.
`sound` - Multiplier - alters the loudness and how much mouth encumbrance affects the spell(s).
`concentration` - Multiplier - alters how much focus alters the difficulty of the spell(s).
Optional kwargs:
`flag_blacklist`: `s/v` and
`flag_whitelist`: `s/v` - Only applies the modifier to spells that matches the blacklist and/or whitelist
`mod`: `s/v`, `school`: `s/v`, `spell`: `s/v` - Only one of these can be applied. Limits what spells will be affected. If none are specified, the modification will apply to all spells (whitelist and blacklist still applies separately).
Example:
`{ "math": [ "u_spellcasting_adjustment('casting_time', 'mod': 'magiclysm', 'flag_blacklist': 'CONSUMES_RUNES' )", "=", "-0.95" ] }`|
diff --git a/src/math_parser_diag.cpp b/src/math_parser_diag.cpp
index 9849f0802c0e7..ac73ca2509771 100644
--- a/src/math_parser_diag.cpp
+++ b/src/math_parser_diag.cpp
@@ -946,6 +946,28 @@ std::function spell_count_eval( char scope,
};
}
+std::function spell_sum_eval( char scope,
+ std::vector const &/* params */, diag_kwargs const &kwargs )
+{
+ diag_value school_value( std::string{} );
+ diag_value min_level( 0.0 );
+
+ if( kwargs.count( "school" ) != 0 ) {
+ school_value = *kwargs.at( "school" );
+ }
+
+ if( kwargs.count( "level" ) != 0 ) {
+ min_level = *kwargs.at( "level" );
+ }
+
+ return[beta = is_beta( scope ), school_value, min_level]( dialogue const & d ) {
+ std::string school_str = school_value.str( d );
+ int const min_spell_level = min_level.dbl( d );
+ const trait_id scid = school_str.empty() ? trait_id::NULL_ID() : trait_id( school_str );
+ return d.actor( beta )->get_spell_sum( scid, min_spell_level );
+ };
+}
+
std::function spell_exp_eval( char scope,
std::vector const ¶ms, diag_kwargs const &/* kwargs */ )
{
@@ -1529,6 +1551,7 @@ std::map const dialogue_eval_f{
{ "skill", { "un", 1, skill_eval } },
{ "skill_exp", { "un", 1, skill_exp_eval } },
{ "spell_count", { "un", 0, spell_count_eval}},
+ { "spell_level_sum", { "un", 0, spell_sum_eval}},
{ "spell_exp", { "un", 1, spell_exp_eval}},
{ "spell_exp_for_level", { "g", 1, spell_exp_for_level_eval}},
{ "spell_level", { "un", 1, spell_level_eval}},
diff --git a/src/talker.h b/src/talker.h
index c251552a4d7c1..9b26f503690fc 100644
--- a/src/talker.h
+++ b/src/talker.h
@@ -215,6 +215,9 @@ class talker
virtual int get_spell_count( const trait_id & ) const {
return 0;
}
+ virtual int get_spell_sum( const trait_id &school, int spell_limit ) const {
+ return 0;
+ }
virtual void set_spell_level( const spell_id &, int ) {}
virtual void set_spell_exp( const spell_id &, int ) {}
virtual void set_skill_level( const skill_id &, int ) {}
diff --git a/src/talker_character.cpp b/src/talker_character.cpp
index d6422a29619bd..2d4df8fdeca4d 100644
--- a/src/talker_character.cpp
+++ b/src/talker_character.cpp
@@ -392,6 +392,18 @@ int talker_character_const::get_spell_count( const trait_id &school ) const
return count;
}
+int talker_character_const::get_spell_sum( const trait_id &school, int min_level ) const
+{
+ int count = 0;
+
+ for( const spell *sp : me_chr_const->magic->get_spells() ) {
+ if( school.is_null() || sp->spell_class() == school && sp->get_effective_level() >= min_level ) {
+ count = count + sp->get_effective_level() ;
+ }
+ }
+ return count;
+}
+
void talker_character::set_spell_level( const spell_id &sp, int new_level )
{
me_chr->magic->set_spell_level( sp, new_level, me_chr );
diff --git a/src/talker_character.h b/src/talker_character.h
index d6a9b30973d87..ebb89e764db21 100644
--- a/src/talker_character.h
+++ b/src/talker_character.h
@@ -103,6 +103,7 @@ class talker_character_const: public talker_cloner
int get_spell_exp( const spell_id & ) const override;
int get_highest_spell_level() const override;
int get_spell_count( const trait_id & ) const override;
+ int get_spell_sum(const trait_id& school, int spell_limit) const;
bool knows_proficiency( const proficiency_id &proficiency ) const override;
time_duration proficiency_practiced_time( const proficiency_id & ) const override;