Skip to content

Commit

Permalink
Add C++ code and doc
Browse files Browse the repository at this point in the history
  • Loading branch information
b3brodie committed Dec 15, 2024
1 parent 42c3aed commit 9937048
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 15 deletions.
4 changes: 4 additions & 0 deletions doc/MAGIC.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ In `data/mods/Magiclysm` there is a template spell, copied here for your perusal
"components": [requirement_id] // an id from a requirement, like the ones you use for crafting. spell components require to cast.
"difficulty": 12, // the difficulty to learn/cast the spell
"max_level": 10, // maximum level you can achieve in the spell
"exp_formula": "constant", // choose between "constant", "linear", "exponential". Exp Per level: Constant: a, Linear: a(level), Exponential (probably): e^(level * b) * e^(b) * e^(-c * b) - e^(level * b) * e^(-c * b). Defaults to exponential if no value is given.
"experience_calc_constant_a": 1000, // adjusts the constants used in the experience level calculation formula.
"experience_calc_constant_b": 0.146661,
"experience_calc_constant_c": -62.5,
"min_accuracy" -20, // the accuracy bonus of the spell. around -15 and it gets blocked all the time
"max_accuracy": 20, // around 20 accuracy and it's basically impossible to block
"accuracy_increment": 1.5
Expand Down
2 changes: 1 addition & 1 deletion doc/NPCs.md
Original file line number Diff line number Diff line change
Expand Up @@ -1361,7 +1361,7 @@ _some functions support array arguments or kwargs, denoted with square brackets
| skill(`s`/`v`) ||| u, n | Return or set skill level<br/><br/>Example:<br/>`"condition": { "math": [ "u_skill('driving') >= 5"] }`<br/>`"condition": { "math": [ "u_skill(someskill) >= 5"] }`|
| skill_exp(`s`/`v`) ||| u, n | Return or set skill experience<br/> Argument is skill ID. <br/><br/> Optional kwargs:<br/>`format`: `s`/`v` - must be `percentage` or `raw`.<br/><br/>Example:<br/>`{ "math": [ "u_skill_exp('driving', 'format': crt_format)--"] }`|
| spell_exp(`s`/`v`) ||| u, n | Return or set spell xp<br/> Example:<br/>`"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. <br/> Example:<br/>`"math": [ "spell_exp_for_level(u_spell_level('SPELL_ID')) * 5"] }`|
| spell_exp_for_level(`s`/`v`, `d`/`v`) ||| g | Return the amount of XP necessary for a spell level for the given spell. Arguments are spell id and desired level. <br/> Example:<br/>`"math": [ "spell_exp_for_level('SPELL_ID', u_spell_level('SPELL_ID') ) * 5"] }`|
| spell_count() ||| u, n | Return number of spells the character knows.<br/><br/> Optional kwargs:<br/>`school`: `s/v` - return number of spells known of that school.<br/><br/> Example:<br/>`"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. <br/><br/> Optional kwargs:<br/>`school`: `s/v` - return number of spells known of that school. Omitting return sum of all spells character has, no matter of the class.<br/>`level`: `d/v` - count only spells that are higher or equal this field. Default 0.<br/><br/> Example:<br/>`{ "math": [ "test_var1 = u_spell_level_sum()" ] }`<br/>`{ "math": [ "test_var2 = u_spell_level_sum('school': 'MAGUS')" ] }`<br/>`{ "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.<br/>Argument is spell ID. If `"null"` is given, return the highest level of spells the character knows (read only).<br/> Example:<br/>`"condition": { "math": [ "u_spell_level('SPELL_ID') == -1"] }`|
Expand Down
4 changes: 3 additions & 1 deletion src/debug_menu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1387,7 +1387,9 @@ static void change_spells( Character &character )
character.magic->get_spellbook().emplace( splt.id, spl );
}

character.magic->get_spell( splt.id ).set_exp( spell::exp_for_level( spell_level ) );
// storing the spell to be used instead of getting it twice somehow breaks the debug functionality.
int set_to_exp = character.magic->get_spell( splt.id ).exp_for_level( spell_level );
character.magic->get_spell( splt.id ).set_exp( set_to_exp );
};

ui_adaptor spellsui;
Expand Down
51 changes: 42 additions & 9 deletions src/magic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,16 @@ std::string enum_to_string<magic_energy_type>( magic_energy_type data )
}
cata_fatal( "Invalid magic_energy_type" );
}
template<>
std::string enum_to_string<xp_formula>( xp_formula data )
{
switch( data ) {
case xp_formula::exponential: return "exponential";
case xp_formula::linear: return "linear";
case xp_formula::constant: return "constant";
}
cata_fatal( "Invalid xp_formula" );
}
// *INDENT-ON*

} // namespace io
Expand Down Expand Up @@ -250,6 +260,7 @@ const float spell_type::energy_increment_default = 0.0f;
const trait_id spell_type::spell_class_default = trait_NONE;
const magic_energy_type spell_type::energy_source_default = magic_energy_type::none;
const damage_type_id spell_type::dmg_type_default = damage_type_id::NULL_ID();
const xp_formula spell_type::experience_formula_default = xp_formula::exponential;
const int spell_type::multiple_projectiles_default = 0;
const int spell_type::difficulty_default = 0;
const int spell_type::max_level_default = 0;
Expand Down Expand Up @@ -486,6 +497,10 @@ void spell_type::load( const JsonObject &jo, const std::string_view src )
optional( jo, was_loaded, "spell_class", spell_class, spell_class_default );
optional( jo, was_loaded, "energy_source", energy_source, energy_source_default );
optional( jo, was_loaded, "damage_type", dmg_type, dmg_type_default );
optional( jo, was_loaded, "experience_formula", experience_formula, experience_formula_default );
optional( jo, was_loaded, "experience_formula_constant_a", a, 6200.0 );
optional( jo, was_loaded, "experience_formula_constant_b", b, 0.146661 );
optional( jo, was_loaded, "experience_formula_constant_c", c, -62.5 );
if( !was_loaded || jo.has_member( "difficulty" ) ) {
difficulty = get_dbl_or_var( jo, "difficulty", false, difficulty_default );
}
Expand Down Expand Up @@ -613,6 +628,8 @@ void spell_type::serialize( JsonOut &json ) const
json.member( "spell_class", spell_class, spell_class_default );
json.member( "energy_source", io::enum_to_string( energy_source ),
io::enum_to_string( energy_source_default ) );
json.member( "experience_formula", io::enum_to_string( experience_formula ),
io::enum_to_string( experience_formula_default ) );
json.member( "damage_type", dmg_type, dmg_type_default );
json.member( "difficulty", static_cast<int>( difficulty.min.dbl_val.value() ), difficulty_default );
json.member( "multiple_projectiles", static_cast<int>( multiple_projectiles.min.dbl_val.value() ),
Expand Down Expand Up @@ -1675,16 +1692,21 @@ std::string spell::damage_type_string() const
return dmg_type()->name.translated();
}

// constants defined below are just for the formula to be used,
// in order for the inverse formula to be equivalent
static constexpr double a = 6200.0;
static constexpr double b = 0.146661;
static constexpr double c = -62.5;

int spell::get_level() const
{
return type->get_level( experience );
}

int spell_type::get_level( int experience ) const
{
// you aren't at the next level unless you have the requisite xp, so floor
return std::max( static_cast<int>( std::floor( std::log( experience + a ) / b + c ) ), 0 );
if( experience_formula == xp_formula::constant ) {
return std::max( static_cast<int>( std::floor( experience / a ) ), 0 );
} else if ( experience_formula == xp_formula::linear ) {
return std::max( static_cast<int>( std::sqrt( ( 2.0 / a ) * experience + 0.25 ) - 0.5 ), 0 );
} else {
return std::max( static_cast<int>( std::floor( std::log( experience + a ) / b + c ) ), 0 );
}
}

int spell::get_effective_level() const
Expand Down Expand Up @@ -1755,13 +1777,24 @@ void spell::clear_temp_adjustments()
// helper function to calculate xp needed to be at a certain level
// pulled out as a helper function to make it easier to either be used in the future
// or easier to tweak the formula
int spell::exp_for_level( int level )
int spell::exp_for_level( int level ) const
{
return type->exp_for_level( level );
}

int spell_type::exp_for_level( int level ) const
{
// level 0 never needs xp
if( level == 0 ) {
return 0;
}
return std::ceil( std::exp( ( level - c ) * b ) ) - a;
if( experience_formula == xp_formula::constant ) {
return std::ceil( a );
} else if( experience_formula == xp_formula::linear ) {
return std::ceil( a * 0.5 * level * ( level + 1 ) );
} else {
return std::ceil( std::exp( ( level - c ) * b ) ) - a;
}
}

int spell::exp_to_next_level() const
Expand Down
31 changes: 30 additions & 1 deletion src/magic.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,15 @@ enum class spell_shape : int {
num_shapes
};

enum class xp_formula : int {
// e^(level * b) * e^(b) * e^(-c * b) - e^(level * b) * e^(-c * b). Probably
exponential,
// a(level)
linear,
// a
constant
};

template<>
struct enum_traits<magic_energy_type> {
static constexpr magic_energy_type last = magic_energy_type::last;
Expand All @@ -148,6 +157,11 @@ struct enum_traits<spell_flag> {
static constexpr spell_flag last = spell_flag::LAST;
};

template<>
struct enum_traits<xp_formula> {
static constexpr xp_formula last = xp_formula::exponential;
};

struct fake_spell {
spell_id id;

Expand Down Expand Up @@ -385,6 +399,20 @@ class spell_type
static void check_consistency();
static void reset_all();
bool is_valid() const;

// constants defined below are for the experience formula to be used,
// in order for the inverse formula to be equivalent
double a = 6200.0;
double b = 0.146661;
double c = -62.5;

// what energy do you use to cast this spell
xp_formula experience_formula = xp_formula::exponential;

// returns the exp required for the given level of the spell.
int exp_for_level( int level ) const;
// returns the level of this spell type if the spell has the given experience.
int get_level( int experience ) const;
private:
// default values

Expand Down Expand Up @@ -432,6 +460,7 @@ class spell_type
static const trait_id spell_class_default;
static const magic_energy_type energy_source_default;
static const damage_type_id dmg_type_default;
static const xp_formula experience_formula_default;
static const int difficulty_default;
static const int multiple_projectiles_default;
static const int max_level_default;
Expand Down Expand Up @@ -486,7 +515,7 @@ class spell

double bash_scaling( const Creature &caster ) const;

static int exp_for_level( int level );
int exp_for_level( int level ) const;
// how much exp you need for the spell to gain a level
int exp_to_next_level() const;
// progress to the next level, expressed as a percent
Expand Down
13 changes: 10 additions & 3 deletions src/math_parser_diag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1098,8 +1098,15 @@ diag_eval_dbl_f spell_exp_eval( char scope, std::vector<diag_value> const &param
diag_eval_dbl_f spell_exp_for_level_eval( char /* scope */,
std::vector<diag_value> const &params, diag_kwargs const &/* kwargs */ )
{
return[level = params[0]]( const_dialogue const & d ) -> double {
return spell::exp_for_level( level.dbl( d ) );
return[sid = params[0], level = params[1]]( const_dialogue const & d ) -> double {
std::string sid_str = sid.str( d );
spell_id spell( sid_str );
if( spell.is_valid() )
{
return spell->exp_for_level( level.dbl( d ) );
}

throw math::runtime_error( R"(Unknown spell id "%s" for spell_exp_for_level)", sid_str );
};
}

Expand Down Expand Up @@ -1803,7 +1810,7 @@ std::map<std::string_view, dialogue_func> const dialogue_funcs{
{ "spell_count", { "un", 0, spell_count_eval}},
{ "spell_level_sum", { "un", 0, spell_sum_eval}},
{ "spell_exp", { "un", 1, spell_exp_eval, spell_exp_ass }},
{ "spell_exp_for_level", { "g", 1, spell_exp_for_level_eval}},
{ "spell_exp_for_level", { "g", 2, spell_exp_for_level_eval}},
{ "spell_level", { "un", 1, spell_level_eval, spell_level_ass }},
{ "spell_level_adjustment", { "un", 1, spell_level_adjustment_eval, spell_level_adjustment_ass } },
{ "time", { "g", 1, time_eval, time_ass } },
Expand Down

0 comments on commit 9937048

Please sign in to comment.