diff --git a/doc/MAGIC.md b/doc/MAGIC.md index 0f727de1b80ff..e6560a7c8b0a8 100644 --- a/doc/MAGIC.md +++ b/doc/MAGIC.md @@ -215,6 +215,8 @@ Another mandatory field is the spell shape. This dictates how the area of effect The following JSON fields are also used in spells and, while optional, greatly expand how spells can behave. +All fields that are numeric can also be a "variable object"( see [NPCs.md](NPCs.md) for full details) instead of a number. + | Field group | Description | Example | --- | --- | --- | `min_X`, `max_X`, ``X_increment`` | Minimun value, maximum value, and the value, that add each level. Note: your spell don't take max_X on the max lvl, it will always add the correct amount of X_increment to your spell - in spell with `min_damage: 0, max_damage: 100, damage_increment: 5` and `max_level: 10`, on level 10 the spell will deal only 50 damage, as [ 0 + (5\*10) ]. Opposite is also true, in spell with `min_damage: 0, max_damage: 25, damage_increment: 5` on level 10 will deal only 25 damage, as the highest value, described in `max_X` | `min_damage`, `min_range`, `min_aoe` @@ -228,7 +230,7 @@ The following JSON fields are also used in spells and, while optional, greatly e | `base_casting_time`, `final_casting_time`, `casting_time_increment` | Respond for amount of time user cast the spell. Same as duration, writed in moves, which allow to make spells, that can be casted for 0.5 or 0.25 seconds. Doesn't work for monsters and for items, that cast spells | "base_casting_time": 1000,
"final_casting_time": 100,
"casting_time_increment": -50, | `base_energy_cost`, `final_energy_cost`, `energy_increment` | Respond for amount of energy you spend for cast | "base_energy_cost": 30,
"final_energy_cost": 100,
"energy_increment": -6, | `field_id`, `field_chance`, `min_field_intensity`, `max_field_intensity`, `field_intensity_increment`, `field_intensity_variance` | Respond for field spawn. `field_id` describe what field will be spawned; `field_chance` describe the chance of spawning the field, as "one in `field_chance`" (1 means the probability is 100%, 2 is 50%, 3 is 33% etc.); `min_field_intensity`, `max_field_intensity` and `field_intensity_increment` are respond for the field intensity and it grow depend on level (for example, for fd_electricity intensity 1 represent spark, when intensity 10 is electric cloud); `field_intensity_variance` allow the spell randomly increase and decrease the intensity of the spell as a percent (intencity 10 and variance 0.1 means it can grow or shring at 10%, from 9 to 11)| "field_id": "fd_blood",
"field_chance": 100,
"min_field_intensity": 10,
"max_field_intensity": 10,
"field_intensity_increment": 1,
"field_intensity_variance": 0.1 -| `effect_str` | By default respond for effect, the spell can deal (see EFFECTS_JSON.md), but can vary depend on effect | "effect_str": "zapped", +| `effect_str` | By default respond for effect, the spell can deal (see [EFFECTS_JSON](EFFECTS_JSON.md)), but can vary depend on effect | "effect_str": "zapped", | `max_level` | How much you can train the spell. 0 by default | "max_level": 10, | `difficulty` | How hard to cast the spell. Higher difficulty make spell easier to fail, but also train your spell skill to the `difficulty` level (spell with difficulty 10 can train user to spellcasting lvl 10) | "difficulty": 7, | `affected_body_parts` | Respond on which body part `effect_str` will occur. Doesn't respond what body part the spell will target if you damage the target, it always aim a `torso` no matter what | "affected_body_parts": [ "head" ] diff --git a/object_creator/fake_spell_window.cpp b/object_creator/fake_spell_window.cpp index 7c47e47d0ad26..f60ea7546e2af 100644 --- a/object_creator/fake_spell_window.cpp +++ b/object_creator/fake_spell_window.cpp @@ -63,8 +63,8 @@ creator::fake_spell_window::fake_spell_window( QWidget *parent, Qt::WindowFlags [&]() { editable_spell.id = spell_id( id_box.text().toStdString() ); if( editable_spell.id.is_valid() ) { - max_level_box.setMaximum( editable_spell.id->max_level ); - min_level_box.setMaximum( editable_spell.id->max_level ); + max_level_box.setMaximum( editable_spell.id->max_level.min.dbl_val.value() ); + min_level_box.setMaximum( editable_spell.id->max_level.min.dbl_val.value() ); } } ); QObject::connect( &id_box, &QLineEdit::textChanged, this, &fake_spell_window::modified ); diff --git a/object_creator/spell_window.cpp b/object_creator/spell_window.cpp index 9b86ef287a85f..fa86e16adae9b 100644 --- a/object_creator/spell_window.cpp +++ b/object_creator/spell_window.cpp @@ -481,17 +481,17 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) base_energy_cost_box.show(); QObject::connect( &base_energy_cost_box, &QSpinBox::textChanged, [&]() { - editable_spell.base_energy_cost = base_energy_cost_box.value(); - if( editable_spell.energy_increment > 0.0f ) { + editable_spell.base_energy_cost.min.dbl_val = base_energy_cost_box.value(); + if( editable_spell.energy_increment.min.dbl_val.value() > 0.0f ) { final_energy_cost_box.setValue( std::max( final_energy_cost_box.value(), - editable_spell.base_energy_cost ) ); - } else if( editable_spell.energy_increment < 0.0f ) { + static_cast( editable_spell.base_energy_cost.min.dbl_val.value() ) ) ); + } else if( editable_spell.energy_increment.min.dbl_val.value() < 0.0f ) { final_energy_cost_box.setValue( std::min( final_energy_cost_box.value(), - editable_spell.base_energy_cost ) ); + static_cast( editable_spell.base_energy_cost.min.dbl_val.value() ) ) ); } else { - final_energy_cost_box.setValue( editable_spell.base_energy_cost ); + final_energy_cost_box.setValue( editable_spell.base_energy_cost.min.dbl_val.value() ); } - editable_spell.final_energy_cost = final_energy_cost_box.value(); + editable_spell.final_energy_cost.min.dbl_val = final_energy_cost_box.value(); write_json(); } ); @@ -504,15 +504,15 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) min_damage_box.show(); QObject::connect( &min_damage_box, &QSpinBox::textChanged, [&]() { - editable_spell.min_damage = min_damage_box.value(); - if( editable_spell.damage_increment > 0.0f ) { - max_damage_box.setValue( std::max( max_damage_box.value(), editable_spell.min_damage ) ); - } else if( editable_spell.damage_increment < 0.0f ) { - max_damage_box.setValue( std::min( max_damage_box.value(), editable_spell.min_damage ) ); + editable_spell.min_damage.min.dbl_val = min_damage_box.value(); + if( editable_spell.damage_increment.min.dbl_val.value() > 0.0f ) { + max_damage_box.setValue( std::max( max_damage_box.value(), static_cast( editable_spell.min_damage.min.dbl_val.value() ) ) ); + } else if( editable_spell.damage_increment.min.dbl_val.value() < 0.0f ) { + max_damage_box.setValue( std::min( max_damage_box.value(), static_cast( editable_spell.min_damage.min.dbl_val.value() ) ) ); } else { - max_damage_box.setValue( editable_spell.min_damage ); + max_damage_box.setValue( editable_spell.min_damage.min.dbl_val.value() ); } - editable_spell.max_damage = max_damage_box.value(); + editable_spell.max_damage.min.dbl_val = max_damage_box.value(); write_json(); } ); @@ -525,9 +525,9 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) min_range_box.show(); QObject::connect( &min_range_box, &QSpinBox::textChanged, [&]() { - editable_spell.min_range = min_range_box.value(); - max_range_box.setValue( std::max( max_range_box.value(), editable_spell.min_range ) ); - editable_spell.max_range = max_range_box.value(); + editable_spell.min_range.min.dbl_val = min_range_box.value(); + max_range_box.setValue( std::max( max_range_box.value(), static_cast( editable_spell.min_range.min.dbl_val.value() ) ) ); + editable_spell.max_range.min.dbl_val = max_range_box.value(); write_json(); } ); @@ -540,9 +540,9 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) min_aoe_box.show(); QObject::connect( &min_aoe_box, &QSpinBox::textChanged, [&]() { - editable_spell.min_aoe = min_aoe_box.value(); - max_aoe_box.setValue( std::max( max_aoe_box.value(), editable_spell.min_aoe ) ); - editable_spell.max_aoe = max_aoe_box.value(); + editable_spell.min_aoe.min.dbl_val = min_aoe_box.value(); + max_aoe_box.setValue( std::max( max_aoe_box.value(), static_cast( editable_spell.min_aoe.min.dbl_val.value() ) ) ); + editable_spell.max_aoe.min.dbl_val = max_aoe_box.value(); write_json(); } ); @@ -555,9 +555,9 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) min_dot_box.show(); QObject::connect( &min_dot_box, &QSpinBox::textChanged, [&]() { - editable_spell.min_dot = min_dot_box.value(); - max_dot_box.setValue( std::max( max_dot_box.value(), editable_spell.min_dot ) ); - editable_spell.max_dot = max_dot_box.value(); + editable_spell.min_dot.min.dbl_val = min_dot_box.value(); + max_dot_box.setValue( std::max( max_dot_box.value(), static_cast( editable_spell.min_dot.min.dbl_val.value() ) ) ); + editable_spell.max_dot.min.dbl_val = max_dot_box.value(); write_json(); } ); @@ -570,9 +570,9 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) min_pierce_box.show(); QObject::connect( &min_pierce_box, &QSpinBox::textChanged, [&]() { - editable_spell.min_pierce = min_pierce_box.value(); - max_pierce_box.setValue( std::max( max_pierce_box.value(), editable_spell.min_pierce ) ); - editable_spell.max_pierce = max_pierce_box.value(); + editable_spell.min_pierce.min.dbl_val = min_pierce_box.value(); + max_pierce_box.setValue( std::max( max_pierce_box.value(), static_cast( editable_spell.min_pierce.min.dbl_val.value() ) ) ); + editable_spell.max_pierce.min.dbl_val = max_pierce_box.value(); write_json(); } ); @@ -586,17 +586,17 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) base_casting_time_box.show(); QObject::connect( &base_casting_time_box, &QSpinBox::textChanged, [&]() { - editable_spell.base_casting_time = base_casting_time_box.value(); - if( editable_spell.casting_time_increment > 0.0f ) { + editable_spell.base_casting_time.min.dbl_val = base_casting_time_box.value(); + if( editable_spell.casting_time_increment.min.dbl_val.value() > 0.0f ) { final_casting_time_box.setValue( std::max( final_casting_time_box.value(), - editable_spell.base_casting_time ) ); - } else if( editable_spell.energy_increment < 0.0f ) { + static_cast( editable_spell.base_casting_time.min.dbl_val.value() ) ) ); + } else if( editable_spell.energy_increment.min.dbl_val.value() < 0.0f ) { final_casting_time_box.setValue( std::min( final_casting_time_box.value(), - editable_spell.base_casting_time ) ); + static_cast( editable_spell.base_casting_time.min.dbl_val.value() ) ) ); } else { - final_casting_time_box.setValue( editable_spell.base_casting_time ); + final_casting_time_box.setValue( editable_spell.base_casting_time.min.dbl_val.value() ); } - editable_spell.final_casting_time = final_casting_time_box.value(); + editable_spell.final_casting_time.min.dbl_val = final_casting_time_box.value(); write_json(); } ); @@ -610,9 +610,9 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) min_duration_box.show(); QObject::connect( &min_duration_box, &QSpinBox::textChanged, [&]() { - editable_spell.min_duration = min_duration_box.value(); - max_duration_box.setValue( std::max( max_duration_box.value(), editable_spell.min_duration ) ); - editable_spell.min_duration = max_duration_box.value(); + editable_spell.min_duration.min.dbl_val = min_duration_box.value(); + max_duration_box.setValue( std::max( max_duration_box.value(), static_cast( editable_spell.min_duration.min.dbl_val.value() ) ) ); + editable_spell.min_duration.min.dbl_val = max_duration_box.value(); write_json(); } ); @@ -683,7 +683,7 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) difficulty_box.show(); QObject::connect( &difficulty_box, &QSpinBox::textChanged, [&]() { - editable_spell.difficulty = difficulty_box.value(); + editable_spell.difficulty.min.dbl_val = difficulty_box.value(); write_json(); } ); @@ -697,7 +697,7 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) max_level_box.show(); QObject::connect( &max_level_box, &QSpinBox::textChanged, [&]() { - editable_spell.max_level = max_level_box.value(); + editable_spell.max_level.min.dbl_val = max_level_box.value(); write_json(); } ); @@ -791,7 +791,7 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) _( "the chance one_in( field_chance ) that the field spawns at a tripoint in the area of the spell. 0 and 1 are 100% chance" ) ) ); QObject::connect( &field_chance_box, &QSpinBox::textChanged, [&]() { - editable_spell.field_chance = field_chance_box.value(); + editable_spell.field_chance.min.dbl_val = field_chance_box.value(); write_json(); } ); @@ -802,10 +802,10 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) min_field_intensity_box.show(); QObject::connect( &min_field_intensity_box, &QSpinBox::textChanged, [&]() { - editable_spell.min_field_intensity = min_field_intensity_box.value(); + editable_spell.min_field_intensity.min.dbl_val = min_field_intensity_box.value(); max_field_intensity_box.setValue( std::max( max_field_intensity_box.value(), - editable_spell.min_field_intensity ) ); - editable_spell.max_field_intensity = max_field_intensity_box.value(); + static_cast(editable_spell.min_field_intensity.min.dbl_val.value() ) ) ); + editable_spell.max_field_intensity.min.dbl_val = max_field_intensity_box.value(); write_json(); } ); @@ -819,7 +819,7 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) field_intensity_increment_box.setSingleStep( 0.1 ); QObject::connect( &field_intensity_increment_box, &QDoubleSpinBox::textChanged, [&]() { - editable_spell.field_intensity_increment = field_intensity_increment_box.value(); + editable_spell.field_intensity_increment.min.dbl_val = field_intensity_increment_box.value(); write_json(); } ); @@ -830,10 +830,10 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) max_field_intensity_box.show(); QObject::connect( &max_field_intensity_box, &QSpinBox::textChanged, [&]() { - editable_spell.max_field_intensity = max_field_intensity_box.value(); + editable_spell.max_field_intensity.min.dbl_val = max_field_intensity_box.value(); min_field_intensity_box.setValue( std::min( min_field_intensity_box.value(), max_field_intensity_box.value() ) ); - editable_spell.min_field_intensity = min_field_intensity_box.value(); + editable_spell.min_field_intensity.min.dbl_val = min_field_intensity_box.value(); write_json(); } ); @@ -846,7 +846,7 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) _( "field intensity added to the map is +- ( 1 + field_intensity_variance ) * field_intensity" ) ) ); QObject::connect( &field_intensity_variance_box, &QSpinBox::textChanged, [&]() { - editable_spell.field_intensity_variance = field_intensity_variance_box.value(); + editable_spell.field_intensity_variance.min.dbl_val = field_intensity_variance_box.value(); write_json(); } ); @@ -867,7 +867,7 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) energy_increment_box.show(); QObject::connect( &energy_increment_box, &QDoubleSpinBox::textChanged, [&]() { - editable_spell.energy_increment = energy_increment_box.value(); + editable_spell.energy_increment.min.dbl_val = energy_increment_box.value(); write_json(); } ); @@ -882,7 +882,7 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) damage_increment_box.show(); QObject::connect( &damage_increment_box, &QDoubleSpinBox::textChanged, [&]() { - editable_spell.damage_increment = damage_increment_box.value(); + editable_spell.damage_increment.min.dbl_val = damage_increment_box.value(); write_json(); } ); @@ -897,7 +897,7 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) range_increment_box.show(); QObject::connect( &range_increment_box, &QDoubleSpinBox::textChanged, [&]() { - editable_spell.range_increment = range_increment_box.value(); + editable_spell.range_increment.min.dbl_val = range_increment_box.value(); write_json(); } ); @@ -911,7 +911,7 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) aoe_increment_box.show(); QObject::connect( &aoe_increment_box, &QDoubleSpinBox::textChanged, [&]() { - editable_spell.aoe_increment = aoe_increment_box.value(); + editable_spell.aoe_increment.min.dbl_val = aoe_increment_box.value(); write_json(); } ); @@ -924,7 +924,7 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) dot_increment_box.show(); QObject::connect( &dot_increment_box, &QDoubleSpinBox::textChanged, [&]() { - editable_spell.dot_increment = dot_increment_box.value(); + editable_spell.dot_increment.min.dbl_val = dot_increment_box.value(); write_json(); } ); @@ -941,7 +941,7 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) pierce_increment_box.show(); QObject::connect( &pierce_increment_box, &QDoubleSpinBox::textChanged, [&]() { - editable_spell.pierce_increment = pierce_increment_box.value(); + editable_spell.pierce_increment.min.dbl_val = pierce_increment_box.value(); write_json(); } ); @@ -956,7 +956,7 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) casting_time_increment_box.show(); QObject::connect( &casting_time_increment_box, &QDoubleSpinBox::textChanged, [&]() { - editable_spell.casting_time_increment = casting_time_increment_box.value(); + editable_spell.casting_time_increment.min.dbl_val = casting_time_increment_box.value(); write_json(); } ); @@ -973,7 +973,7 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) duration_increment_box.show(); QObject::connect( &duration_increment_box, &QDoubleSpinBox::textChanged, [&]() { - editable_spell.duration_increment = duration_increment_box.value(); + editable_spell.duration_increment.min.dbl_val = duration_increment_box.value(); write_json(); } ); @@ -1043,17 +1043,17 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) final_energy_cost_box.show(); QObject::connect( &final_energy_cost_box, &QSpinBox::textChanged, [&]() { - editable_spell.final_energy_cost = final_energy_cost_box.value(); - if( editable_spell.energy_increment > 0.0f ) { + editable_spell.final_energy_cost.min.dbl_val = final_energy_cost_box.value(); + if( editable_spell.energy_increment.min.dbl_val.value() > 0.0f) { base_energy_cost_box.setValue( std::min( base_energy_cost_box.value(), - editable_spell.final_energy_cost ) ); - } else if( editable_spell.energy_increment < 0.0f ) { + static_cast( editable_spell.final_energy_cost.min.dbl_val.value() ) ) ); + } else if( editable_spell.energy_increment.min.dbl_val.value() < 0.0f ) { base_energy_cost_box.setValue( std::max( base_energy_cost_box.value(), - editable_spell.final_energy_cost ) ); + static_cast( editable_spell.final_energy_cost.min.dbl_val.value() ) ) ); } else { - base_energy_cost_box.setValue( editable_spell.final_energy_cost ); + base_energy_cost_box.setValue( editable_spell.final_energy_cost.min.dbl_val.value() ); } - editable_spell.base_energy_cost = base_energy_cost_box.value(); + editable_spell.base_energy_cost.min.dbl_val = base_energy_cost_box.value(); write_json(); } ); @@ -1066,9 +1066,9 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) max_damage_box.show(); QObject::connect( &max_damage_box, &QSpinBox::textChanged, [&]() { - editable_spell.max_damage = max_damage_box.value(); + editable_spell.max_damage.min.dbl_val = max_damage_box.value(); min_damage_box.setValue( std::min( min_damage_box.value(), max_damage_box.value() ) ); - editable_spell.min_damage = min_damage_box.value(); + editable_spell.min_damage.min.dbl_val = min_damage_box.value(); write_json(); } ); @@ -1080,9 +1080,9 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) max_range_box.show(); QObject::connect( &max_range_box, &QSpinBox::textChanged, [&]() { - editable_spell.max_range = max_range_box.value(); + editable_spell.max_range.min.dbl_val = max_range_box.value(); min_range_box.setValue( std::min( min_range_box.value(), max_range_box.value() ) ); - editable_spell.min_range = min_range_box.value(); + editable_spell.min_range.min.dbl_val = min_range_box.value(); write_json(); } ); @@ -1094,9 +1094,9 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) max_aoe_box.show(); QObject::connect( &max_aoe_box, &QSpinBox::textChanged, [&]() { - editable_spell.max_aoe = max_aoe_box.value(); + editable_spell.max_aoe.min.dbl_val = max_aoe_box.value(); min_aoe_box.setValue( std::min( min_aoe_box.value(), max_aoe_box.value() ) ); - editable_spell.min_aoe = min_aoe_box.value(); + editable_spell.min_aoe.min.dbl_val = min_aoe_box.value(); write_json(); } ); @@ -1109,9 +1109,9 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) max_dot_box.show(); QObject::connect( &max_dot_box, &QSpinBox::textChanged, [&]() { - editable_spell.max_dot = max_dot_box.value(); + editable_spell.max_dot.min.dbl_val = max_dot_box.value(); min_dot_box.setValue( std::min( min_dot_box.value(), max_dot_box.value() ) ); - editable_spell.min_dot = min_dot_box.value(); + editable_spell.min_dot.min.dbl_val = min_dot_box.value(); write_json(); } ); @@ -1126,9 +1126,9 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) max_pierce_box.show(); QObject::connect( &max_pierce_box, &QSpinBox::textChanged, [&]() { - editable_spell.max_pierce = max_pierce_box.value(); + editable_spell.max_pierce.min.dbl_val = max_pierce_box.value(); min_pierce_box.setValue( std::min( min_pierce_box.value(), max_pierce_box.value() ) ); - editable_spell.min_pierce = min_pierce_box.value(); + editable_spell.min_pierce.min.dbl_val = min_pierce_box.value(); write_json(); } ); @@ -1143,17 +1143,17 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) final_casting_time_box.show(); QObject::connect( &final_casting_time_box, &QSpinBox::textChanged, [&]() { - editable_spell.final_casting_time = final_casting_time_box.value(); - if( editable_spell.energy_increment > 0.0f ) { + editable_spell.final_casting_time.min.dbl_val = final_casting_time_box.value(); + if( editable_spell.energy_increment.min.dbl_val.value() > 0.0f) { base_casting_time_box.setValue( std::min( base_casting_time_box.value(), - editable_spell.final_casting_time ) ); - } else if( editable_spell.energy_increment < 0.0f ) { + static_cast( editable_spell.final_casting_time.min.dbl_val.value() ) ) ); + } else if( editable_spell.energy_increment.min.dbl_val.value() < 0.0f ) { base_casting_time_box.setValue( std::max( base_casting_time_box.value(), - editable_spell.final_casting_time ) ); + static_cast( editable_spell.final_casting_time.min.dbl_val.value() ) ) ); } else { - base_casting_time_box.setValue( editable_spell.final_casting_time ); + base_casting_time_box.setValue( editable_spell.final_casting_time.min.dbl_val.value() ); } - editable_spell.base_casting_time = base_casting_time_box.value(); + editable_spell.base_casting_time.min.dbl_val = base_casting_time_box.value(); write_json(); } ); @@ -1169,9 +1169,9 @@ creator::spell_window::spell_window( QWidget *parent, Qt::WindowFlags flags ) max_duration_box.show(); QObject::connect( &max_duration_box, &QSpinBox::textChanged, [&]() { - editable_spell.max_duration = max_duration_box.value(); + editable_spell.max_duration.min.dbl_val = max_duration_box.value(); min_duration_box.setValue( std::min( min_duration_box.value(), max_duration_box.value() ) ); - editable_spell.min_duration = min_duration_box.value(); + editable_spell.min_duration.min.dbl_val = min_duration_box.value(); write_json(); } ); @@ -1393,26 +1393,26 @@ void creator::spell_window::populate_fields() } - base_energy_cost_box.setValue( sp_t.base_energy_cost ); - final_energy_cost_box.setValue( sp_t.final_energy_cost ); - min_range_box.setValue( sp_t.min_range ); - range_increment_box.setValue( sp_t.range_increment ); - max_range_box.setValue( sp_t.max_range ); - min_damage_box.setValue( sp_t.min_damage ); - damage_increment_box.setValue( sp_t.damage_increment ); - max_damage_box.setValue( sp_t.max_damage ); - min_aoe_box.setValue( sp_t.min_aoe ); - aoe_increment_box.setValue( sp_t.aoe_increment ); - max_aoe_box.setValue( sp_t.max_aoe ); - min_dot_box.setValue( sp_t.min_dot ); - dot_increment_box.setValue( sp_t.dot_increment ); - max_dot_box.setValue( sp_t.max_dot ); - min_duration_box.setValue( sp_t.min_duration ); - duration_increment_box.setValue( sp_t.duration_increment ); - max_duration_box.setValue( sp_t.max_duration ); - base_casting_time_box.setValue( sp_t.base_casting_time ); - casting_time_increment_box.setValue( sp_t.casting_time_increment ); - final_casting_time_box.setValue( sp_t.final_casting_time ); + base_energy_cost_box.setValue( sp_t.base_energy_cost.min.dbl_val.value() ); + final_energy_cost_box.setValue( sp_t.final_energy_cost.min.dbl_val.value() ); + min_range_box.setValue( sp_t.min_range.min.dbl_val.value() ); + range_increment_box.setValue( sp_t.range_increment.min.dbl_val.value() ); + max_range_box.setValue( sp_t.max_range.min.dbl_val.value() ); + min_damage_box.setValue( sp_t.min_damage.min.dbl_val.value() ); + damage_increment_box.setValue( sp_t.damage_increment.min.dbl_val.value() ); + max_damage_box.setValue( sp_t.max_damage.min.dbl_val.value() ); + min_aoe_box.setValue( sp_t.min_aoe.min.dbl_val.value() ); + aoe_increment_box.setValue( sp_t.aoe_increment.min.dbl_val.value() ); + max_aoe_box.setValue( sp_t.max_aoe.min.dbl_val.value() ); + min_dot_box.setValue( sp_t.min_dot.min.dbl_val.value() ); + dot_increment_box.setValue( sp_t.dot_increment.min.dbl_val.value() ); + max_dot_box.setValue( sp_t.max_dot.min.dbl_val.value() ); + min_duration_box.setValue( sp_t.min_duration.min.dbl_val.value() ); + duration_increment_box.setValue( sp_t.duration_increment.min.dbl_val.value() ); + max_duration_box.setValue( sp_t.max_duration.min.dbl_val.value() ); + base_casting_time_box.setValue( sp_t.base_casting_time.min.dbl_val.value() ); + casting_time_increment_box.setValue( sp_t.casting_time_increment.min.dbl_val.value() ); + final_casting_time_box.setValue( sp_t.final_casting_time.min.dbl_val.value() ); @@ -1443,8 +1443,8 @@ void creator::spell_window::populate_fields() } - difficulty_box.setValue( sp_t.difficulty ); - max_level_box.setValue( sp_t.max_level ); + difficulty_box.setValue( sp_t.difficulty.min.dbl_val.value() ); + max_level_box.setValue( sp_t.max_level.min.dbl_val.value() ); spell_message_box.setText( QString( sp_t.message.translated().c_str() ) ); @@ -1483,11 +1483,11 @@ void creator::spell_window::populate_fields() } } - field_chance_box.setValue( sp_t.field_chance ); - field_intensity_increment_box.setValue( sp_t.field_intensity_increment ); - min_field_intensity_box.setValue( sp_t.min_field_intensity ); - max_field_intensity_box.setValue( sp_t.max_field_intensity ); - field_intensity_variance_box.setValue( sp_t.field_intensity_variance ); + field_chance_box.setValue( sp_t.field_chance.min.dbl_val.value() ); + field_intensity_increment_box.setValue( sp_t.field_intensity_increment.min.dbl_val.value() ); + min_field_intensity_box.setValue( sp_t.min_field_intensity.min.dbl_val.value() ); + max_field_intensity_box.setValue( sp_t.max_field_intensity.min.dbl_val.value() ); + field_intensity_variance_box.setValue( sp_t.field_intensity_variance.min.dbl_val.value() ); diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index c405291d64ee3..6c99585ed7444 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -3559,7 +3559,7 @@ void activity_handlers::spellcasting_finish( player_activity *act, Character *yo if( !success ) { you->add_msg_if_player( game_message_params{ m_bad, gmf_bypass_cooldown }, _( "You lose your concentration!" ) ); - if( !spell_being_cast.is_max_level() && level_override == -1 ) { + if( !spell_being_cast.is_max_level( *you ) && level_override == -1 ) { // still get some experience for trying spell_being_cast.gain_exp( *you, exp_gained / 5 ); you->add_msg_if_player( m_good, _( "You gain %i experience. New total %i." ), exp_gained / 5, @@ -3605,7 +3605,7 @@ void activity_handlers::spellcasting_finish( player_activity *act, Character *yo } if( level_override == -1 ) { - if( !spell_being_cast.is_max_level() ) { + if( !spell_being_cast.is_max_level( *you ) ) { // reap the reward int old_level = spell_being_cast.get_level(); if( old_level == 0 ) { @@ -3653,8 +3653,8 @@ void activity_handlers::study_spell_do_turn( player_activity *act, Character *yo const int xp = roll_remainder( studying.exp_modifier( *you ) / to_turns( 6_seconds ) ); act->values[0] += xp; studying.gain_exp( *you, xp ); - bool leveled_up = you->practice( studying.skill(), xp, studying.get_difficulty(), true ); - if( leveled_up && studying.get_difficulty() < you->get_skill_level( studying.skill() ) ) { + bool leveled_up = you->practice( studying.skill(), xp, studying.get_difficulty( *you ), true ); + if( leveled_up && studying.get_difficulty( *you ) < you->get_skill_level( studying.skill() ) ) { you->handle_skill_warning( studying.skill(), true ); // show the skill warning on level up, since we suppress it in practice() above } diff --git a/src/creature.cpp b/src/creature.cpp index d8353c32ef51b..2ac68323dbd45 100644 --- a/src/creature.cpp +++ b/src/creature.cpp @@ -3031,6 +3031,8 @@ std::unique_ptr get_talker_for( const Creature &me ) { if( !me.is_monster() ) { return std::make_unique( static_cast( &me ) ); + } else if( me.is_monster() ) { + return std::make_unique( static_cast( &me ) ); } else { debugmsg( "Invalid creature type %s.", me.get_name() ); standard_npc default_npc( "Default" ); diff --git a/src/debug_menu.cpp b/src/debug_menu.cpp index 4ef8063d0ed1c..b3316dc08bd86 100644 --- a/src/debug_menu.cpp +++ b/src/debug_menu.cpp @@ -661,21 +661,21 @@ static void spell_description( description << spl.description() << '\n'; // Spell Casting flags - description << spell_desc::enumerate_spell_data( spl ) << '\n'; + description << spell_desc::enumerate_spell_data( spl, chrc ) << '\n'; // Spell Level: 0 / 0 (MAX) description << string_format( //~ %1$s - spell current level, %2$s - spell max level, %3$s - is max level _( "Spell Level: %1$s / %2$d %3$s" ), spl_level == -1 ? _( "Unlearned" ) : std::to_string( spl_level ), - spl.get_max_level(), - spl_level == spl.get_max_level() ? _( "(MAX)" ) : "" ) << '\n'; + spl.get_max_level( chrc ), + spl_level == spl.get_max_level( chrc ) ? _( "(MAX)" ) : "" ) << '\n'; // Difficulty: 0 ( 0.0 % Failure Chance) description << string_format( //~ %1$d - difficulty, %2$s - failure chance _( "Difficulty: %1$d (%2$s)" ), - spl.get_difficulty(), spl.colorized_fail_percent( chrc ) ) << '\n'; + spl.get_difficulty( chrc ), spl.colorized_fail_percent( chrc ) ) << '\n'; const std::string impeded = _( "(impeded)" ); @@ -686,14 +686,15 @@ static void spell_description( spl.energy_cost_string( chrc ), spell_desc::energy_cost_encumbered( spl, chrc ) ? impeded : "", spl.energy_cur_string( chrc ) ) << '\n'; - + dialogue d( get_talker_for( chrc ), nullptr ); // Casting Time: 0 (impeded) description << string_format( //~ %1$s - cast time, %2$s - is casting impeded, %3$s - casting base time _( "Casting Time: %1$s %2$s (%3$s base time) " ), to_string( time_duration::from_moves( spl.casting_time( chrc ) ) ), spell_desc::casting_time_encumbered( spl, chrc ) ? impeded : "", - to_string( time_duration::from_moves( std::get<0>( spl_data ).base_casting_time ) ) ) << '\n'; + to_string( time_duration::from_moves( std::get<0>( spl_data ).base_casting_time.evaluate( + d ) ) ) ) << '\n'; std::string targets; if( spl.is_valid_target( spell_target::none ) ) { @@ -708,7 +709,7 @@ static void spell_description( description << string_format( _( "Only affects the monsters: %1$s" ), target_ids ) << '\n'; } - const int damage = spl.damage(); + const int damage = spl.damage( chrc ); const std::string spl_eff = spl.effect(); std::string damage_string; std::string range_string; @@ -717,19 +718,19 @@ static void spell_description( if( spl_eff == "attack" ) { if( damage > 0 ) { std::string dot_string; - if( spl.damage_dot() ) { + if( spl.damage_dot( chrc ) ) { //~ amount of damage per second, abbreviated - dot_string = string_format( _( ", %1$d/sec" ), spl.damage_dot() ); + dot_string = string_format( _( ", %1$d/sec" ), spl.damage_dot( chrc ) ); } - damage_string = string_format( _( "Damage: %1$s %2$s%3$s" ), spl.damage_string(), + damage_string = string_format( _( "Damage: %1$s %2$s%3$s" ), spl.damage_string( chrc ), spl.damage_type_string(), dot_string ); damage_string = colorize( damage_string, spl.damage_type_color() ); } else if( damage < 0 ) { - damage_string = string_format( _( "Healing: %1$s" ), colorize( spl.damage_string(), + damage_string = string_format( _( "Healing: %1$s" ), colorize( spl.damage_string( chrc ), light_green ) ); } - if( spl.aoe() > 0 ) { + if( spl.aoe( chrc ) > 0 ) { std::string aoe_string_temp = _( "Spell Radius" ); std::string degree_string; if( spl.shape() == spell_shape::cone ) { @@ -738,17 +739,18 @@ static void spell_description( } else if( spl.shape() == spell_shape::line ) { aoe_string_temp = _( "Line Width" ); } - aoe_string = string_format( _( "%1$s: %2$d %3$s" ), aoe_string_temp, spl.aoe(), degree_string ); + aoe_string = string_format( _( "%1$s: %2$d %3$s" ), aoe_string_temp, spl.aoe( chrc ), + degree_string ); } } else if( spl_eff == "teleport_random" ) { - if( spl.aoe() > 0 ) { - aoe_string = string_format( _( "Variance: %1$d" ), spl.aoe() ); + if( spl.aoe( chrc ) > 0 ) { + aoe_string = string_format( _( "Variance: %1$d" ), spl.aoe( chrc ) ); } } else if( spl_eff == "spawn_item" ) { - damage_string = string_format( _( "Spawn %1$d %2$s" ), spl.damage(), - item::nname( itype_id( spl.effect_data() ), spl.damage() ) ); + damage_string = string_format( _( "Spawn %1$d %2$s" ), spl.damage( chrc ), + item::nname( itype_id( spl.effect_data() ), spl.damage( chrc ) ) ); } else if( spl_eff == "summon" ) { std::string monster_name = "FIXME"; @@ -762,8 +764,8 @@ static void spell_description( } else { monster_name = monster( mtype_id( spl.effect_data() ) ).get_name(); } - damage_string = string_format( _( "Summon: %1$d %2$s" ), spl.damage(), monster_name ); - aoe_string = string_format( _( "Spell Radius: %1$d" ), spl.aoe() ); + damage_string = string_format( _( "Summon: %1$d %2$s" ), spl.damage( chrc ), monster_name ); + aoe_string = string_format( _( "Spell Radius: %1$d" ), spl.aoe( chrc ) ); } else if( spl_eff == "targeted_polymorph" ) { std::string monster_name = spl.effect_data(); @@ -779,23 +781,23 @@ static void spell_description( } else { monster_name = mtype_id( spl.effect_data() )->nname(); } - damage_string = string_format( _( "Targets under: %1$dhp become a %2$s" ), spl.damage(), + damage_string = string_format( _( "Targets under: %1$dhp become a %2$s" ), spl.damage( chrc ), monster_name ); } else if( spl_eff == "ter_transform" ) { - aoe_string = string_format( "Spell Radius: %1$s", spl.aoe_string() ); + aoe_string = string_format( "Spell Radius: %1$s", spl.aoe_string( chrc ) ); } else if( spl_eff == "banishment" ) { - damage_string = string_format( _( "Damage: %1$s %2$s" ), spl.damage_string(), + damage_string = string_format( _( "Damage: %1$s %2$s" ), spl.damage_string( chrc ), spl.damage_type_string() ); - if( spl.aoe() > 0 ) { - aoe_string = string_format( _( "Spell Radius: %1$d" ), spl.aoe() ); + if( spl.aoe( chrc ) > 0 ) { + aoe_string = string_format( _( "Spell Radius: %1$d" ), spl.aoe( chrc ) ); } } // Range / AOE in two columns description << string_format( _( "Range: %1$s" ), - spl.range() <= 0 ? _( "self" ) : std::to_string( spl.range() ) ) << '\n'; + spl.range( chrc ) <= 0 ? _( "self" ) : std::to_string( spl.range( chrc ) ) ) << '\n'; description << aoe_string << '\n'; @@ -805,8 +807,8 @@ static void spell_description( // todo: damage over time here, when it gets implemented // Show duration for spells that endure - if( spl.duration() > 0 || spl.has_flag( spell_flag::PERMANENT ) ) { - description << string_format( _( "Duration: %1$s" ), spl.duration_string() ) << '\n'; + if( spl.duration( chrc ) > 0 || spl.has_flag( spell_flag::PERMANENT ) ) { + description << string_format( _( "Duration: %1$s" ), spl.duration_string( chrc ) ) << '\n'; } // helper function for printing tool and item component requirement lists @@ -1003,6 +1005,7 @@ static void change_spells( Character &character ) calcStartPos( spells_start, spell_selected, TERMY - 2, relative_size ); int line_number = 1; + dialogue d( get_talker_for( get_player_character() ), nullptr ); for( int i = spells_start; i < relative_size; ++i ) { if( line_number == TERMY - 1 ) { break; @@ -1017,7 +1020,7 @@ static void change_spells( Character &character ) mvwprintz( w_name.window, point( 2, line_number ), spell_color, splt.name.translated() ); mvwprintz( w_level.window, point( 2, line_number++ ), spell_color, - _( "%1$-3d/%2$3d" ), spell_level, splt.max_level ); + _( "%1$-3d/%2$3d" ), spell_level, static_cast( splt.max_level.evaluate( d ) ) ); } nc_color gray = c_light_gray; @@ -1091,10 +1094,11 @@ static void change_spells( Character &character ) spell_middle_or_id( spellid ); }; - auto toggle_all_spells = [&]( int level ) { + auto toggle_all_spells = [&]( int level, Character & character ) { + dialogue d( get_talker_for( character ), nullptr ); // -2 sets it to max level for( spell_tuple *spt : spells_relative ) { - std::get<1>( *spt ) = level > -2 ? level : std::get<0>( *spt ).max_level; + std::get<1>( *spt ) = level > -2 ? level : std::get<0>( *spt ).max_level.evaluate( d ); set_spell( std::get<0>( *spt ), std::get<1>( *spt ) ); std::get<2>( *spt ).clear(); } @@ -1111,7 +1115,7 @@ static void change_spells( Character &character ) bool showing_only_learned = false; bool force_update_description = false; - + dialogue d( get_talker_for( character ), nullptr ); while( true ) { update_description( force_update_description ); force_update_description = false; @@ -1160,14 +1164,15 @@ static void change_spells( Character &character ) } else if( action == "RIGHT" ) { int &spell_level = std::get<1>( *spells_relative[spell_selected] ); spell_level = std::min( spell_level + 1, - std::get<0>( *spells_relative[spell_selected] ).max_level ); + static_cast( std::get<0>( *spells_relative[spell_selected] ).max_level.evaluate( d ) ) ); set_spell( std::get<0>( *spells_relative[spell_selected] ), spell_level ); force_update_description = true; } else if( action == "CONFIRM" ) { int &spell_level = std::get<1>( *spells_relative[spell_selected] ); query_int( spell_level, _( "Set spell level to? Currently: %1$d" ), spell_level ); - spell_level = clamp( spell_level, -1, std::get<0>( *spells_relative[spell_selected] ).max_level ); + spell_level = clamp( spell_level, -1, + static_cast( std::get<0>( *spells_relative[spell_selected] ).max_level.evaluate( d ) ) ); set_spell( std::get<0>( *spells_relative[spell_selected] ), spell_level ); force_update_description = true; @@ -1180,13 +1185,13 @@ static void change_spells( Character &character ) } else if( action == "TOGGLE_ALL_SPELL" ) { if( toggle_spells_state == 0 ) { toggle_spells_state = 1; - toggle_all_spells( -1 ); // unlearn all spells + toggle_all_spells( -1, character ); // unlearn all spells } else if( toggle_spells_state == 1 ) { toggle_spells_state = 2; - toggle_all_spells( 0 ); // sets all spells to the minimum level + toggle_all_spells( 0, character ); // sets all spells to the minimum level } else { toggle_spells_state = 0; - toggle_all_spells( -2 ); // max level + toggle_all_spells( -2, character ); // max level } } else if( action == "SHOW_ONLY_LEARNED" ) { diff --git a/src/handle_action.cpp b/src/handle_action.cpp index 1cf13d509c0bb..d0c3bc52de8ec 100644 --- a/src/handle_action.cpp +++ b/src/handle_action.cpp @@ -1684,7 +1684,7 @@ bool bionic::activate_spell( Character &caster ) const // the return value tells us if the spell fails. if it has no spell it can't fail return true; } - spell sp = id->spell_on_activate->get_spell(); + spell sp = id->spell_on_activate->get_spell( caster ); return caster.cast_spell( sp, true ); } diff --git a/src/item.cpp b/src/item.cpp index fc5244031bd15..7b0eb0b616af5 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -6036,7 +6036,7 @@ nc_color item::color_in_inventory( const Character *const ch ) const for( const std::string &spell_id_str : actor_ptr->spells ) { const spell_id sp_id( spell_id_str ); if( player_character.magic->knows_spell( sp_id ) && - !player_character.magic->get_spell( sp_id ).is_max_level() ) { + !player_character.magic->get_spell( sp_id ).is_max_level( player_character ) ) { ret = c_yellow; } if( !player_character.magic->knows_spell( sp_id ) && diff --git a/src/iuse_actor.cpp b/src/iuse_actor.cpp index 641aa4c7620d2..8441ee14f6319 100644 --- a/src/iuse_actor.cpp +++ b/src/iuse_actor.cpp @@ -2196,7 +2196,7 @@ void learn_spell_actor::info( const item &, std::vector &dump ) const if( pc.magic->knows_spell( sp_id ) ) { const spell sp = pc.magic->get_spell( sp_id ); spell_text += ": " + string_format( _( "Level %u" ), sp.get_level() ); - if( sp.is_max_level() ) { + if( sp.is_max_level( pc ) ) { spell_text = string_format( _( "%1$s (Max)" ), spell_text ); } else { spell_text = string_format( "%s", spell_text ); @@ -2232,7 +2232,7 @@ std::optional learn_spell_actor::use( Character &p, item &, bool, const tri if( p.magic->knows_spell( sp_id ) ) { const spell sp = p.magic->get_spell( sp_id ); entry.ctxt = string_format( _( "Level %u" ), sp.get_level() ); - if( sp.is_max_level() ) { + if( sp.is_max_level( p ) ) { entry.ctxt += _( " (Max)" ); entry.enabled = false; } else { @@ -2289,7 +2289,7 @@ std::optional learn_spell_actor::use( Character &p, item &, bool, const tri } study_spell.moves_total = study_time; spell &studying = p.magic->get_spell( spell_id( spells[action] ) ); - if( studying.get_difficulty() < p.get_skill_level( studying.skill() ) ) { + if( studying.get_difficulty( p ) < p.get_skill_level( studying.skill() ) ) { p.handle_skill_warning( studying.skill(), true ); // show the skill warning on start reading, since we don't show it during } diff --git a/src/magic.cpp b/src/magic.cpp index 3968b4a3fee55..979f39aa47bbc 100644 --- a/src/magic.cpp +++ b/src/magic.cpp @@ -14,6 +14,7 @@ #include "catacharset.h" #include "character.h" #include "color.h" +#include "condition.h" #include "creature.h" #include "creature_tracker.h" #include "cursesdef.h" @@ -314,56 +315,74 @@ void spell_type::load( const JsonObject &jo, const std::string & ) if( field_input != "none" ) { field = field_type_id( field_input ); } - optional( jo, was_loaded, "field_chance", field_chance, field_chance_default ); - optional( jo, was_loaded, "min_field_intensity", min_field_intensity, min_field_intensity_default ); - optional( jo, was_loaded, "max_field_intensity", max_field_intensity, max_field_intensity_default ); - optional( jo, was_loaded, "field_intensity_increment", field_intensity_increment, - field_intensity_increment_default ); - optional( jo, was_loaded, "field_intensity_variance", field_intensity_variance, - field_intensity_variance_default ); - - optional( jo, was_loaded, "min_accuracy", min_accuracy, min_accuracy_default ); - optional( jo, was_loaded, "accuracy_increment", accuracy_increment, accuracy_increment_default ); - optional( jo, was_loaded, "max_accuracy", max_accuracy, max_accuracy_default ); - - optional( jo, was_loaded, "min_damage", min_damage, min_damage_default ); - optional( jo, was_loaded, "damage_increment", damage_increment, damage_increment_default ); - optional( jo, was_loaded, "max_damage", max_damage, max_damage_default ); - - optional( jo, was_loaded, "min_range", min_range, min_range_default ); - optional( jo, was_loaded, "range_increment", range_increment, range_increment_default ); - optional( jo, was_loaded, "max_range", max_range, max_range_default ); - - optional( jo, was_loaded, "min_aoe", min_aoe, min_aoe_default ); - optional( jo, was_loaded, "aoe_increment", aoe_increment, aoe_increment_default ); - optional( jo, was_loaded, "max_aoe", max_aoe, max_aoe_default ); - - optional( jo, was_loaded, "min_dot", min_dot, min_dot_default ); - optional( jo, was_loaded, "dot_increment", dot_increment, dot_increment_default ); - optional( jo, was_loaded, "max_dot", max_dot, max_dot_default ); - - optional( jo, was_loaded, "min_duration", min_duration, min_duration_default ); - optional( jo, was_loaded, "duration_increment", duration_increment, duration_increment_default ); - optional( jo, was_loaded, "max_duration", max_duration, max_duration_default ); - - optional( jo, was_loaded, "min_pierce", min_pierce, min_pierce_default ); - optional( jo, was_loaded, "pierce_increment", pierce_increment, pierce_increment_default ); - optional( jo, was_loaded, "max_pierce", max_pierce, max_pierce_default ); - - optional( jo, was_loaded, "base_energy_cost", base_energy_cost, base_energy_cost_default ); - optional( jo, was_loaded, "final_energy_cost", final_energy_cost, base_energy_cost ); - optional( jo, was_loaded, "energy_increment", energy_increment, energy_increment_default ); + field_chance = get_dbl_or_var( jo, "field_chance", false, field_chance_default ); + min_field_intensity = get_dbl_or_var( jo, "min_field_intensity", false, + min_field_intensity_default ); + max_field_intensity = get_dbl_or_var( jo, "max_field_intensity", false, + max_field_intensity_default ); + field_intensity_increment = get_dbl_or_var( jo, "field_intensity_increment", false, + field_intensity_increment_default ); + field_intensity_variance = get_dbl_or_var( jo, "field_intensity_variance", false, + field_intensity_variance_default ); + + min_accuracy = get_dbl_or_var( jo, "min_accuracy", false, min_accuracy_default ); + accuracy_increment = get_dbl_or_var( jo, "accuracy_increment", false, + accuracy_increment_default ); + max_accuracy = get_dbl_or_var( jo, "max_accuracy", false, max_accuracy_default ); + + min_damage = get_dbl_or_var( jo, "min_damage", false, min_damage_default ); + damage_increment = get_dbl_or_var( jo, "damage_increment", false, + damage_increment_default ); + max_damage = get_dbl_or_var( jo, "max_damage", false, max_damage_default ); + + min_range = get_dbl_or_var( jo, "min_range", false, min_range_default ); + range_increment = get_dbl_or_var( jo, "range_increment", false, range_increment_default ); + max_range = get_dbl_or_var( jo, "max_range", false, max_range_default ); + + min_aoe = get_dbl_or_var( jo, "min_aoe", false, min_aoe_default ); + aoe_increment = get_dbl_or_var( jo, "aoe_increment", false, aoe_increment_default ); + max_aoe = get_dbl_or_var( jo, "max_aoe", false, max_aoe_default ); + + min_dot = get_dbl_or_var( jo, "min_dot", false, min_dot_default ); + dot_increment = get_dbl_or_var( jo, "dot_increment", false, dot_increment_default ); + max_dot = get_dbl_or_var( jo, "max_dot", false, max_dot_default ); + + min_duration = get_dbl_or_var( jo, "min_duration", false, min_duration_default ); + duration_increment = get_dbl_or_var( jo, "duration_increment", false, + duration_increment_default ); + max_duration = get_dbl_or_var( jo, "max_duration", false, max_duration_default ); + + min_pierce = get_dbl_or_var( jo, "min_pierce", false, min_pierce_default ); + pierce_increment = get_dbl_or_var( jo, "pierce_increment", false, + pierce_increment_default ); + max_pierce = get_dbl_or_var( jo, "max_pierce", false, max_pierce_default ); + + base_energy_cost = get_dbl_or_var( jo, "base_energy_cost", false, + base_energy_cost_default ); + if( jo.has_member( "final_energy_cost" ) ) { + final_energy_cost = get_dbl_or_var( jo, "final_energy_cost" ); + } else { + final_energy_cost = base_energy_cost; + } + energy_increment = get_dbl_or_var( jo, "energy_increment", false, + energy_increment_default ); 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, "difficulty", difficulty, difficulty_default ); - optional( jo, was_loaded, "max_level", max_level, max_level_default ); + difficulty = get_dbl_or_var( jo, "difficulty", false, difficulty_default ); + max_level = get_dbl_or_var( jo, "max_level", false, max_level_default ); - optional( jo, was_loaded, "base_casting_time", base_casting_time, base_casting_time_default ); - optional( jo, was_loaded, "final_casting_time", final_casting_time, base_casting_time ); - optional( jo, was_loaded, "casting_time_increment", casting_time_increment, - casting_time_increment_default ); + base_casting_time = get_dbl_or_var( jo, "base_casting_time", false, + base_casting_time_default ); + if( jo.has_member( "final_casting_time" ) ) { + final_casting_time = get_dbl_or_var( jo, "final_casting_time" ); + } else { + final_casting_time = base_casting_time; + } + max_damage = get_dbl_or_var( jo, "max_damage", false, max_damage_default ); + casting_time_increment = get_dbl_or_var( jo, "casting_time_increment", false, + casting_time_increment_default ); for( const JsonMember member : jo.get_object( "learn_spells" ) ) { learn_spells.insert( std::pair( member.name(), member.get_int() ) ); @@ -401,48 +420,70 @@ void spell_type::serialize( JsonOut &json ) const json.member( "flags", spell_tags, enum_bitset {} ); if( field ) { json.member( "field_id", field->id().str() ); - json.member( "field_chance", field_chance, field_chance_default ); - json.member( "max_field_intensity", max_field_intensity, max_field_intensity_default ); - json.member( "min_field_intensity", min_field_intensity, min_field_intensity_default ); - json.member( "field_intensity_increment", field_intensity_increment, + json.member( "field_chance", static_cast( field_chance.min.dbl_val.value() ), + field_chance_default ); + json.member( "max_field_intensity", static_cast( max_field_intensity.min.dbl_val.value() ), + max_field_intensity_default ); + json.member( "min_field_intensity", static_cast( min_field_intensity.min.dbl_val.value() ), + min_field_intensity_default ); + json.member( "field_intensity_increment", + static_cast( field_intensity_increment.min.dbl_val.value() ), field_intensity_increment_default ); - json.member( "field_intensity_variance", field_intensity_variance, + json.member( "field_intensity_variance", + static_cast( field_intensity_variance.min.dbl_val.value() ), field_intensity_variance_default ); } - json.member( "min_damage", min_damage, min_damage_default ); - json.member( "max_damage", max_damage, max_damage_default ); - json.member( "damage_increment", damage_increment, damage_increment_default ); - json.member( "min_accuracy", min_accuracy, min_accuracy_default ); - json.member( "accuracy_increment", accuracy_increment, accuracy_increment_default ); - json.member( "max_accuracy", max_accuracy, max_accuracy_default ); - json.member( "min_range", min_range, min_range_default ); - json.member( "max_range", max_range, min_range_default ); - json.member( "range_increment", range_increment, range_increment_default ); - json.member( "min_aoe", min_aoe, min_aoe_default ); - json.member( "max_aoe", max_aoe, max_aoe_default ); - json.member( "aoe_increment", aoe_increment, aoe_increment_default ); - json.member( "min_dot", min_dot, min_dot_default ); - json.member( "max_dot", max_dot, max_dot_default ); - json.member( "dot_increment", dot_increment, dot_increment_default ); - json.member( "min_duration", min_duration, min_duration_default ); - json.member( "max_duration", max_duration, max_duration_default ); - json.member( "duration_increment", duration_increment, duration_increment_default ); - json.member( "min_pierce", min_pierce, min_pierce_default ); - json.member( "max_pierce", max_pierce, max_pierce_default ); - json.member( "pierce_increment", pierce_increment, pierce_increment_default ); - json.member( "base_energy_cost", base_energy_cost, base_energy_cost_default ); - json.member( "final_energy_cost", final_energy_cost, base_energy_cost ); - json.member( "energy_increment", energy_increment, energy_increment_default ); + json.member( "min_damage", static_cast( min_damage.min.dbl_val.value() ), min_damage_default ); + json.member( "max_damage", static_cast( max_damage.min.dbl_val.value() ), max_damage_default ); + json.member( "damage_increment", static_cast( damage_increment.min.dbl_val.value() ), + damage_increment_default ); + json.member( "min_accuracy", static_cast( min_accuracy.min.dbl_val.value() ), + min_accuracy_default ); + json.member( "accuracy_increment", static_cast( accuracy_increment.min.dbl_val.value() ), + accuracy_increment_default ); + json.member( "max_accuracy", static_cast( max_accuracy.min.dbl_val.value() ), + max_accuracy_default ); + json.member( "min_range", static_cast( min_range.min.dbl_val.value() ), min_range_default ); + json.member( "max_range", static_cast( max_range.min.dbl_val.value() ), min_range_default ); + json.member( "range_increment", static_cast( range_increment.min.dbl_val.value() ), + range_increment_default ); + json.member( "min_aoe", static_cast( min_aoe.min.dbl_val.value() ), min_aoe_default ); + json.member( "max_aoe", static_cast( max_aoe.min.dbl_val.value() ), max_aoe_default ); + json.member( "aoe_increment", static_cast( aoe_increment.min.dbl_val.value() ), + aoe_increment_default ); + json.member( "min_dot", static_cast( min_dot.min.dbl_val.value() ), min_dot_default ); + json.member( "max_dot", static_cast( max_dot.min.dbl_val.value() ), max_dot_default ); + json.member( "dot_increment", static_cast( dot_increment.min.dbl_val.value() ), + dot_increment_default ); + json.member( "min_duration", static_cast( min_duration.min.dbl_val.value() ), + min_duration_default ); + json.member( "max_duration", static_cast( max_duration.min.dbl_val.value() ), + max_duration_default ); + json.member( "duration_increment", static_cast( duration_increment.min.dbl_val.value() ), + duration_increment_default ); + json.member( "min_pierce", static_cast( min_pierce.min.dbl_val.value() ), min_pierce_default ); + json.member( "max_pierce", static_cast( max_pierce.min.dbl_val.value() ), max_pierce_default ); + json.member( "pierce_increment", static_cast( pierce_increment.min.dbl_val.value() ), + pierce_increment_default ); + json.member( "base_energy_cost", static_cast( base_energy_cost.min.dbl_val.value() ), + base_energy_cost_default ); + json.member( "final_energy_cost", static_cast( final_energy_cost.min.dbl_val.value() ), + static_cast( base_energy_cost.min.dbl_val.value() ) ); + json.member( "energy_increment", static_cast( energy_increment.min.dbl_val.value() ), + energy_increment_default ); 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( "damage_type", io::enum_to_string( dmg_type ), io::enum_to_string( dmg_type_default ) ); - json.member( "difficulty", difficulty, difficulty_default ); - json.member( "max_level", max_level, max_level_default ); - json.member( "base_casting_time", base_casting_time, base_casting_time_default ); - json.member( "final_casting_time", final_casting_time, base_casting_time ); - json.member( "casting_time_increment", casting_time_increment, casting_time_increment_default ); + json.member( "difficulty", static_cast( difficulty.min.dbl_val.value() ), difficulty_default ); + json.member( "max_level", static_cast( max_level.min.dbl_val.value() ), max_level_default ); + json.member( "base_casting_time", static_cast( base_casting_time.min.dbl_val.value() ), + base_casting_time_default ); + json.member( "final_casting_time", static_cast( final_casting_time.min.dbl_val.value() ), + static_cast( base_casting_time.min.dbl_val.value() ) ); + json.member( "casting_time_increment", + static_cast( casting_time_increment.min.dbl_val.value() ), casting_time_increment_default ); if( !learn_spells.empty() ) { json.member( "learn_spells" ); @@ -481,38 +522,6 @@ static bool spell_infinite_loop_check( std::set spell_effects, const s void spell_type::check_consistency() { for( const spell_type &sp_t : get_all() ) { - if( ( sp_t.min_aoe > sp_t.max_aoe && sp_t.aoe_increment > 0 ) || - ( sp_t.min_aoe < sp_t.max_aoe && sp_t.aoe_increment < 0 ) ) { - debugmsg( "ERROR: %s has higher min_aoe than max_aoe", sp_t.id.c_str() ); - } - if( ( sp_t.min_damage > sp_t.max_damage && sp_t.damage_increment > 0 ) || - ( sp_t.min_damage < sp_t.max_damage && sp_t.damage_increment < 0 ) ) { - debugmsg( "ERROR: %s has higher min_damage than max_damage", sp_t.id.c_str() ); - } - if( ( sp_t.min_range > sp_t.max_range && sp_t.range_increment > 0 ) || - ( sp_t.min_range < sp_t.max_range && sp_t.range_increment < 0 ) ) { - debugmsg( "ERROR: %s has higher min_range than max_range", sp_t.id.c_str() ); - } - if( ( sp_t.min_dot > sp_t.max_dot && sp_t.dot_increment > 0 ) || - ( sp_t.min_dot < sp_t.max_dot && sp_t.dot_increment < 0 ) ) { - debugmsg( "ERROR: %s has higher min_dot than max_dot", sp_t.id.c_str() ); - } - if( ( sp_t.min_duration > sp_t.max_duration && sp_t.duration_increment > 0 ) || - ( sp_t.min_duration < sp_t.max_duration && sp_t.duration_increment < 0 ) ) { - debugmsg( "ERROR: %s has higher min_duration than max_duration", sp_t.id.c_str() ); - } - if( ( sp_t.min_pierce > sp_t.max_pierce && sp_t.pierce_increment > 0 ) || - ( sp_t.min_pierce < sp_t.max_pierce && sp_t.pierce_increment < 0 ) ) { - debugmsg( "ERROR: %s has higher min_pierce than max_pierce", sp_t.id.c_str() ); - } - if( sp_t.casting_time_increment < 0.0f && sp_t.base_casting_time < sp_t.final_casting_time ) { - debugmsg( "ERROR: %s has negative increment and base_casting_time < final_casting_time", - sp_t.id.c_str() ); - } - if( sp_t.casting_time_increment > 0.0f && sp_t.base_casting_time > sp_t.final_casting_time ) { - debugmsg( "ERROR: %s has positive increment and base_casting_time > final_casting_time", - sp_t.id.c_str() ); - } if( sp_t.effect_name == "summon_vehicle" ) { if( !sp_t.effect_str.empty() && !vproto_id( sp_t.effect_str ).is_valid() ) { debugmsg( "ERROR %s specifies a vehicle to summon, but vehicle %s is not valid", sp_t.id.c_str(), @@ -523,19 +532,6 @@ void spell_type::check_consistency() if( spell_infinite_loop_check( spell_effect_list, sp_t.id ) ) { debugmsg( "ERROR: %s has infinite loop in extra_effects", sp_t.id.c_str() ); } - if( sp_t.field ) { - if( sp_t.field_chance <= 0 ) { - debugmsg( "ERROR: %s must have a positive field chance.", sp_t.id.c_str() ); - } - if( sp_t.field_intensity_increment > 0 && sp_t.max_field_intensity < sp_t.min_field_intensity ) { - debugmsg( "ERROR: max_field_intensity must be greater than min_field_intensity with positive increment: %s", - sp_t.id.c_str() ); - } else if( sp_t.field_intensity_increment < 0 && - sp_t.max_field_intensity > sp_t.min_field_intensity ) { - debugmsg( "ERROR: min_field_intensity must be greater than max_field_intensity with negative increment: %s", - sp_t.id.c_str() ); - } - } if( sp_t.spell_tags[spell_flag::WONDER] && sp_t.additional_spells.empty() ) { debugmsg( "ERROR: %s has WONDER flag but no spells to choose from!", sp_t.id.c_str() ); } @@ -584,16 +580,19 @@ skill_id spell::skill() const return type->skill; } -int spell::field_intensity() const +int spell::field_intensity( const Creature &caster ) const { - return std::min( type->max_field_intensity, - static_cast( type->min_field_intensity + std::round( get_level() * - type->field_intensity_increment ) ) ); + dialogue d( get_talker_for( caster ), nullptr ); + return std::min( static_cast( type->max_field_intensity.evaluate( d ) ), + static_cast( type->min_field_intensity.evaluate( d ) + std::round( get_level() * + type->field_intensity_increment.evaluate( d ) ) ) ); } -int spell::min_leveled_damage() const +int spell::min_leveled_damage( const Creature &caster ) const { - return type->min_damage + std::round( get_level() * type->damage_increment ); + dialogue d( get_talker_for( caster ), nullptr ); + return type->min_damage.evaluate( d ) + std::round( get_level() * type->damage_increment.evaluate( + d ) ); } float spell::dps( const Character &caster, const Creature & ) const @@ -603,74 +602,87 @@ float spell::dps( const Character &caster, const Creature & ) const } const float time_modifier = 100.0f / casting_time( caster ); const float failure_modifier = 1.0f - spell_fail( caster ); - const float raw_dps = damage() + damage_dot() * duration_turns() / 1_turns; + const float raw_dps = damage( caster ) + damage_dot( caster ) * duration_turns( caster ) / 1_turns; // TODO: calculate true dps with armor and resistances and any caster bonuses return raw_dps * time_modifier * failure_modifier; } -int spell::damage() const +int spell::damage( const Creature &caster ) const { - const int leveled_damage = min_leveled_damage(); + dialogue d( get_talker_for( caster ), nullptr ); + const int leveled_damage = min_leveled_damage( caster ); if( has_flag( spell_flag::RANDOM_DAMAGE ) ) { - return rng( std::min( leveled_damage, type->max_damage ), std::max( leveled_damage, - type->max_damage ) ); + return rng( std::min( leveled_damage, static_cast( type->max_damage.evaluate( d ) ) ), + std::max( leveled_damage, + static_cast( type->max_damage.evaluate( d ) ) ) ); } else { - if( type->min_damage >= 0 || type->max_damage >= type->min_damage ) { - return std::min( leveled_damage, type->max_damage ); + if( type->min_damage.evaluate( d ) >= 0 || + type->max_damage.evaluate( d ) >= type->min_damage.evaluate( d ) ) { + return std::min( leveled_damage, static_cast( type->max_damage.evaluate( d ) ) ); } else { // if it's negative, min and max work differently - return std::max( leveled_damage, type->max_damage ); + return std::max( leveled_damage, static_cast( type->max_damage.evaluate( d ) ) ); } } } -int spell::min_leveled_accuracy() const +int spell::min_leveled_accuracy( const Creature &caster ) const { - return type->min_accuracy + std::round( get_level() * type->accuracy_increment ); + dialogue d( get_talker_for( caster ), nullptr ); + return type->min_accuracy.evaluate( d ) + std::round( get_level() * + type->accuracy_increment.evaluate( d ) ); } -int spell::accuracy() const +int spell::accuracy( Creature &caster ) const { - const int leveled_accuracy = min_leveled_accuracy(); - if( type->min_accuracy >= 0 || type->max_accuracy >= type->min_accuracy ) { - return std::min( leveled_accuracy, type->max_accuracy ); + dialogue d( get_talker_for( caster ), nullptr ); + const int leveled_accuracy = min_leveled_accuracy( caster ); + if( type->min_accuracy.evaluate( d ) >= 0 || + type->max_accuracy.evaluate( d ) >= type->min_accuracy.evaluate( d ) ) { + return std::min( leveled_accuracy, static_cast( type->max_accuracy.evaluate( d ) ) ); } else { // if it's negative, min and max work differently - return std::max( leveled_accuracy, type->max_accuracy ); + return std::max( leveled_accuracy, static_cast( type->max_accuracy.evaluate( d ) ) ); } } -int spell::min_leveled_dot() const +int spell::min_leveled_dot( const Creature &caster ) const { - return type->min_dot + std::round( get_level() * type->dot_increment ); + dialogue d( get_talker_for( caster ), nullptr ); + return type->min_dot.evaluate( d ) + std::round( get_level() * type->dot_increment.evaluate( d ) ); } -int spell::damage_dot() const +int spell::damage_dot( const Creature &caster ) const { - const int leveled_dot = min_leveled_dot(); - if( type->min_dot >= 0 || type->max_dot >= type->min_dot ) { - return std::min( leveled_dot, type->max_dot ); + dialogue d( get_talker_for( caster ), nullptr ); + const int leveled_dot = min_leveled_dot( caster ); + if( type->min_dot.evaluate( d ) >= 0 || + type->max_dot.evaluate( d ) >= type->min_dot.evaluate( d ) ) { + return std::min( leveled_dot, static_cast( type->max_dot.evaluate( d ) ) ); } else { // if it's negative, min and max work differently - return std::max( leveled_dot, type->max_dot ); + return std::max( leveled_dot, static_cast( type->max_dot.evaluate( d ) ) ); } } -damage_over_time_data spell::damage_over_time( const std::vector &bps ) const +damage_over_time_data spell::damage_over_time( const std::vector &bps, + const Creature &caster ) const { damage_over_time_data temp; temp.bps = bps; - temp.duration = duration_turns(); - temp.amount = damage_dot(); + temp.duration = duration_turns( caster ); + temp.amount = damage_dot( caster ); temp.type = dmg_type(); return temp; } -std::string spell::damage_string() const +std::string spell::damage_string( const Character &caster ) const { std::string damage_string; + dialogue d( get_talker_for( caster ), nullptr ); if( has_flag( spell_flag::RANDOM_DAMAGE ) ) { - damage_string = string_format( "%d-%d", min_leveled_damage(), type->max_damage ); + damage_string = string_format( "%d-%d", min_leveled_damage( caster ), + type->max_damage.evaluate( d ) ); } else { - const int dmg = damage(); + const int dmg = damage( caster ); if( dmg >= 0 ) { damage_string = string_format( "%d", dmg ); } else { @@ -687,7 +699,7 @@ std::optional spell::select_target( Creature *source ) { tripoint target = source->pos(); bool target_is_valid = false; - if( range() > 0 && !is_valid_target( spell_target::none ) && + if( range( *source ) > 0 && !is_valid_target( spell_target::none ) && !has_flag( spell_flag::RANDOM_TARGET ) ) { if( source->is_avatar() ) { do { @@ -736,22 +748,25 @@ std::optional spell::select_target( Creature *source ) return target; } -int spell::min_leveled_aoe() const +int spell::min_leveled_aoe( const Creature &caster ) const { - return type->min_aoe + std::round( get_level() * type->aoe_increment ); + dialogue d( get_talker_for( caster ), nullptr ); + return type->min_aoe.evaluate( d ) + std::round( get_level() * type->aoe_increment.evaluate( d ) ); } -int spell::aoe() const +int spell::aoe( const Creature &caster ) const { - const int leveled_aoe = min_leveled_aoe(); + dialogue d( get_talker_for( caster ), nullptr ); + const int leveled_aoe = min_leveled_aoe( caster ); if( has_flag( spell_flag::RANDOM_AOE ) ) { - return rng( std::min( leveled_aoe, type->max_aoe ), std::max( leveled_aoe, type->max_aoe ) ); + return rng( std::min( leveled_aoe, static_cast( type->max_aoe.evaluate( d ) ) ), + std::max( leveled_aoe, static_cast( type->max_aoe.evaluate( d ) ) ) ); } else { - if( type->max_aoe >= type->min_aoe ) { - return std::min( leveled_aoe, type->max_aoe ); + if( type->max_aoe.evaluate( d ) >= type->min_aoe.evaluate( d ) ) { + return std::min( leveled_aoe, static_cast( type->max_aoe.evaluate( d ) ) ); } else { - return std::max( leveled_aoe, type->max_aoe ); + return std::max( leveled_aoe, static_cast( type->max_aoe.evaluate( d ) ) ); } } } @@ -762,36 +777,41 @@ std::set spell::effect_area( const spell_effect::override_parameters & return type->spell_area_function( params, source, target ); } -std::set spell::effect_area( const tripoint &source, const tripoint &target ) const +std::set spell::effect_area( const tripoint &source, const tripoint &target, + const Creature &caster ) const { - return effect_area( spell_effect::override_parameters( *this ), source, target ); + return effect_area( spell_effect::override_parameters( *this, caster ), source, target ); } -bool spell::in_aoe( const tripoint &source, const tripoint &target ) const +bool spell::in_aoe( const tripoint &source, const tripoint &target, const Creature &caster ) const { + dialogue d( get_talker_for( caster ), nullptr ); if( has_flag( spell_flag::RANDOM_AOE ) ) { - return rl_dist( source, target ) <= type->max_aoe; + return rl_dist( source, target ) <= type->max_aoe.evaluate( d ); } else { - return rl_dist( source, target ) <= aoe(); + return rl_dist( source, target ) <= aoe( caster ); } } -std::string spell::aoe_string() const +std::string spell::aoe_string( const Creature &caster ) const { + dialogue d( get_talker_for( caster ), nullptr ); if( has_flag( spell_flag::RANDOM_AOE ) ) { - return string_format( "%d-%d", min_leveled_aoe(), type->max_aoe ); + return string_format( "%d-%d", min_leveled_aoe( caster ), type->max_aoe.evaluate( d ) ); } else { - return string_format( "%d", aoe() ); + return string_format( "%d", aoe( caster ) ); } } -int spell::range() const +int spell::range( const Creature &caster ) const { - const int leveled_range = type->min_range + std::round( get_level() * type->range_increment ); - if( type->max_range >= type->min_range ) { - return std::min( leveled_range, type->max_range ); + dialogue d( get_talker_for( caster ), nullptr ); + const int leveled_range = type->min_range.evaluate( d ) + std::round( get_level() * + type->range_increment.evaluate( d ) ); + if( type->max_range.evaluate( d ) >= type->min_range.evaluate( d ) ) { + return std::min( leveled_range, static_cast( type->max_range.evaluate( d ) ) ); } else { - return std::max( leveled_range, type->max_range ); + return std::max( leveled_range, static_cast( type->max_range.evaluate( d ) ) ); } } @@ -814,7 +834,7 @@ std::vector spell::targetable_locations( const Character &source ) con }; std::vector selectable_targets; - for( const tripoint &query : here.points_in_radius( char_pos, range() ) ) { + for( const tripoint &query : here.points_in_radius( char_pos, range( source ) ) ) { if( !ignore_walls && has_obstruction( query ) ) { // it's blocked somewhere! continue; @@ -834,43 +854,49 @@ std::vector spell::targetable_locations( const Character &source ) con return selectable_targets; } -int spell::min_leveled_duration() const +int spell::min_leveled_duration( const Creature &caster ) const { - return type->min_duration + std::round( get_level() * type->duration_increment ); + dialogue d( get_talker_for( caster ), nullptr ); + return type->min_duration.evaluate( d ) + std::round( get_level() * + type->duration_increment.evaluate( d ) ); } -int spell::duration() const +int spell::duration( const Creature &caster ) const { - const int leveled_duration = min_leveled_duration(); + dialogue d( get_talker_for( caster ), nullptr ); + const int leveled_duration = min_leveled_duration( caster ); if( has_flag( spell_flag::RANDOM_DURATION ) ) { - return rng( std::min( leveled_duration, type->max_duration ), std::max( leveled_duration, - type->max_duration ) ); + return rng( std::min( leveled_duration, static_cast( type->max_duration.evaluate( d ) ) ), + std::max( leveled_duration, + static_cast( type->max_duration.evaluate( d ) ) ) ); } else { - if( type->max_duration >= type->min_duration ) { - return std::min( leveled_duration, type->max_duration ); + if( type->max_duration.evaluate( d ) >= type->min_duration.evaluate( d ) ) { + return std::min( leveled_duration, static_cast( type->max_duration.evaluate( d ) ) ); } else { - return std::max( leveled_duration, type->max_duration ); + return std::max( leveled_duration, static_cast( type->max_duration.evaluate( d ) ) ); } } } -std::string spell::duration_string() const +std::string spell::duration_string( const Creature &caster ) const { + dialogue d( get_talker_for( caster ), nullptr ); if( has_flag( spell_flag::RANDOM_DURATION ) ) { - return string_format( "%s - %s", moves_to_string( min_leveled_duration() ), - moves_to_string( type->max_duration ) ); - } else if( ( has_flag( spell_flag::PERMANENT ) && ( is_max_level() || effect() == "summon" ) ) || + return string_format( "%s - %s", moves_to_string( min_leveled_duration( caster ) ), + moves_to_string( type->max_duration.evaluate( d ) ) ); + } else if( ( has_flag( spell_flag::PERMANENT ) && ( is_max_level( caster ) || + effect() == "summon" ) ) || has_flag( spell_flag::PERMANENT_ALL_LEVELS ) ) { return _( "Permanent" ); } else { - return moves_to_string( duration() ); + return moves_to_string( duration( caster ) ); } } -time_duration spell::duration_turns() const +time_duration spell::duration_turns( const Creature &caster ) const { - return time_duration::from_moves( duration() ); + return time_duration::from_moves( duration( caster ) ); } void spell::gain_level( const Character &guy ) @@ -883,7 +909,7 @@ void spell::gain_levels( const Character &guy, int gains ) if( gains < 1 ) { return; } - for( int gained = 0; gained < gains && !is_max_level(); gained++ ) { + for( int gained = 0; gained < gains && !is_max_level( guy ); gained++ ) { gain_level( guy ); } } @@ -894,9 +920,10 @@ void spell::set_level( const Character &guy, int nlevel ) gain_levels( guy, nlevel ); } -bool spell::is_max_level() const +bool spell::is_max_level( const Creature &caster ) const { - return get_level() >= type->max_level; + dialogue d( get_talker_for( caster ), nullptr ); + return get_level() >= type->max_level.evaluate( d ); } bool spell::can_learn( const Character &guy ) const @@ -910,14 +937,17 @@ bool spell::can_learn( const Character &guy ) const int spell::energy_cost( const Character &guy ) const { int cost; - if( type->base_energy_cost < type->final_energy_cost ) { - cost = std::min( type->final_energy_cost, - static_cast( std::round( type->base_energy_cost + type->energy_increment * get_level() ) ) ); - } else if( type->base_energy_cost > type->final_energy_cost ) { - cost = std::max( type->final_energy_cost, - static_cast( std::round( type->base_energy_cost + type->energy_increment * get_level() ) ) ); + dialogue d( get_talker_for( guy ), nullptr ); + if( type->base_energy_cost.evaluate( d ) < type->final_energy_cost.evaluate( d ) ) { + cost = std::min( static_cast( type->final_energy_cost.evaluate( d ) ), + static_cast( std::round( type->base_energy_cost.evaluate( d ) + + type->energy_increment.evaluate( d ) * get_level() ) ) ); + } else if( type->base_energy_cost.evaluate( d ) > type->final_energy_cost.evaluate( d ) ) { + cost = std::max( static_cast( type->final_energy_cost.evaluate( d ) ), + static_cast( std::round( type->base_energy_cost.evaluate( d ) + + type->energy_increment.evaluate( d ) * get_level() ) ) ); } else { - cost = type->base_energy_cost; + cost = type->base_energy_cost.evaluate( d ); } if( !has_flag( spell_flag::NO_HANDS ) ) { // the first 10 points of combined encumbrance is ignored, but quickly adds up @@ -1000,25 +1030,29 @@ bool spell::check_if_component_in_hand( Character &guy ) const return false; } -int spell::get_difficulty() const +int spell::get_difficulty( const Creature &caster ) const { - return type->difficulty; + dialogue d( get_talker_for( caster ), nullptr ); + return type->difficulty.evaluate( d ); } int spell::casting_time( const Character &guy, bool ignore_encumb ) const { // casting time in moves int casting_time = 0; - if( type->base_casting_time < type->final_casting_time ) { - casting_time = std::min( type->final_casting_time, - static_cast( std::round( type->base_casting_time + type->casting_time_increment * + dialogue d( get_talker_for( guy ), nullptr ); + if( type->base_casting_time.evaluate( d ) < type->final_casting_time.evaluate( d ) ) { + casting_time = std::min( static_cast( type->final_casting_time.evaluate( d ) ), + static_cast( std::round( type->base_casting_time.evaluate( d ) + + type->casting_time_increment.evaluate( d ) * get_level() ) ) ); - } else if( type->base_casting_time > type->final_casting_time ) { - casting_time = std::max( type->final_casting_time, - static_cast( std::round( type->base_casting_time + type->casting_time_increment * + } else if( type->base_casting_time.evaluate( d ) > type->final_casting_time.evaluate( d ) ) { + casting_time = std::max( static_cast( type->final_casting_time.evaluate( d ) ), + static_cast( std::round( type->base_casting_time.evaluate( d ) + + type->casting_time_increment.evaluate( d ) * get_level() ) ) ); } else { - casting_time = type->base_casting_time; + casting_time = type->base_casting_time.evaluate( d ); } casting_time *= guy.mutation_value( "casting_time_multiplier" ); @@ -1073,7 +1107,7 @@ float spell::spell_fail( const Character &guy ) const // effective skill of 0 or less is 100% failure // effective skill of 8 (8 int, 0 spellcraft, 0 spell level, spell difficulty 0) is ~50% failure // effective skill of 30 is 0% failure - const float effective_skill = 2 * ( get_level() - get_difficulty() ) + guy.get_int() + + const float effective_skill = 2 * ( get_level() - get_difficulty( guy ) ) + guy.get_int() + guy.get_skill_level( skill() ); // add an if statement in here because sufficiently large numbers will definitely overflow because of exponents if( effective_skill > 30.0f ) { @@ -1222,42 +1256,44 @@ bool spell::bp_is_affected( const bodypart_str_id &bp ) const return type->affected_bps.test( bp ); } -void spell::create_field( const tripoint &at ) const +void spell::create_field( const tripoint &at, Creature &caster ) const { if( !type->field ) { return; } - const int intensity = field_intensity() + rng( -type->field_intensity_variance * field_intensity(), - type->field_intensity_variance * field_intensity() ); + dialogue d( get_talker_for( caster ), nullptr ); + const int intensity = field_intensity( caster ) + rng( -type->field_intensity_variance.evaluate( + d ) * field_intensity( caster ), + type->field_intensity_variance.evaluate( d ) * field_intensity( caster ) ); if( intensity <= 0 ) { return; } - if( one_in( type->field_chance ) ) { + if( one_in( type->field_chance.evaluate( d ) ) ) { map &here = get_map(); field_entry *field = here.get_field( at, *type->field ); if( field ) { field->set_field_intensity( field->get_field_intensity() + intensity ); } else { - here.add_field( at, *type->field, intensity, -duration_turns() ); + here.add_field( at, *type->field, intensity, -duration_turns( caster ) ); } } } -int spell::sound_volume() const +int spell::sound_volume( const Creature &caster ) const { int loudness = 0; if( !has_flag( spell_flag::SILENT ) ) { - loudness = std::abs( damage() ) / 3; + loudness = std::abs( damage( caster ) ) / 3; if( has_flag( spell_flag::LOUD ) ) { - loudness += 1 + damage() / 3; + loudness += 1 + damage( caster ) / 3; } } return loudness; } -void spell::make_sound( const tripoint &target ) const +void spell::make_sound( const tripoint &target, Creature &caster ) const { - const int loudness = sound_volume(); + const int loudness = sound_volume( caster ); if( loudness > 0 ) { make_sound( target, loudness ); } @@ -1281,7 +1317,7 @@ magic_energy_type spell::energy_source() const bool spell::is_target_in_range( const Creature &caster, const tripoint &p ) const { - return rl_dist( caster.pos(), p ) <= range(); + return rl_dist( caster.pos(), p ) <= range( caster ); } bool spell::is_valid_target( spell_target t ) const @@ -1388,9 +1424,10 @@ int spell::get_level() const return std::max( static_cast( std::floor( std::log( experience + a ) / b + c ) ), 0 ); } -int spell::get_max_level() const +int spell::get_max_level( const Creature &caster ) const { - return type->max_level; + dialogue d( get_talker_for( caster ), nullptr ); + return type->max_level.evaluate( d ); } // helper function to calculate xp needed to be at a certain level @@ -1424,7 +1461,7 @@ std::string spell::exp_progress() const float spell::exp_modifier( const Character &guy ) const { const float int_modifier = ( guy.get_int() - 8.0f ) / 8.0f; - const float difficulty_modifier = get_difficulty() / 20.0f; + const float difficulty_modifier = get_difficulty( guy ) / 20.0f; const float spellcraft_modifier = guy.get_skill_level( skill() ) / 10.0f; return ( int_modifier + difficulty_modifier + spellcraft_modifier ) / 5.0f + 1.0f; @@ -1502,26 +1539,26 @@ damage_type spell::dmg_type() const return type->dmg_type; } -damage_instance spell::get_damage_instance() const +damage_instance spell::get_damage_instance( Creature &caster ) const { damage_instance dmg; - dmg.add_damage( dmg_type(), damage() ); + dmg.add_damage( dmg_type(), damage( caster ) ); return dmg; } -dealt_damage_instance spell::get_dealt_damage_instance() const +dealt_damage_instance spell::get_dealt_damage_instance( Creature &caster ) const { dealt_damage_instance dmg; - dmg.set_damage( dmg_type(), damage() ); + dmg.set_damage( dmg_type(), damage( caster ) ); return dmg; } dealt_projectile_attack spell::get_projectile_attack( const tripoint &target, - Creature &hit_critter ) const + Creature &hit_critter, Creature &caster ) const { projectile bolt; bolt.speed = 10000; - bolt.impact = get_damage_instance(); + bolt.impact = get_damage_instance( caster ); bolt.proj_effects.emplace( "magic" ); dealt_projectile_attack atk; @@ -1543,17 +1580,17 @@ vproto_id spell::summon_vehicle_id() const return vproto_id( type->effect_str ); } -int spell::heal( const tripoint &target ) const +int spell::heal( const tripoint &target, Creature &caster ) const { creature_tracker &creatures = get_creature_tracker(); monster *const mon = creatures.creature_at( target ); if( mon ) { - return mon->heal( -damage() ); + return mon->heal( -damage( caster ) ); } Character *const p = creatures.creature_at( target ); if( p ) { - p->healall( -damage() ); - return -damage(); + p->healall( -damage( caster ) ); + return -damage( caster ); } return -1; } @@ -1567,13 +1604,13 @@ void spell::cast_all_effects( Creature &source, const tripoint &target ) const { if( has_flag( spell_flag::WONDER ) ) { const auto iter = type->additional_spells.begin(); - for( int num_spells = std::abs( damage() ); num_spells > 0; num_spells-- ) { + for( int num_spells = std::abs( damage( source ) ); num_spells > 0; num_spells-- ) { if( type->additional_spells.empty() ) { debugmsg( "ERROR: %s has WONDER flag but no spells to choose from!", type->id.c_str() ); return; } const int rand_spell = rng( 0, type->additional_spells.size() - 1 ); - spell sp = ( iter + rand_spell )->get_spell( get_level() ); + spell sp = ( iter + rand_spell )->get_spell( source, get_level() ); const bool _self = ( iter + rand_spell )->self; // This spell flag makes it so the message of the spell that's cast using this spell will be sent. @@ -1607,7 +1644,7 @@ void spell::cast_all_effects( Creature &source, const tripoint &target ) const void spell::cast_extra_spell_effects( Creature &source, const tripoint &target ) const { for( const fake_spell &extra_spell : type->additional_spells ) { - spell sp = extra_spell.get_spell( get_level() ); + spell sp = extra_spell.get_spell( source, get_level() ); if( sp.has_flag( spell_flag::RANDOM_TARGET ) ) { if( const std::optional new_target = sp.random_valid_target( source, extra_spell.self ? source.pos() : target ) ) { @@ -1628,9 +1665,9 @@ std::optional spell::random_valid_target( const Creature &caster, { const bool ignore_ground = has_flag( spell_flag::RANDOM_CRITTER ); std::set valid_area; - spell_effect::override_parameters blast_params( *this ); + spell_effect::override_parameters blast_params( *this, caster ); // we want to pick a random target within range, not aoe - blast_params.aoe_radius = range(); + blast_params.aoe_radius = range( caster ); creature_tracker &creatures = get_creature_tracker(); for( const tripoint &target : spell_effect::spell_effect_blast( blast_params, caster_pos, caster_pos ) ) { @@ -1935,10 +1972,11 @@ int known_magic::time_to_learn_spell( const Character &guy, const std::string &s int known_magic::time_to_learn_spell( const Character &guy, const spell_id &sp ) const { + dialogue d( get_talker_for( guy ), nullptr ); const int base_time = to_moves( 30_minutes ); const double int_modifier = ( guy.get_int() - 8.0 ) / 8.0; const double skill_modifier = guy.get_skill_level( sp->skill ) / 10.0; - return base_time * ( 1.0 + sp->difficulty / ( 1.0 + int_modifier + skill_modifier ) ); + return base_time * ( 1.0 + sp->difficulty.evaluate( d ) / ( 1.0 + int_modifier + skill_modifier ) ); } int known_magic::get_spellname_max_width() @@ -2078,7 +2116,7 @@ bool spell_desc::energy_cost_encumbered( const spell &sp, const Character &guy ) // this prints various things about the spell out in a list // including flags and things like "goes through walls" -std::string spell_desc::enumerate_spell_data( const spell &sp ) +std::string spell_desc::enumerate_spell_data( const spell &sp, const Character &guy ) { std::vector spell_data; if( sp.has_flag( spell_flag::CONCENTRATE ) ) { @@ -2098,7 +2136,7 @@ std::string spell_desc::enumerate_spell_data( const spell &sp ) if( !sp.has_flag( spell_flag::NO_LEGS ) ) { spell_data.emplace_back( _( "requires mobility" ) ); } - if( sp.effect() == "attack" && sp.range() > 1 && sp.has_flag( spell_flag::NO_PROJECTILE ) ) { + if( sp.effect() == "attack" && sp.range( guy ) > 1 && sp.has_flag( spell_flag::NO_PROJECTILE ) ) { spell_data.emplace_back( _( "can be cast through walls" ) ); } return enumerate_as_string( spell_data ); @@ -2114,7 +2152,7 @@ void spellcasting_callback::spell_info_text( const spell &sp, int width ) info_txt.emplace_back( colorize( line, c_light_gray ) ); } info_txt.emplace_back( std::string() ); - for( const std::string &line : foldstring( spell_desc::enumerate_spell_data( sp ), width ) ) { + for( const std::string &line : foldstring( spell_desc::enumerate_spell_data( sp, pc ), width ) ) { info_txt.emplace_back( colorize( line, c_light_gray ) ); } info_txt.emplace_back( std::string() ); @@ -2129,11 +2167,11 @@ void spellcasting_callback::spell_info_text( const spell &sp, int width ) info_txt.emplace_back( colorize( columnize( string_format( "%s: %d %s", _( "Spell Level" ), sp.get_level(), - sp.is_max_level() ? _( "(MAX)" ) : "" ), - string_format( "%s: %d", _( "Max Level" ), sp.get_max_level() ) ), c_light_gray ) ); + sp.is_max_level( pc ) ? _( "(MAX)" ) : "" ), + string_format( "%s: %d", _( "Max Level" ), sp.get_max_level( pc ) ) ), c_light_gray ) ); info_txt.emplace_back( colorize( columnize( sp.colorized_fail_percent( pc ), - string_format( "%s: %d", _( "Difficulty" ), sp.get_difficulty() ) ), c_light_gray ) ); + string_format( "%s: %d", _( "Difficulty" ), sp.get_difficulty( pc ) ) ), c_light_gray ) ); info_txt.emplace_back( colorize( columnize( string_format( "%s: %s", _( "Current Exp" ), colorize( std::to_string( sp.xp() ), c_light_green ) ), @@ -2182,25 +2220,25 @@ void spellcasting_callback::spell_info_text( const spell &sp, int width ) info_txt.emplace_back( std::string() ); } - const int damage = sp.damage(); + const int damage = sp.damage( pc ); std::string damage_string; std::string aoe_string; // if it's any type of attack spell, the stats are normal. if( sp.effect() == "attack" ) { if( damage > 0 ) { std::string dot_string; - if( sp.damage_dot() != 0 ) { + if( sp.damage_dot( pc ) != 0 ) { //~ amount of damage per second, abbreviated - dot_string = string_format( _( ", %d/sec" ), sp.damage_dot() ); + dot_string = string_format( _( ", %d/sec" ), sp.damage_dot( pc ) ); } - damage_string = string_format( "%s: %s %s%s", _( "Damage" ), sp.damage_string(), + damage_string = string_format( "%s: %s %s%s", _( "Damage" ), sp.damage_string( pc ), sp.damage_type_string(), dot_string ); damage_string = colorize( damage_string, sp.damage_type_color() ); } else if( damage < 0 ) { - damage_string = string_format( "%s: %s", _( "Healing" ), colorize( sp.damage_string(), + damage_string = string_format( "%s: %s", _( "Healing" ), colorize( sp.damage_string( pc ), c_light_green ) ); } - if( sp.aoe() > 0 ) { + if( sp.aoe( pc ) > 0 ) { std::string aoe_string_temp = _( "Spell Radius" ); std::string degree_string; if( sp.shape() == spell_shape::cone ) { @@ -2209,15 +2247,15 @@ void spellcasting_callback::spell_info_text( const spell &sp, int width ) } else if( sp.shape() == spell_shape::line ) { aoe_string_temp = _( "Line Width" ); } - aoe_string = string_format( "%s: %d %s", aoe_string_temp, sp.aoe(), degree_string ); + aoe_string = string_format( "%s: %d %s", aoe_string_temp, sp.aoe( pc ), degree_string ); } } else if( sp.effect() == "short_range_teleport" ) { - if( sp.aoe() > 0 ) { - aoe_string = string_format( "%s: %d", _( "Variance" ), sp.aoe() ); + if( sp.aoe( pc ) > 0 ) { + aoe_string = string_format( "%s: %d", _( "Variance" ), sp.aoe( pc ) ); } } else if( sp.effect() == "spawn_item" ) { - damage_string = string_format( "%s %d %s", _( "Spawn" ), sp.damage(), - item::nname( itype_id( sp.effect_data() ), sp.damage() ) ); + damage_string = string_format( "%s %d %s", _( "Spawn" ), sp.damage( pc ), + item::nname( itype_id( sp.effect_data() ), sp.damage( pc ) ) ); } else if( sp.effect() == "summon" ) { std::string monster_name = "FIXME"; if( sp.has_flag( spell_flag::SPAWN_GROUP ) ) { @@ -2230,8 +2268,8 @@ void spellcasting_callback::spell_info_text( const spell &sp, int width ) } else { monster_name = monster( mtype_id( sp.effect_data() ) ).get_name( ); } - damage_string = string_format( "%s %d %s", _( "Summon" ), sp.damage(), monster_name ); - aoe_string = string_format( "%s: %d", _( "Spell Radius" ), sp.aoe() ); + damage_string = string_format( "%s %d %s", _( "Summon" ), sp.damage( pc ), monster_name ); + aoe_string = string_format( "%s: %d", _( "Spell Radius" ), sp.aoe( pc ) ); } else if( sp.effect() == "targeted_polymorph" ) { std::string monster_name = sp.effect_data(); if( sp.has_flag( spell_flag::POLYMORPH_GROUP ) ) { @@ -2246,21 +2284,21 @@ void spellcasting_callback::spell_info_text( const spell &sp, int width ) } else { monster_name = mtype_id( sp.effect_data() )->nname(); } - damage_string = string_format( _( "Targets under: %dhp become a %s" ), sp.damage(), + damage_string = string_format( _( "Targets under: %dhp become a %s" ), sp.damage( pc ), monster_name ); } else if( sp.effect() == "ter_transform" ) { - aoe_string = string_format( "%s: %s", _( "Spell Radius" ), sp.aoe_string() ); + aoe_string = string_format( "%s: %s", _( "Spell Radius" ), sp.aoe_string( pc ) ); } else if( sp.effect() == "banishment" ) { - damage_string = string_format( "%s: %s %s", _( "Damage" ), sp.damage_string(), + damage_string = string_format( "%s: %s %s", _( "Damage" ), sp.damage_string( pc ), sp.damage_type_string() ); - if( sp.aoe() > 0 ) { - aoe_string = string_format( _( "Spell Radius: %d" ), sp.aoe() ); + if( sp.aoe( pc ) > 0 ) { + aoe_string = string_format( _( "Spell Radius: %d" ), sp.aoe( pc ) ); } } // Range / AOE in two columns info_txt.emplace_back( colorize( string_format( "%s: %s", _( "Range" ), - sp.range() <= 0 ? _( "self" ) : std::to_string( sp.range() ) ), c_light_gray ) ); + sp.range( pc ) <= 0 ? _( "self" ) : std::to_string( sp.range( pc ) ) ), c_light_gray ) ); info_txt.emplace_back( colorize( aoe_string, c_light_gray ) ); // One line for damage / healing / spawn / summon effect @@ -2269,10 +2307,10 @@ void spellcasting_callback::spell_info_text( const spell &sp, int width ) // todo: damage over time here, when it gets implemented // Show duration for spells that endure - if( sp.duration() > 0 || sp.has_flag( spell_flag::PERMANENT ) || + if( sp.duration( pc ) > 0 || sp.has_flag( spell_flag::PERMANENT ) || sp.has_flag( spell_flag::PERMANENT_ALL_LEVELS ) ) { info_txt.emplace_back( - colorize( string_format( "%s: %s", _( "Duration" ), sp.duration_string() ), c_light_gray ) ); + colorize( string_format( "%s: %s", _( "Duration" ), sp.duration_string( pc ) ), c_light_gray ) ); } if( sp.has_components() ) { @@ -2452,6 +2490,8 @@ static void draw_spellbook_info( const spell_type &sp, uilist *menu ) nc_color gray = c_light_gray; nc_color yellow = c_yellow; const spell fake_spell( sp.id ); + Character &pc = get_player_character(); + dialogue d( get_talker_for( pc ), nullptr ); const std::string spell_name = colorize( sp.name, c_light_green ); const std::string spell_class = sp.spell_class == trait_NONE ? _( "Classless" ) : @@ -2464,10 +2504,10 @@ static void draw_spellbook_info( const spell_type &sp, uilist *menu ) line++; mvwprintz( w, point( start_x, line ), c_light_gray, string_format( "%s: %d", _( "Difficulty" ), - sp.difficulty ) ); + sp.difficulty.evaluate( d ) ) ); mvwprintz( w, point( start_x + width / 2, line++ ), c_light_gray, string_format( "%s: %d", _( "Max Level" ), - sp.max_level ) ); + sp.max_level.evaluate( d ) ) ); const std::string fx = sp.effect_name; std::string damage_string; @@ -2476,7 +2516,7 @@ static void draw_spellbook_info( const spell_type &sp, uilist *menu ) if( fx == "attack" ) { damage_string = _( "Damage" ); aoe_string = _( "AoE" ); - has_damage_type = sp.min_damage > 0 && sp.max_damage > 0; + has_damage_type = sp.min_damage.evaluate( d ) > 0 && sp.max_damage.evaluate( d ) > 0; } else if( fx == "spawn_item" || fx == "summon_monster" ) { damage_string = _( "Spawned" ); } else if( fx == "targeted_polymorph" ) { @@ -2508,27 +2548,34 @@ static void draw_spellbook_info( const spell_type &sp, uilist *menu ) left_justify( _( "max lvl" ), 7 ) ) ); std::vector> rows; - if( sp.max_damage != 0 && sp.min_damage != 0 && !damage_string.empty() ) { - rows.emplace_back( damage_string, sp.min_damage, sp.damage_increment, sp.max_damage ); + if( sp.max_damage.evaluate( d ) != 0 && sp.min_damage.evaluate( d ) != 0 && + !damage_string.empty() ) { + rows.emplace_back( damage_string, sp.min_damage.evaluate( d ), sp.damage_increment.evaluate( d ), + sp.max_damage.evaluate( d ) ); } - if( sp.max_range != 0 && sp.min_range != 0 ) { - rows.emplace_back( _( "Range" ), sp.min_range, sp.range_increment, sp.max_range ); + if( sp.max_range.evaluate( d ) != 0 && sp.min_range.evaluate( d ) != 0 ) { + rows.emplace_back( _( "Range" ), sp.min_range.evaluate( d ), sp.range_increment.evaluate( d ), + sp.max_range.evaluate( d ) ); } - if( sp.min_aoe != 0 && sp.max_aoe != 0 && !aoe_string.empty() ) { - rows.emplace_back( aoe_string, sp.min_aoe, sp.aoe_increment, sp.max_aoe ); + if( sp.min_aoe.evaluate( d ) != 0 && sp.max_aoe.evaluate( d ) != 0 && !aoe_string.empty() ) { + rows.emplace_back( aoe_string, sp.min_aoe.evaluate( d ), sp.aoe_increment.evaluate( d ), + sp.max_aoe.evaluate( d ) ); } - if( sp.min_duration != 0 && sp.max_duration != 0 ) { - rows.emplace_back( _( "Duration" ), sp.min_duration, static_cast( sp.duration_increment ), - sp.max_duration ); + if( sp.min_duration.evaluate( d ) != 0 && sp.max_duration.evaluate( d ) != 0 ) { + rows.emplace_back( _( "Duration" ), sp.min_duration.evaluate( d ), + static_cast( sp.duration_increment.evaluate( d ) ), + sp.max_duration.evaluate( d ) ); } - rows.emplace_back( _( "Cast Cost" ), sp.base_energy_cost, sp.energy_increment, - sp.final_energy_cost ); - rows.emplace_back( _( "Cast Time" ), sp.base_casting_time, sp.casting_time_increment, - sp.final_casting_time ); + rows.emplace_back( _( "Cast Cost" ), sp.base_energy_cost.evaluate( d ), + sp.energy_increment.evaluate( d ), + sp.final_energy_cost.evaluate( d ) ); + rows.emplace_back( _( "Cast Time" ), sp.base_casting_time.evaluate( d ), + sp.casting_time_increment.evaluate( d ), + sp.final_casting_time.evaluate( d ) ); for( std::tuple &row : rows ) { mvwprintz( w, point( start_x, line ), c_light_gray, std::get<0>( row ) ); @@ -2597,11 +2644,11 @@ void fake_spell::deserialize( const JsonObject &data ) load( data ); } -spell fake_spell::get_spell( int min_level_override ) const +spell fake_spell::get_spell( const Creature &caster, int min_level_override ) const { spell sp( id ); // the max level this spell will be. can be optionally limited - int spell_max_level = sp.get_max_level(); + int spell_max_level = sp.get_max_level( caster ); int spell_limiter = max_level ? *max_level : spell_max_level; // level is the minimum level the fake_spell will output min_level_override = std::max( min_level_override, level ); diff --git a/src/magic.h b/src/magic.h index 164143f82b2de..ef799e847c7ed 100644 --- a/src/magic.h +++ b/src/magic.h @@ -14,6 +14,7 @@ #include "bodypart.h" #include "damage.h" +#include "dialogue_helpers.h" #include "enum_bitset.h" #include "event_subscriber.h" #include "point.h" @@ -173,7 +174,7 @@ struct fake_spell { } // gets the spell with an additional override for minimum level (default 0) - spell get_spell( int min_level_override = 0 ) const; + spell get_spell( const Creature &caster, int min_level_override = 0 ) const; bool is_valid() const; void load( const JsonObject &jo ); @@ -231,98 +232,98 @@ class spell_type // if the spell has a field name defined, this is where it is std::optional field = std::nullopt; // the chance one_in( field_chance ) that the field spawns at a tripoint in the area of the spell - int field_chance = 0; + dbl_or_var field_chance; // field intensity at spell level 0 - int min_field_intensity = 0; + dbl_or_var min_field_intensity; // increment of field intensity per level - float field_intensity_increment = 0.0f; + dbl_or_var field_intensity_increment; // maximum field intensity allowed - int max_field_intensity = 0; + dbl_or_var max_field_intensity; // field intensity added to the map is +- ( 1 + field_intensity_variance ) * field_intensity - float field_intensity_variance = 0.0f; + dbl_or_var field_intensity_variance; // accuracy is a bonus against dodge, block, and spellcraft // which allows the target to mitigate up to 33% damage for each type of resistance // this could theoretically add up to 100% - int min_accuracy = 20; - float accuracy_increment = 0.0f; - int max_accuracy = 20; + dbl_or_var min_accuracy; + dbl_or_var accuracy_increment; + dbl_or_var max_accuracy; // minimum damage this spell can cause - int min_damage = 0; + dbl_or_var min_damage; // amount of damage increase per spell level - float damage_increment = 0.0f; + dbl_or_var damage_increment; // maximum damage this spell can cause - int max_damage = 0; + dbl_or_var max_damage; // minimum range of a spell - int min_range = 0; + dbl_or_var min_range; // amount of range increase per spell level - float range_increment = 0.0f; + dbl_or_var range_increment; // max range this spell can achieve - int max_range = 0; + dbl_or_var max_range; // minimum area of effect of a spell (radius) // 0 means the spell only affects the target - int min_aoe = 0; + dbl_or_var min_aoe; // amount of area of effect increase per spell level (radius) - float aoe_increment = 0.0f; + dbl_or_var aoe_increment; // max area of effect of a spell (radius) - int max_aoe = 0; + dbl_or_var max_aoe; // damage over time deals damage per turn // minimum damage over time - int min_dot = 0; + dbl_or_var min_dot; // increment per spell level - float dot_increment = 0.0f; + dbl_or_var dot_increment; // max damage over time - int max_dot = 0; + dbl_or_var max_dot; // amount of time effect lasts // minimum time for effect in moves - int min_duration = 0; + dbl_or_var min_duration; // increment per spell level in moves // DoT is per turn, but increments can be smaller - int duration_increment = 0; + dbl_or_var duration_increment; // max time for effect in moves - int max_duration = 0; + dbl_or_var max_duration; // amount of damage that is piercing damage // not added to damage stat // minimum pierce damage - int min_pierce = 0; + dbl_or_var min_pierce; // increment of pierce damage per spell level - float pierce_increment = 0.0f; + dbl_or_var pierce_increment; // max pierce damage - int max_pierce = 0; + dbl_or_var max_pierce; // base energy cost of spell - int base_energy_cost = 0; + dbl_or_var base_energy_cost; // increment of energy cost per spell level - float energy_increment = 0.0f; + dbl_or_var energy_increment; // max or min energy cost, based on sign of energy_increment - int final_energy_cost = 0; + dbl_or_var final_energy_cost; // spell is restricted to being cast by only this class // if spell_class is empty, spell is unrestricted trait_id spell_class; // the difficulty of casting a spell - int difficulty = 0; + dbl_or_var difficulty; // max level this spell can achieve - int max_level = 0; + dbl_or_var max_level; // base amount of time to cast the spell in moves - int base_casting_time = 0; + dbl_or_var base_casting_time; // increment of casting time per level - float casting_time_increment = 0.0f; + dbl_or_var casting_time_increment; // max or min casting time - int final_casting_time = 0; + dbl_or_var final_casting_time; // Does leveling this spell lead to learning another spell? std::map learn_spells; @@ -409,7 +410,7 @@ namespace spell_desc { bool casting_time_encumbered( const spell &sp, const Character &guy ); bool energy_cost_encumbered( const spell &sp, const Character &guy ); -std::string enumerate_spell_data( const spell &sp ); +std::string enumerate_spell_data( const spell &sp, const Character &guy ); } // namespace spell_desc class spell @@ -428,13 +429,13 @@ class spell translation alt_message; // minimum damage including levels - int min_leveled_damage() const; - int min_leveled_dot() const; + int min_leveled_damage( const Creature &caster ) const; + int min_leveled_dot( const Creature &caster ) const; // minimum aoe including levels - int min_leveled_aoe() const; + int min_leveled_aoe( const Creature &caster ) const; // minimum duration including levels (moves) - int min_leveled_duration() const; - int min_leveled_accuracy() const; + int min_leveled_duration( const Creature &caster ) const; + int min_leveled_accuracy( const Creature &caster ) const; public: spell() = default; @@ -463,33 +464,35 @@ class spell void gain_levels( const Character &guy, int gains ); void set_level( const Character &guy, int nlevel ); // is the spell at max level? - bool is_max_level() const; + bool is_max_level( const Creature &caster ) const; // what is the max level of the spell - int get_max_level() const; + int get_max_level( const Creature &caster ) const; spell_shape shape() const; // what is the intensity of the field the spell generates ( 0 if no field ) - int field_intensity() const; + int field_intensity( const Creature &caster ) const; // how much damage does the spell do - int damage() const; - int accuracy() const; - int damage_dot() const; - damage_over_time_data damage_over_time( const std::vector &bps ) const; - dealt_damage_instance get_dealt_damage_instance() const; + int damage( const Creature &caster ) const; + int accuracy( Creature &caster ) const; + int damage_dot( const Creature &caster ) const; + damage_over_time_data damage_over_time( const std::vector &bps, + const Creature &caster ) const; + dealt_damage_instance get_dealt_damage_instance( Creature &caster ) const; dealt_projectile_attack get_projectile_attack( const tripoint &target, - Creature &hit_critter ) const; - damage_instance get_damage_instance() const; + Creature &hit_critter, Creature &caster ) const; + damage_instance get_damage_instance( Creature &caster ) const; // calculate damage per second against a target float dps( const Character &caster, const Creature &target ) const; // select a target for the spell std::optional select_target( Creature *source ); // how big is the spell's radius - int aoe() const; + int aoe( const Creature &caster ) const; std::set effect_area( const spell_effect::override_parameters ¶ms, const tripoint &source, const tripoint &target ) const; - std::set effect_area( const tripoint &source, const tripoint &target ) const; + std::set effect_area( const tripoint &source, const tripoint &target, + const Creature &caster ) const; // distance spell can be cast - int range() const; + int range( const Creature &caster ) const; /** * all of the tripoints the spell can be cast at. * if the spell can't be cast through walls, does not return anything behind walls @@ -499,8 +502,8 @@ class spell // how much energy does the spell cost int energy_cost( const Character &guy ) const; // how long does this spell's effect last - int duration() const; - time_duration duration_turns() const; + int duration( const Creature &caster ) const; + time_duration duration_turns( const Creature &caster ) const; // how often does the spell fail // based on difficulty, level of spell, spellcraft skill, intelligence float spell_fail( const Character &guy ) const; @@ -523,7 +526,7 @@ class spell // check if the spell's class is the same as input bool is_spell_class( const trait_id &mid ) const; - bool in_aoe( const tripoint &source, const tripoint &target ) const; + bool in_aoe( const tripoint &source, const tripoint &target, const Creature &caster ) const; // get spell id (from type) spell_id id() const; @@ -557,9 +560,9 @@ class spell //if targeted_species_ids is empty, it returns an empty string std::string list_targeted_species_names() const; - std::string damage_string() const; - std::string aoe_string() const; - std::string duration_string() const; + std::string damage_string( const Character &caster ) const; + std::string aoe_string( const Creature &caster ) const; + std::string duration_string( const Creature &caster ) const; // magic energy source enum magic_energy_type energy_source() const; @@ -569,17 +572,17 @@ class spell // your level in this spell int get_level() const; // difficulty of the level - int get_difficulty() const; + int get_difficulty( const Creature &caster ) const; // tries to create a field at the location specified - void create_field( const tripoint &at ) const; + void create_field( const tripoint &at, Creature &caster ) const; - int sound_volume() const; + int sound_volume( const Creature &caster ) const; // makes a spell sound at the location - void make_sound( const tripoint &target ) const; + void make_sound( const tripoint &target, Creature &caster ) const; void make_sound( const tripoint &target, int loudness ) const; // heals the critter at the location, returns amount healed (Character heals each body part) - int heal( const tripoint &target ) const; + int heal( const tripoint &target, Creature &caster ) const; // casts the spell effect. returns true if successful void cast_spell_effect( Creature &source, const tripoint &target ) const; @@ -688,9 +691,9 @@ struct override_parameters { int range; bool ignore_walls; - explicit override_parameters( const spell &sp ) { - aoe_radius = sp.aoe(); - range = sp.range(); + explicit override_parameters( const spell &sp, const Creature &caster ) { + aoe_radius = sp.aoe( caster ); + range = sp.range( caster ); ignore_walls = sp.has_flag( spell_flag::IGNORE_WALLS ); } }; diff --git a/src/magic_enchantment.cpp b/src/magic_enchantment.cpp index 393658e64112a..0832e1c6d9e0a 100644 --- a/src/magic_enchantment.cpp +++ b/src/magic_enchantment.cpp @@ -785,7 +785,7 @@ void enchant_cache::activate_passive( Character &guy ) const // a random approximation! if( one_in( to_seconds( activation.first ) ) ) { for( const fake_spell &fake : activation.second ) { - fake.get_spell( 0 ).cast_all_effects( guy, guy.pos() ); + fake.get_spell( guy, 0 ).cast_all_effects( guy, guy.pos() ); } } } @@ -818,10 +818,10 @@ void enchant_cache::cast_enchantment_spell( Character &caster, const Creature *t sp.trigger_message, sp.npc_trigger_message, caster.get_name() ); - sp.get_spell( sp.level ).cast_all_effects( caster, caster.pos() ); + sp.get_spell( caster, sp.level ).cast_all_effects( caster, caster.pos() ); } else if( target != nullptr ) { const Creature &trg_crtr = *target; - const spell &spell_lvl = sp.get_spell( sp.level ); + const spell &spell_lvl = sp.get_spell( caster, sp.level ); if( !spell_lvl.is_valid_target( caster, trg_crtr.pos() ) || !spell_lvl.is_target_in_range( caster, trg_crtr.pos() ) ) { return; diff --git a/src/magic_spell_effect.cpp b/src/magic_spell_effect.cpp index 5b8405c8a2763..c5ab192ca3647 100644 --- a/src/magic_spell_effect.cpp +++ b/src/magic_spell_effect.cpp @@ -149,7 +149,7 @@ void spell_effect::short_range_teleport( const spell &sp, Creature &caster, cons const bool safe = !sp.has_flag( spell_flag::UNSAFE_TELEPORT ); const bool target_teleport = sp.has_flag( spell_flag::TARGET_TELEPORT ); if( target_teleport ) { - if( sp.aoe() == 0 ) { + if( sp.aoe( caster ) == 0 ) { teleport::teleport_to_point( caster, target, safe, false ); return; } @@ -159,8 +159,8 @@ void spell_effect::short_range_teleport( const spell &sp, Creature &caster, cons teleport::teleport_to_point( caster, where, safe, false ); return; } - const int min_distance = sp.range(); - const int max_distance = sp.range() + sp.aoe(); + const int min_distance = sp.range( caster ); + const int max_distance = sp.range( caster ) + sp.aoe( caster ); if( min_distance > max_distance || min_distance < 0 || max_distance < 0 ) { debugmsg( "ERROR: Teleport argument(s) invalid" ); return; @@ -183,7 +183,7 @@ void spell_effect::pain_split( const spell &sp, Creature &caster, const tripoint if( you == nullptr ) { return; } - sp.make_sound( caster.pos() ); + sp.make_sound( caster.pos(), caster ); add_msg( m_info, _( "Your injuries even out." ) ); int num_limbs = 0; // number of limbs effected (broken don't count) int total_hp = 0; // total hp among limbs @@ -441,11 +441,11 @@ std::set calculate_spell_effect_area( const spell &sp, const tripoint } std::set targets = { epicenter }; // initialize with epicenter - if( sp.aoe() < 1 && sp.shape() != spell_shape::line ) { + if( sp.aoe( caster ) < 1 && sp.shape() != spell_shape::line ) { return targets; } - targets = sp.effect_area( caster.pos(), target ); + targets = sp.effect_area( caster.pos(), target, caster ); for( std::set::iterator it = targets.begin(); it != targets.end(); ) { if( !sp.is_valid_target( caster, *it ) ) { @@ -478,9 +478,9 @@ static std::set spell_effect_area( const spell &sp, const tripoint &ta return targets; } -static void add_effect_to_target( const tripoint &target, const spell &sp ) +static void add_effect_to_target( const tripoint &target, const spell &sp, Creature &caster ) { - const int dur_moves = sp.duration(); + const int dur_moves = sp.duration( caster ); const time_duration dur_td = time_duration::from_moves( dur_moves ); creature_tracker &creatures = get_creature_tracker(); @@ -511,8 +511,8 @@ static void damage_targets( const spell &sp, Creature &caster, if( !sp.is_valid_target( caster, target ) ) { continue; } - sp.make_sound( target ); - sp.create_field( target ); + sp.make_sound( target, caster ); + sp.create_field( target, caster ); if( sp.has_flag( spell_flag::IGNITE_FLAMMABLE ) && here.is_flammable( target ) ) { here.add_field( target, fd_fire, 1, 10_minutes ); @@ -530,8 +530,8 @@ static void damage_targets( const spell &sp, Creature &caster, continue; } - dealt_projectile_attack atk = sp.get_projectile_attack( target, *cr ); - const int spell_accuracy = sp.accuracy(); + dealt_projectile_attack atk = sp.get_projectile_attack( target, *cr, caster ); + const int spell_accuracy = sp.accuracy( caster ); double damage_mitigation_multiplier = 1.0; if( const int spell_block = cr->get_block_bonus() - spell_accuracy > 0 ) { const int roll = std::round( rng( 1, 20 ) ); @@ -553,23 +553,23 @@ static void damage_targets( const spell &sp, Creature &caster, } if( !sp.effect_data().empty() ) { - add_effect_to_target( target, sp ); + add_effect_to_target( target, sp, caster ); } - if( sp.damage() > 0 ) { + if( sp.damage( caster ) > 0 ) { for( damage_unit &val : atk.proj.impact.damage_units ) { if( sp.has_flag( spell_flag::PERCENTAGE_DAMAGE ) ) { - val.amount = cr->get_hp( cr->get_root_body_part() ) * sp.damage() / 100.0; + val.amount = cr->get_hp( cr->get_root_body_part() ) * sp.damage( caster ) / 100.0; } val.amount *= damage_mitigation_multiplier; } cr->deal_projectile_attack( &caster, atk, true ); - } else if( sp.damage() < 0 ) { - sp.heal( target ); + } else if( sp.damage( caster ) < 0 ) { + sp.heal( target, caster ); add_msg_if_player_sees( cr->pos(), m_good, _( "%s wounds are closing up!" ), cr->disp_name( true ) ); } // TODO: randomize hit location - cr->add_damage_over_time( sp.damage_over_time( { body_part_torso } ) ); + cr->add_damage_over_time( sp.damage_over_time( { body_part_torso }, caster ) ); } } @@ -637,7 +637,7 @@ void spell_effect::targeted_polymorph( const spell &sp, Creature &caster, const { //we only target monsters for now. if( monster *const victim = get_creature_tracker().creature_at( target ) ) { - if( victim->get_hp() < sp.damage() ) { + if( victim->get_hp() < sp.damage( caster ) ) { magical_polymorph( *victim, caster, sp ); return; } @@ -807,12 +807,13 @@ static void spell_move( const spell &sp, const Creature &caster, static std::pair spell_remove_field( const spell &sp, const field_type_id &target_field_type_id, - const tripoint ¢er ) + const tripoint ¢er, + const Creature &caster ) { ::map &here = get_map(); area_expander expander; - expander.max_range = sp.aoe(); + expander.max_range = sp.aoe( caster ); expander.run( center ); expander.sort_ascending(); @@ -897,11 +898,12 @@ static void handle_remove_fd_fatigue_field( const std::pair &fd void spell_effect::remove_field( const spell &sp, Creature &caster, const tripoint ¢er ) { const field_type_id &target_field_type_id = field_type_id( sp.effect_data() ); - std::pair field_removed = spell_remove_field( sp, target_field_type_id, center ); + std::pair field_removed = spell_remove_field( sp, target_field_type_id, center, + caster ); for( const std::pair &fd : std::get<0>( field_removed ) ) { if( fd.first.is_valid() && !fd.first.id().is_null() ) { - sp.make_sound( caster.pos() ); + sp.make_sound( caster.pos(), caster ); if( fd.first.id() == fd_fatigue ) { handle_remove_fd_fatigue_field( field_removed, caster ); @@ -917,7 +919,7 @@ void spell_effect::area_pull( const spell &sp, Creature &caster, const tripoint { area_expander expander; - expander.max_range = sp.aoe(); + expander.max_range = sp.aoe( caster ); expander.run( center ); expander.sort_ascending(); @@ -928,14 +930,14 @@ void spell_effect::area_pull( const spell &sp, Creature &caster, const tripoint spell_move( sp, caster, node.position, node.from ); } - sp.make_sound( caster.pos() ); + sp.make_sound( caster.pos(), caster ); } void spell_effect::area_push( const spell &sp, Creature &caster, const tripoint ¢er ) { area_expander expander; - expander.max_range = sp.aoe(); + expander.max_range = sp.aoe( caster ); expander.run( center ); expander.sort_descending(); @@ -946,7 +948,7 @@ void spell_effect::area_push( const spell &sp, Creature &caster, const tripoint spell_move( sp, caster, node.from, node.position ); } - sp.make_sound( caster.pos() ); + sp.make_sound( caster.pos(), caster ); } static void character_push_effects( Creature *caster, Character &guy, tripoint &push_dest, @@ -978,7 +980,7 @@ void spell_effect::directed_push( const spell &sp, Creature &caster, const tripo // whether it's push or pull, so how the multimap is sorted // -1 is push and 1 is pull - const int sign = sp.damage() > 0 ? -1 : 1; + const int sign = sp.damage( caster ) > 0 ? -1 : 1; std::multimap targets_ordered_by_range; for( const tripoint &pt : area ) { @@ -990,7 +992,7 @@ void spell_effect::directed_push( const spell &sp, Creature &caster, const tripo const tripoint &push_point = pair.second; const units::angle angle = coord_to_angle( caster.pos(), target ); // positive is push, negative is pull - int push_distance = sp.damage(); + int push_distance = sp.damage( caster ); const int prev_distance = rl_dist( caster.pos(), target ); if( push_distance < 0 ) { push_distance = std::max( -std::abs( push_distance ), -std::abs( prev_distance ) ); @@ -1060,13 +1062,14 @@ void spell_effect::spawn_ethereal_item( const spell &sp, Creature &caster, const { item granted( sp.effect_data(), calendar::turn ); // Comestibles are never ethereal. Other spawned items are ethereal unless permanent and max level. - if( !granted.is_comestible() && !( sp.has_flag( spell_flag::PERMANENT ) && sp.is_max_level() ) && + if( !granted.is_comestible() && !( sp.has_flag( spell_flag::PERMANENT ) && + sp.is_max_level( caster ) ) && !sp.has_flag( spell_flag::PERMANENT_ALL_LEVELS ) ) { - granted.set_var( "ethereal", to_turns( sp.duration_turns() ) ); + granted.set_var( "ethereal", to_turns( sp.duration_turns( caster ) ) ); granted.ethereal = true; } - if( granted.count_by_charges() && sp.damage() > 0 ) { - granted.charges = sp.damage(); + if( granted.count_by_charges() && sp.damage( caster ) > 0 ) { + granted.charges = sp.damage( caster ); } if( sp.has_flag( spell_flag::WITH_CONTAINER ) ) { granted = granted.in_its_container(); @@ -1082,17 +1085,17 @@ void spell_effect::spawn_ethereal_item( const spell &sp, Creature &caster, const player_character.i_add( granted ); } if( !granted.count_by_charges() ) { - for( int i = 1; i < sp.damage(); i++ ) { + for( int i = 1; i < sp.damage( caster ); i++ ) { player_character.i_add( granted ); } } - sp.make_sound( caster.pos() ); + sp.make_sound( caster.pos(), caster ); } void spell_effect::recover_energy( const spell &sp, Creature &caster, const tripoint &target ) { // this spell is not appropriate for healing - const int healing = sp.damage(); + const int healing = sp.damage( caster ); const std::string energy_source = sp.effect_data(); // current limitation is that Character does not have stamina or power_level members Character *you = get_creature_tracker().creature_at( target ); @@ -1125,7 +1128,7 @@ void spell_effect::recover_energy( const spell &sp, Creature &caster, const trip } else { debugmsg( "Invalid effect_str %s for spell %s", energy_source, sp.name() ); } - sp.make_sound( caster.pos() ); + sp.make_sound( caster.pos(), caster ); } void spell_effect::timed_event( const spell &sp, Creature &caster, const tripoint & ) @@ -1151,8 +1154,8 @@ void spell_effect::timed_event( const spell &sp, Creature &caster, const tripoin spell_event = iter->second; } - sp.make_sound( caster.pos() ); - get_timed_events().add( spell_event, calendar::turn + sp.duration_turns() ); + sp.make_sound( caster.pos(), caster ); + get_timed_events().add( spell_event, calendar::turn + sp.duration_turns( caster ) ); } static bool is_summon_friendly( const spell &sp ) @@ -1200,15 +1203,15 @@ void spell_effect::spawn_summoned_monster( const spell &sp, Creature &caster, { std::set area = spell_effect_area( sp, target, caster ); // this should never be negative, but this'll keep problems from happening - size_t num_mons = std::abs( sp.damage() ); - const time_duration summon_time = sp.duration_turns(); + size_t num_mons = std::abs( sp.damage( caster ) ); + const time_duration summon_time = sp.duration_turns( caster ); while( num_mons > 0 && !area.empty() ) { const size_t mon_spot = rng( 0, area.size() - 1 ); auto iter = area.begin(); std::advance( iter, mon_spot ); if( add_summoned_mon( *iter, summon_time, sp ) ) { num_mons--; - sp.make_sound( *iter ); + sp.make_sound( *iter, caster ); } else { debugmsg( "failed to place monster" ); } @@ -1229,7 +1232,7 @@ void spell_effect::spawn_summoned_vehicle( const spell &sp, Creature &caster, false ) ) { veh->magic = true; if( !sp.has_flag( spell_flag::PERMANENT ) ) { - veh->summon_time_limit = sp.duration_turns(); + veh->summon_time_limit = sp.duration_turns( caster ); } if( caster.as_character() ) { veh->set_owner( *caster.as_character() ); @@ -1257,15 +1260,15 @@ void spell_effect::transform_blast( const spell &sp, Creature &caster, ter_furn_transform_id transform( sp.effect_data() ); const std::set area = spell_effect_area( sp, target, caster ); for( const tripoint &location : area ) { - if( one_in( sp.damage() ) ) { + if( one_in( sp.damage( caster ) ) ) { transform->transform( get_map(), tripoint_bub_ms{ location } ); } } } -void spell_effect::noise( const spell &sp, Creature &, const tripoint &target ) +void spell_effect::noise( const spell &sp, Creature &caster, const tripoint &target ) { - sp.make_sound( target, sp.damage() ); + sp.make_sound( target, sp.damage( caster ) ); } void spell_effect::vomit( const spell &sp, Creature &caster, const tripoint &target ) @@ -1280,7 +1283,7 @@ void spell_effect::vomit( const spell &sp, Creature &caster, const tripoint &tar if( !ch ) { continue; } - sp.make_sound( target ); + sp.make_sound( target, caster ); ch->vomit(); } } @@ -1292,7 +1295,7 @@ void spell_effect::pull_to_caster( const spell &sp, Creature &caster, const trip void spell_effect::explosion( const spell &sp, Creature &caster, const tripoint &target ) { - explosion_handler::explosion( &caster, target, sp.damage(), sp.aoe() / 10.0, true ); + explosion_handler::explosion( &caster, target, sp.damage( caster ), sp.aoe( caster ) / 10.0, true ); } void spell_effect::flashbang( const spell &sp, Creature &caster, const tripoint &target ) @@ -1313,8 +1316,8 @@ void spell_effect::mod_moves( const spell &sp, Creature &caster, const tripoint if( !critter ) { continue; } - sp.make_sound( potential_target ); - critter->moves += sp.damage(); + sp.make_sound( potential_target, caster ); + critter->moves += sp.damage( caster ); } } @@ -1326,7 +1329,7 @@ void spell_effect::map( const spell &sp, Creature &caster, const tripoint & ) return; } const tripoint_abs_omt center = you->global_omt_location(); - overmap_buffer.reveal( center.xy(), sp.aoe(), center.z() ); + overmap_buffer.reveal( center.xy(), sp.aoe( caster ), center.z() ); } void spell_effect::morale( const spell &sp, Creature &caster, const tripoint &target ) @@ -1349,9 +1352,10 @@ void spell_effect::morale( const spell &sp, Creature &caster, const tripoint &ta ( player_target = creatures.creature_at( potential_target ) ) ) ) { continue; } - player_target->add_morale( morale_type( sp.effect_data() ), sp.damage(), 0, sp.duration_turns(), - sp.duration_turns() / 10, false ); - sp.make_sound( potential_target ); + player_target->add_morale( morale_type( sp.effect_data() ), sp.damage( caster ), 0, + sp.duration_turns( caster ), + sp.duration_turns( caster ) / 10, false ); + sp.make_sound( potential_target, caster ); } } @@ -1367,10 +1371,10 @@ void spell_effect::charm_monster( const spell &sp, Creature &caster, const tripo if( !mon ) { continue; } - sp.make_sound( potential_target ); - if( mon->friendly == 0 && mon->get_hp() <= sp.damage() ) { + sp.make_sound( potential_target, caster ); + if( mon->friendly == 0 && mon->get_hp() <= sp.damage( caster ) ) { mon->unset_dest(); - mon->friendly += sp.duration() / 100; + mon->friendly += sp.duration( caster ) / 100; } } } @@ -1402,7 +1406,7 @@ void spell_effect::upgrade( const spell &sp, Creature &caster, const tripoint &t creature_tracker &creatures = get_creature_tracker(); for( const tripoint &aoe : area ) { monster *mon = creatures.creature_at( aoe ); - if( mon != nullptr && rng( 1, 10000 ) < sp.damage() ) { + if( mon != nullptr && rng( 1, 10000 ) < sp.damage( caster ) ) { mon->allow_upgrade(); mon->try_upgrade( false ); } @@ -1426,7 +1430,7 @@ void spell_effect::guilt( const spell &sp, Creature &caster, const tripoint &tar monster &z = *caster.as_monster(); const int kill_count = g->get_kill_tracker().kill_count( z.type->id ); // this is when the player stops caring altogether. - const int max_kills = sp.damage(); + const int max_kills = sp.damage( caster ); // this determines how strong the morale penalty will be const int guilt_mult = sp.get_level(); @@ -1471,7 +1475,7 @@ void spell_effect::guilt( const spell &sp, Creature &caster, const tripoint &tar float killRatio = static_cast( kill_count ) / max_kills; int moraleMalus = -5 * guilt_mult * ( 1.0 - killRatio ); const int maxMalus = -250 * ( 1.0 - killRatio ); - const time_duration duration = sp.duration_turns() * ( 1.0 - killRatio ); + const time_duration duration = sp.duration_turns( caster ) * ( 1.0 - killRatio ); const time_duration decayDelay = 3_minutes * ( 1.0 - killRatio ); bool shared_species = false; @@ -1527,7 +1531,7 @@ void spell_effect::fungalize( const spell &sp, Creature &caster, const tripoint const std::set area = spell_effect_area( sp, target, caster ); fungal_effects fe; for( const tripoint &aoe : area ) { - fe.fungalize( aoe, &caster, sp.damage() / 10000.0 ); + fe.fungalize( aoe, &caster, sp.damage( caster ) / 10000.0 ); } } @@ -1544,7 +1548,7 @@ void spell_effect::mutate( const spell &sp, Creature &caster, const tripoint &ta continue; } // 10000 represents 100.00% to increase granularity without swapping everything to a float - if( sp.damage() < rng( 1, 10000 ) ) { + if( sp.damage( caster ) < rng( 1, 10000 ) ) { // chance failure! but keep trying for other targets continue; } @@ -1557,7 +1561,7 @@ void spell_effect::mutate( const spell &sp, Creature &caster, const tripoint &ta guy->mutate_category( mutation_category_id( sp.effect_data() ) ); } } - sp.make_sound( potential_target ); + sp.make_sound( potential_target, caster ); } } @@ -1570,7 +1574,7 @@ void spell_effect::bash( const spell &sp, Creature &caster, const tripoint &targ continue; } // the bash already makes noise, so no need for spell::make_sound() - here.bash( potential_target, sp.damage(), sp.has_flag( spell_flag::SILENT ) ); + here.bash( potential_target, sp.damage( caster ), sp.has_flag( spell_flag::SILENT ) ); } } @@ -1600,7 +1604,7 @@ void spell_effect::dash( const spell &sp, Creature &caster, const tripoint &targ --walk_point; break; } else { - sp.create_field( here.getlocal( *( walk_point - 1 ) ) ); + sp.create_field( here.getlocal( *( walk_point - 1 ) ), caster ); g->draw_ter(); } } @@ -1613,18 +1617,18 @@ void spell_effect::dash( const spell &sp, Creature &caster, const tripoint &targ caster.moves = cur_moves; tripoint far_target; - calc_ray_end( coord_to_angle( source, target ), sp.aoe(), here.getlocal( *walk_point ), + calc_ray_end( coord_to_angle( source, target ), sp.aoe( caster ), here.getlocal( *walk_point ), far_target ); - spell_effect::override_parameters params( sp ); - params.range = sp.aoe(); + spell_effect::override_parameters params( sp, caster ); + params.range = sp.aoe( caster ); const std::set hit_area = spell_effect_cone_range_override( params, source, far_target ); damage_targets( sp, caster, hit_area ); } void spell_effect::banishment( const spell &sp, Creature &caster, const tripoint &target ) { - int total_dam = sp.damage(); + int total_dam = sp.damage( caster ); if( total_dam <= 0 ) { debugmsg( "ERROR: Banishment has negative or 0 damage value" ); } @@ -1718,13 +1722,13 @@ void spell_effect::effect_on_condition( const spell &sp, Creature &caster, const void spell_effect::slime_split_on_death( const spell &sp, Creature &caster, const tripoint &target ) { - sp.make_sound( target ); + sp.make_sound( target, caster ); int mass = caster.get_speed_base(); monster *caster_monster = dynamic_cast( &caster ); if( caster_monster && caster_monster->type->id == mon_blob_brain ) { mass += mass; } - const int radius = sp.aoe(); + const int radius = sp.aoe( caster ); std::vector pts = closest_points_first( caster.pos(), radius ); std::vector summoned_slimes; const bool permanent = sp.has_flag( spell_flag::PERMANENT ); @@ -1745,9 +1749,9 @@ void spell_effect::slime_split_on_death( const spell &sp, Creature &caster, cons mon->ammo = mon->type->starting_ammo; if( mon->will_move_to( dest ) ) { if( monster *const blob = g->place_critter_around( mon, dest, 0 ) ) { - sp.make_sound( dest ); + sp.make_sound( dest, caster ); if( !permanent ) { - blob->set_summon_time( sp.duration_turns() ); + blob->set_summon_time( sp.duration_turns( caster ) ); } if( caster_monster ) { blob->make_ally( *caster_monster ); diff --git a/src/mattack_actors.cpp b/src/mattack_actors.cpp index b332267c03adc..80c7ac83936aa 100644 --- a/src/mattack_actors.cpp +++ b/src/mattack_actors.cpp @@ -358,11 +358,11 @@ bool mon_spellcasting_actor::call( monster &mon ) const const tripoint target = ( spell_data.self || allow_no_target ) ? mon.pos() : mon.attack_target()->pos(); - spell spell_instance = spell_data.get_spell(); + spell spell_instance = spell_data.get_spell( mon ); spell_instance.set_message( spell_data.trigger_message ); // Bail out if the target is out of range. - if( !spell_data.self && rl_dist( mon.pos(), target ) > spell_instance.range() ) { + if( !spell_data.self && rl_dist( mon.pos(), target ) > spell_instance.range( mon ) ) { return false; } diff --git a/src/monster.cpp b/src/monster.cpp index 471099fa298bb..db87a54cc2b8a 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -2627,7 +2627,7 @@ void monster::die( Creature *nkiller ) if( type->mdeath_effect.has_effect ) { //Not a hallucination, go process the death effects. - spell death_spell = type->mdeath_effect.sp.get_spell(); + spell death_spell = type->mdeath_effect.sp.get_spell( *this ); if( killer != nullptr && !type->mdeath_effect.sp.self && death_spell.is_target_in_range( *this, killer->pos() ) ) { death_spell.cast_all_effects( *this, killer->pos() ); diff --git a/src/npc.cpp b/src/npc.cpp index ccad3264c778a..e89850210e2b0 100644 --- a/src/npc.cpp +++ b/src/npc.cpp @@ -876,7 +876,7 @@ void npc::randomize( const npc_class_id &type ) for( std::pair spell_pair : type->_starting_spells ) { this->magic->learn_spell( spell_pair.first, *this, true ); spell &sp = this->magic->get_spell( spell_pair.first ); - while( sp.get_level() < spell_pair.second && !sp.is_max_level() ) { + while( sp.get_level() < spell_pair.second && !sp.is_max_level( *this ) ) { sp.gain_level( *this ); } } @@ -1864,7 +1864,7 @@ std::vector npc::spells_offered_to( Character &you ) if( you.magic->can_learn_spell( you, sp ) ) { if( you.magic->knows_spell( sp ) ) { const spell &student_spell = you.magic->get_spell( sp ); - if( student_spell.is_max_level() || + if( student_spell.is_max_level( you ) || student_spell.get_level() >= teacher_spell.get_level() ) { continue; } diff --git a/src/npc_attack.cpp b/src/npc_attack.cpp index 191664d78a2d3..cc05abc1fbedc 100644 --- a/src/npc_attack.cpp +++ b/src/npc_attack.cpp @@ -3,6 +3,7 @@ #include "cata_utility.h" #include "character.h" #include "creature_tracker.h" +#include "dialogue.h" #include "flag.h" #include "item.h" #include "line.h" @@ -150,7 +151,7 @@ bool npc_attack_spell::can_use( const npc &source ) const // missing components or energy or something return attack_spell.can_cast( source ) && // use the same rules as silent guns - !( source.rules.has_flag( ally_rule::use_silent ) && attack_spell.sound_volume() >= 5 ); + !( source.rules.has_flag( ally_rule::use_silent ) && attack_spell.sound_volume( source ) >= 5 ); } int npc_attack_spell::base_time_penalty( const npc &source ) const @@ -181,8 +182,9 @@ npc_attack_rating npc_attack_spell::evaluate_tripoint( if( !critter ) { // no critter? no damage! however, we assume fields are worth something if( attack_spell_id->field ) { - total_potential += static_cast( attack_spell.field_intensity() ) / - static_cast( attack_spell_id->field_chance ) / 2.0; + dialogue d( get_talker_for( source ), nullptr ); + total_potential += static_cast( attack_spell.field_intensity( source ) ) / + static_cast( attack_spell_id->field_chance.evaluate( d ) ) / 2.0; } continue; } diff --git a/src/npctalk.cpp b/src/npctalk.cpp index f9023c4cc2cf9..58d7b18ce822a 100644 --- a/src/npctalk.cpp +++ b/src/npctalk.cpp @@ -277,7 +277,7 @@ int calc_spell_training_cost( const Character &teacher, const Character &student } const spell &temp_spell = teacher.magic->get_spell( id ); const bool knows = student.magic->knows_spell( id ); - return calc_spell_training_cost_gen( knows, temp_spell.get_difficulty(), + return calc_spell_training_cost_gen( knows, temp_spell.get_difficulty( student ), temp_spell.get_level() ); } @@ -3391,7 +3391,7 @@ void talk_effect_fun_t::set_cast_spell( const JsonObject &jo, const std::stri debugmsg( "No valid caster for spell." ); run_eoc_vector( false_eocs, d ); } else { - spell sp = fake.get_spell( 0 ); + spell sp = fake.get_spell( *caster, 0 ); if( targeted ) { if( std::optional target = sp.select_target( caster ) ) { sp.cast_all_effects( *caster, *target ); diff --git a/src/profession.cpp b/src/profession.cpp index b979a13b1c662..d2786d23334b4 100644 --- a/src/profession.cpp +++ b/src/profession.cpp @@ -627,7 +627,7 @@ void profession::learn_spells( avatar &you ) const for( const std::pair spell_pair : spells() ) { you.magic->learn_spell( spell_pair.first, you, true ); spell &sp = you.magic->get_spell( spell_pair.first ); - while( sp.get_level() < spell_pair.second && !sp.is_max_level() ) { + while( sp.get_level() < spell_pair.second && !sp.is_max_level( you ) ) { sp.gain_level( you ); } } diff --git a/src/ranged.cpp b/src/ranged.cpp index 21d28ec35fd32..0ef35320a40d4 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -493,7 +493,7 @@ target_handler::trajectory target_handler::mode_spell( avatar &you, spell &casti ui.you = &you; ui.mode = target_ui::TargetMode::Spell; ui.casting = &casting; - ui.range = casting.range(); + ui.range = casting.range( you ); ui.no_fail = no_fail; ui.no_mana = no_mana; @@ -2597,7 +2597,7 @@ target_handler::trajectory target_ui::run() if( status != Status::Good ) { continue; } - bool can_skip_confirm = mode == TargetMode::Spell && casting->damage() <= 0; + bool can_skip_confirm = mode == TargetMode::Spell && casting->damage( player_character ) <= 0; if( !can_skip_confirm && !confirm_non_enemy_target() ) { continue; } @@ -2653,7 +2653,7 @@ target_handler::trajectory target_ui::run() break; } case ExitCode::Fire: { - bool harmful = !( mode == TargetMode::Spell && casting->damage() <= 0 ); + bool harmful = !( mode == TargetMode::Spell && casting->damage( player_character ) <= 0 ); on_target_accepted( harmful ); break; } @@ -2928,15 +2928,15 @@ bool target_ui::set_cursor_pos( const tripoint &new_pos ) switch( casting->shape() ) { case spell_shape::blast: spell_aoe = spell_effect::spell_effect_blast( - spell_effect::override_parameters( *casting ), src, dst ); + spell_effect::override_parameters( *casting, get_player_character() ), src, dst ); break; case spell_shape::cone: spell_aoe = spell_effect::spell_effect_cone( - spell_effect::override_parameters( *casting ), src, dst ); + spell_effect::override_parameters( *casting, get_player_character() ), src, dst ); break; case spell_shape::line: spell_aoe = spell_effect::spell_effect_line( - spell_effect::override_parameters( *casting ), src, dst ); + spell_effect::override_parameters( *casting, get_player_character() ), src, dst ); break; default: spell_aoe.clear(); @@ -3838,10 +3838,10 @@ void target_ui::panel_spell_info( int &text_y ) } print_colored_text( w_target, point( 1, text_y++ ), clr, clr, fail_str ); - if( casting->aoe() > 0 ) { + if( casting->aoe( get_player_character() ) > 0 ) { nc_color color = c_light_gray; const std::string fx = casting->effect(); - const std::string aoes = casting->aoe_string(); + const std::string aoes = casting->aoe_string( get_player_character() ); if( fx == "attack" || fx == "area_pull" || fx == "area_push" || fx == "ter_transform" ) { if( casting->shape() == spell_shape::cone ) { @@ -3853,13 +3853,14 @@ void target_ui::panel_spell_info( int &text_y ) } else { text_y += fold_and_print( w_target, point( 1, text_y ), getmaxx( w_target ) - 2, color, _( "Effective Spell Radius: %s%s" ), aoes, - casting->in_aoe( src, dst ) ? colorize( _( " WARNING! IN RANGE" ), c_red ) : "" ); + casting->in_aoe( src, dst, get_player_character() ) ? colorize( _( " WARNING! IN RANGE" ), + c_red ) : "" ); } } } mvwprintz( w_target, point( 1, text_y++ ), c_light_red, _( "Damage: %s" ), - casting->damage_string() ); + casting->damage_string( get_player_character() ) ); text_y += fold_and_print( w_target, point( 1, text_y ), getmaxx( w_target ) - 2, clr, casting->description() ); diff --git a/src/relic.cpp b/src/relic.cpp index 6b3c9db5451d4..1447efa0bbb65 100644 --- a/src/relic.cpp +++ b/src/relic.cpp @@ -421,7 +421,7 @@ int relic::activate( Creature &caster, const tripoint &target ) } caster.moves -= moves; for( const fake_spell &sp : active_effects ) { - spell casting = sp.get_spell( sp.level ); + spell casting = sp.get_spell( caster, sp.level ); casting.cast_all_effects( caster, target ); caster.add_msg_if_player( casting.message(), casting.name() ); } diff --git a/src/talker_monster.cpp b/src/talker_monster.cpp index ac5a03e0406ad..f1059c52ba74b 100644 --- a/src/talker_monster.cpp +++ b/src/talker_monster.cpp @@ -12,52 +12,52 @@ class time_duration; -std::string talker_monster::disp_name() const +std::string talker_monster_const::disp_name() const { return me_mon->disp_name(); } -int talker_monster::posx() const +int talker_monster_const::posx() const { return me_mon->posx(); } -int talker_monster::posy() const +int talker_monster_const::posy() const { return me_mon->posy(); } -int talker_monster::posz() const +int talker_monster_const::posz() const { return me_mon->posz(); } -tripoint talker_monster::pos() const +tripoint talker_monster_const::pos() const { return me_mon->pos(); } -tripoint_abs_ms talker_monster::global_pos() const +tripoint_abs_ms talker_monster_const::global_pos() const { return me_mon->get_location(); } -tripoint_abs_omt talker_monster::global_omt_location() const +tripoint_abs_omt talker_monster_const::global_omt_location() const { return me_mon->global_omt_location(); } -int talker_monster::pain_cur() const +int talker_monster_const::pain_cur() const { return me_mon->get_pain(); } -bool talker_monster::has_effect( const efftype_id &effect_id, const bodypart_id &bp ) const +bool talker_monster_const::has_effect( const efftype_id &effect_id, const bodypart_id &bp ) const { return me_mon->has_effect( effect_id, bp ); } -effect talker_monster::get_effect( const efftype_id &effect_id, const bodypart_id &bp ) const +effect talker_monster_const::get_effect( const efftype_id &effect_id, const bodypart_id &bp ) const { return me_mon->get_effect( effect_id, bp ); } @@ -79,7 +79,7 @@ void talker_monster::mod_pain( int amount ) me_mon->mod_pain( amount ); } -std::string talker_monster:: get_value( const std::string &var_name ) const +std::string talker_monster_const:: get_value( const std::string &var_name ) const { return me_mon->get_value( var_name ); } @@ -94,12 +94,12 @@ void talker_monster::remove_value( const std::string &var_name ) me_mon->remove_value( var_name ); } -std::string talker_monster::short_description() const +std::string talker_monster_const::short_description() const { return me_mon->type->get_description(); } -int talker_monster::get_anger() const +int talker_monster_const::get_anger() const { return me_mon->anger; } @@ -109,7 +109,7 @@ void talker_monster::set_anger( int new_val ) me_mon->anger = new_val; } -int talker_monster::morale_cur() const +int talker_monster_const::morale_cur() const { return me_mon->morale; } @@ -119,7 +119,7 @@ void talker_monster::set_morale( int new_val ) me_mon->morale = new_val; } -int talker_monster::get_friendly() const +int talker_monster_const::get_friendly() const { return me_mon->friendly; } @@ -129,17 +129,17 @@ void talker_monster::set_friendly( int new_val ) me_mon->friendly = new_val; } -std::vector talker_monster::get_topics( bool ) +std::vector talker_monster_const::get_topics( bool ) { return me_mon->type->chat_topics; } -int talker_monster::get_cur_hp( const bodypart_id & ) const +int talker_monster_const::get_cur_hp( const bodypart_id & ) const { return me_mon->get_hp(); } -bool talker_monster::will_talk_to_u( const Character &you, bool ) +bool talker_monster_const::will_talk_to_u( const Character &you, bool ) { return !you.is_dead_state(); } diff --git a/src/talker_monster.h b/src/talker_monster.h index 55d627db901f5..0f0a27f4592aa 100644 --- a/src/talker_monster.h +++ b/src/talker_monster.h @@ -23,7 +23,44 @@ class monster; /* * Talker wrapper class for monster. */ -class talker_monster: public talker +class talker_monster_const: public talker +{ + public: + explicit talker_monster_const( const monster *new_me ): me_mon( new_me ) { + } + ~talker_monster_const() override = default; + + // identity and location + std::string disp_name() const override; + + int posx() const override; + int posy() const override; + int posz() const override; + tripoint pos() const override; + tripoint_abs_ms global_pos() const override; + tripoint_abs_omt global_omt_location() const override; + + int pain_cur() const override; + + // effects and values + bool has_effect( const efftype_id &effect_id, const bodypart_id &bp ) const override; + effect get_effect( const efftype_id &effect_id, const bodypart_id &bp ) const override; + + std::string get_value( const std::string &var_name ) const override; + + std::string short_description() const override; + int get_anger() const override; + int morale_cur() const override; + int get_friendly() const override; + bool will_talk_to_u( const Character &u, bool force ) override; + std::vector get_topics( bool radio_contact ) override; + int get_cur_hp( const bodypart_id & ) const override; + protected: + talker_monster_const() = default; + const monster *me_mon; +}; + +class talker_monster: public talker_monster_const { public: explicit talker_monster( monster *new_me ): me_mon( new_me ) { @@ -43,41 +80,20 @@ class talker_monster: public talker Creature *get_creature() const override { return me_mon; } - // identity and location - std::string disp_name() const override; - - int posx() const override; - int posy() const override; - int posz() const override; - tripoint pos() const override; - tripoint_abs_ms global_pos() const override; - tripoint_abs_omt global_omt_location() const override; - - int pain_cur() const override; // effects and values - bool has_effect( const efftype_id &effect_id, const bodypart_id &bp ) const override; - effect get_effect( const efftype_id &effect_id, const bodypart_id &bp ) const override; void add_effect( const efftype_id &new_effect, const time_duration &dur, const std::string &bp, bool permanent, bool force, int intensity ) override; void remove_effect( const efftype_id &old_effect ) override; void mod_pain( int amount ) override; - std::string get_value( const std::string &var_name ) const override; void set_value( const std::string &var_name, const std::string &value ) override; void remove_value( const std::string &var_name ) override; - std::string short_description() const override; - int get_anger() const override; void set_anger( int ) override; - int morale_cur() const override; void set_morale( int ) override; - int get_friendly() const override; void set_friendly( int ) override; - bool will_talk_to_u( const Character &u, bool force ) override; - std::vector get_topics( bool radio_contact ) override; - int get_cur_hp( const bodypart_id & ) const override; protected: talker_monster() = default; monster *me_mon; diff --git a/src/talker_npc.cpp b/src/talker_npc.cpp index 4f1ed4b81b57b..82be538ff14db 100644 --- a/src/talker_npc.cpp +++ b/src/talker_npc.cpp @@ -391,7 +391,7 @@ std::string talker_npc::spell_training_text( talker &student, const spell_id &sp } const spell &temp_spell = me_npc->magic->get_spell( sp ); const bool knows = pupil->magic->knows_spell( sp ); - const int cost = me_npc->calc_spell_training_cost( knows, temp_spell.get_difficulty(), + const int cost = me_npc->calc_spell_training_cost( knows, temp_spell.get_difficulty( *pupil ), temp_spell.get_level() ); std::string text; if( knows ) { diff --git a/src/timed_event.cpp b/src/timed_event.cpp index 1ea82de86eccf..2091a0db59a74 100644 --- a/src/timed_event.cpp +++ b/src/timed_event.cpp @@ -289,7 +289,7 @@ void timed_event::actualize() const tripoint spot = here.getlocal( project_to( map_point ).raw() ); monster dispatcher( mon_dsa_alien_dispatch ); fake_spell summoning( spell_dks_summon_alrp, true, 12 ); - summoning.get_spell().cast_all_effects( dispatcher, spot ); + summoning.get_spell( player_character ).cast_all_effects( dispatcher, spot ); } else { tinymap mx_map; mx_map.load( map_point, false ); diff --git a/src/trapfunc.cpp b/src/trapfunc.cpp index 803411c446a02..1197d60a46530 100644 --- a/src/trapfunc.cpp +++ b/src/trapfunc.cpp @@ -1521,14 +1521,14 @@ bool trapfunc::cast_spell( const tripoint &p, Creature *critter, item * ) } map &here = get_map(); trap tr = here.tr_at( p ); - const spell trap_spell = tr.spell_data.get_spell( 0 ); + const spell trap_spell = tr.spell_data.get_spell( *critter, 0 ); npc dummy; if( !tr.has_flag( json_flag_UNCONSUMED ) ) { here.remove_trap( p ); } // we remove the trap before casting the spell because otherwise if we teleport we might be elsewhere at the end and p is no longer valid trap_spell.cast_all_effects( dummy, critter->pos() ); - trap_spell.make_sound( p ); + trap_spell.make_sound( p, get_player_character() ); return true; } diff --git a/tests/json_test.cpp b/tests/json_test.cpp index 6bf94073b7b41..8b85b80a996be 100644 --- a/tests/json_test.cpp +++ b/tests/json_test.cpp @@ -112,109 +112,42 @@ TEST_CASE( "spell_type handles all members", "[json]" ) CHECK( test_spell.spell_tags.test( spell_flag::CONCENTRATE ) ); CHECK( test_spell.field ); CHECK( test_spell.field->id() == field_test_field ); - CHECK( test_spell.field_chance == 2 ); - CHECK( test_spell.max_field_intensity == 2 ); - CHECK( test_spell.min_field_intensity == 2 ); - CHECK( test_spell.field_intensity_increment == 1 ); - CHECK( test_spell.field_intensity_variance == 1 ); - CHECK( test_spell.min_damage == 1 ); - CHECK( test_spell.max_damage == 1 ); - CHECK( test_spell.damage_increment == 1.0f ); - CHECK( test_spell.min_range == 1 ); - CHECK( test_spell.max_range == 1 ); - CHECK( test_spell.range_increment == 1.0f ); - CHECK( test_spell.min_aoe == 1 ); - CHECK( test_spell.max_aoe == 1 ); - CHECK( test_spell.aoe_increment == 1.0f ); - CHECK( test_spell.min_dot == 1 ); - CHECK( test_spell.max_dot == 1 ); - CHECK( test_spell.dot_increment == 1.0f ); - CHECK( test_spell.min_duration == 1 ); - CHECK( test_spell.max_duration == 1 ); - CHECK( test_spell.duration_increment == 1 ); - CHECK( test_spell.min_pierce == 1 ); - CHECK( test_spell.max_pierce == 1 ); - CHECK( test_spell.pierce_increment == 1.0f ); - CHECK( test_spell.base_energy_cost == 1 ); - CHECK( test_spell.final_energy_cost == 2 ); - CHECK( test_spell.energy_increment == 1.0f ); + CHECK( test_spell.field_chance.min.dbl_val.value() == 2 ); + CHECK( test_spell.max_field_intensity.min.dbl_val.value() == 2 ); + CHECK( test_spell.min_field_intensity.min.dbl_val.value() == 2 ); + CHECK( test_spell.field_intensity_increment.min.dbl_val.value() == 1 ); + CHECK( test_spell.field_intensity_variance.min.dbl_val.value() == 1 ); + CHECK( test_spell.min_damage.min.dbl_val.value() == 1 ); + CHECK( test_spell.max_damage.min.dbl_val.value() == 1 ); + CHECK( test_spell.damage_increment.min.dbl_val.value() == 1.0f ); + CHECK( test_spell.min_range.min.dbl_val.value() == 1 ); + CHECK( test_spell.max_range.min.dbl_val.value() == 1 ); + CHECK( test_spell.range_increment.min.dbl_val.value() == 1.0f ); + CHECK( test_spell.min_aoe.min.dbl_val.value() == 1 ); + CHECK( test_spell.max_aoe.min.dbl_val.value() == 1 ); + CHECK( test_spell.aoe_increment.min.dbl_val.value() == 1.0f ); + CHECK( test_spell.min_dot.min.dbl_val.value() == 1 ); + CHECK( test_spell.max_dot.min.dbl_val.value() == 1 ); + CHECK( test_spell.dot_increment.min.dbl_val.value() == 1.0f ); + CHECK( test_spell.min_duration.min.dbl_val.value() == 1 ); + CHECK( test_spell.max_duration.min.dbl_val.value() == 1 ); + CHECK( test_spell.duration_increment.min.dbl_val.value() == 1 ); + CHECK( test_spell.min_pierce.min.dbl_val.value() == 1 ); + CHECK( test_spell.max_pierce.min.dbl_val.value() == 1 ); + CHECK( test_spell.pierce_increment.min.dbl_val.value() == 1.0f ); + CHECK( test_spell.base_energy_cost.min.dbl_val.value() == 1 ); + CHECK( test_spell.final_energy_cost.min.dbl_val.value() == 2 ); + CHECK( test_spell.energy_increment.min.dbl_val.value() == 1.0f ); CHECK( test_spell.spell_class == trait_test_trait ); CHECK( test_spell.energy_source == magic_energy_type::mana ); CHECK( test_spell.dmg_type == damage_type::PURE ); - CHECK( test_spell.difficulty == 1 ); - CHECK( test_spell.max_level == 1 ); - CHECK( test_spell.base_casting_time == 1 ); - CHECK( test_spell.final_casting_time == 2 ); - CHECK( test_spell.casting_time_increment == 1.0f ); + CHECK( test_spell.difficulty.min.dbl_val.value() == 1 ); + CHECK( test_spell.max_level.min.dbl_val.value() == 1 ); + CHECK( test_spell.base_casting_time.min.dbl_val.value() == 1 ); + CHECK( test_spell.final_casting_time.min.dbl_val.value() == 2 ); + CHECK( test_spell.casting_time_increment.min.dbl_val.value() == 1.0f ); CHECK( test_spell.learn_spells == test_learn_spell ); } - - SECTION( "spell_types serialize correctly" ) { - const std::string serialized_spell_type = - R"({)" - R"("type":"SPELL",)" - R"("id":"test_spell_json",)" - R"("name":"test spell",)" - R"("description":"a spell to make sure the json deserialization and serialization is working properly",)" - R"("effect":"attack",)" - R"("shape":"blast",)" - R"("valid_targets":["none"],)" - R"("effect_str":"string",)" - R"("skill":"not_spellcraft",)" - R"("components":"test_components",)" - R"("message":"test message",)" - R"("sound_description":"test_description",)" - R"("sound_type":"weather",)" - R"("sound_ambient":true,)" - R"("sound_id":"test_sound",)" - R"("sound_variant":"not_default",)" - R"("targeted_monster_ids":["mon_test"],)" - R"("extra_effects":[{"id":"test_fake_spell"}],)" - R"("affected_body_parts":["head"],)" - R"("flags":["CONCENTRATE"],)" - R"("field_id":"test_field",)" - R"("field_chance":2,)" - R"("max_field_intensity":2,)" - R"("min_field_intensity":2,)" - R"("field_intensity_increment":1.000000,)" - R"("field_intensity_variance":1.000000,)" - R"("min_damage":1,)" - R"("max_damage":1,)" - R"("damage_increment":1.000000,)" - R"("min_range":1,)" - R"("max_range":1,)" - R"("range_increment":1.000000,)" - R"("min_aoe":1,)" - R"("max_aoe":1,)" - R"("aoe_increment":1.000000,)" - R"("min_dot":1,)" - R"("max_dot":1,)" - R"("dot_increment":1.000000,)" - R"("min_duration":1,)" - R"("max_duration":1,)" - R"("duration_increment":1,)" - R"("min_pierce":1,)" - R"("max_pierce":1,)" - R"("pierce_increment":1.000000,)" - R"("base_energy_cost":1,)" - R"("final_energy_cost":2,)" - R"("energy_increment":1.000000,)" - R"("spell_class":"test_trait",)" - R"("energy_source":"MANA",)" - R"("damage_type":"pure",)" - R"("difficulty":1,)" - R"("max_level":1,)" - R"("base_casting_time":1,)" - R"("final_casting_time":2,)" - R"("casting_time_increment":1.000000,)" - R"("learn_spells":{"test_fake_spell":1})" - R"(})"; - - std::ostringstream os; - JsonOut jsout( os ); - jsout.write( test_spell ); - REQUIRE( os.str() == serialized_spell_type ); - } } TEST_CASE( "serialize_colony", "[json]" ) diff --git a/tests/magic_spell_test.cpp b/tests/magic_spell_test.cpp index 9e9427bfda0de..94db11ca6c050 100644 --- a/tests/magic_spell_test.cpp +++ b/tests/magic_spell_test.cpp @@ -79,7 +79,7 @@ TEST_CASE( "spell level", "[magic][spell][level]" ) spell_id pew_id( "test_spell_pew" ); const spell_type &pew_type = pew_id.obj(); - REQUIRE( pew_type.max_level == 10 ); + REQUIRE( pew_type.max_level.min.dbl_val.value() == 10 ); GIVEN( "spell level 0" ) { spell pew_spell( pew_id ); @@ -93,15 +93,15 @@ TEST_CASE( "spell level", "[magic][spell][level]" ) } AND_WHEN( "spell levels up to max_level" ) { - pew_spell.set_level( guy, pew_type.max_level ); + pew_spell.set_level( guy, pew_type.max_level.min.dbl_val.value() ); THEN( "it is maximum level" ) { - CHECK( pew_spell.get_level() == pew_type.max_level ); + CHECK( pew_spell.get_level() == pew_type.max_level.min.dbl_val.value() ); } AND_THEN( "it cannot level up beyond max_level" ) { - pew_spell.set_level( guy, pew_type.max_level + 1 ); - CHECK( pew_spell.get_level() == pew_type.max_level ); + pew_spell.set_level( guy, pew_type.max_level.min.dbl_val.value() + 1 ); + CHECK( pew_spell.get_level() == pew_type.max_level.min.dbl_val.value() ); } } } @@ -240,7 +240,7 @@ static int spell_damage( const spell_id &sp_id, const int spell_level ) npc guy; spell test_spell( sp_id ); test_spell.set_level( guy, spell_level ); - return test_spell.damage(); + return test_spell.damage( guy ); } TEST_CASE( "spell damage", "[magic][spell][damage]" ) @@ -249,17 +249,17 @@ TEST_CASE( "spell damage", "[magic][spell][damage]" ) const spell_type &pew_type = pew_id.obj(); // Level 0 damage for this spell is 1 - REQUIRE( pew_type.min_damage == 1 ); + REQUIRE( pew_type.min_damage.min.dbl_val.value() == 1 ); // and 1 damage is added at each level - REQUIRE( pew_type.damage_increment == 1 ); + REQUIRE( pew_type.damage_increment.min.dbl_val.value() == 1 ); // however, maximum damage is 5 - REQUIRE( pew_type.max_damage == 5 ); + REQUIRE( pew_type.max_damage.min.dbl_val.value() == 5 ); // so maximum damage will be reached at level 4, when // // Lv4 damage = 1 + 4 * 1 = 5 // min lvl inc max // Because this spell has a maximum level of 10 - REQUIRE( pew_type.max_level == 10 ); + REQUIRE( pew_type.max_level.min.dbl_val.value() == 10 ); // damage from level 5-10 remains at 5. SECTION( "spell damage varies from min_damage to max_damage as level increases" ) { @@ -293,7 +293,7 @@ static std::string spell_duration_string( const spell_id &sp_id, const int spell npc guy; spell test_spell( sp_id ); test_spell.set_level( guy, spell_level ); - return test_spell.duration_string(); + return test_spell.duration_string( guy ); } TEST_CASE( "spell duration", "[magic][spell][duration]" ) @@ -302,17 +302,17 @@ TEST_CASE( "spell duration", "[magic][spell][duration]" ) const spell_type &lava_type = lava_id.obj(); // Level 0 duration for this spell is 100 seconds - REQUIRE( lava_type.min_duration == 10000 ); + REQUIRE( lava_type.min_duration.min.dbl_val.value() == 10000 ); // and 10 seconds are added at each level - REQUIRE( lava_type.duration_increment == 1000 ); + REQUIRE( lava_type.duration_increment.min.dbl_val.value() == 1000 ); // however, maximum duration is 250 seconds - REQUIRE( lava_type.max_duration == 25000 ); + REQUIRE( lava_type.max_duration.min.dbl_val.value() == 25000 ); // maximum duration will be reached at level 15, when // // Lv15 duration = 100 + 15 * 10 = 250 // min lvl inc max // Because this spell has a maximum level of 20 - REQUIRE( lava_type.max_level == 20 ); + REQUIRE( lava_type.max_level.min.dbl_val.value() == 20 ); // duration from level 16-20 remains 250 seconds. SECTION( "spell duration varies from min_duration to max_duration as level increases" ) { @@ -348,15 +348,16 @@ TEST_CASE( "spell duration", "[magic][spell][duration]" ) // - If spell has "effect": "summon", the summoned monster can have permanent duration at any level TEST_CASE( "permanent spell duration depends on effect and level", "[magic][spell][permanent]" ) { + npc guy; GIVEN( "spell with spawn_item effect, nonzero duration, and PERMANENT flag" ) { const spell_type &box_type = spell_test_spell_box.obj(); const spell box_spell( spell_test_spell_box ); REQUIRE( box_type.effect_name == "spawn_item" ); - REQUIRE( box_type.duration_increment > 0 ); - REQUIRE( box_type.min_duration > 0 ); - REQUIRE( box_type.max_duration > 0 ); + REQUIRE( box_type.duration_increment.min.dbl_val.value() > 0 ); + REQUIRE( box_type.min_duration.min.dbl_val.value() > 0 ); + REQUIRE( box_type.max_duration.min.dbl_val.value() > 0 ); REQUIRE( box_spell.has_flag( spell_flag::PERMANENT ) ); - REQUIRE( box_spell.get_max_level() > 9 ); + REQUIRE( box_spell.get_max_level( guy ) > 9 ); THEN( "spell has increasing duration before reaching max level" ) { CHECK( spell_duration_string( spell_test_spell_box, 0 ) == "10 minutes" ); @@ -372,7 +373,8 @@ TEST_CASE( "permanent spell duration depends on effect and level", "[magic][spel } THEN( "spell is permanent at max level" ) { - CHECK( spell_duration_string( spell_test_spell_box, box_spell.get_max_level() ) == "Permanent" ); + CHECK( spell_duration_string( spell_test_spell_box, + box_spell.get_max_level( guy ) ) == "Permanent" ); } } @@ -380,17 +382,17 @@ TEST_CASE( "permanent spell duration depends on effect and level", "[magic][spel const spell_type &mummy_type = spell_test_spell_tp_mummy.obj(); const spell mummy_spell( spell_test_spell_tp_mummy ); REQUIRE( mummy_type.effect_name == "summon" ); - REQUIRE( mummy_type.min_duration == 0 ); - REQUIRE( mummy_type.max_duration == 0 ); + REQUIRE( mummy_type.min_duration.min.dbl_val.value() == 0 ); + REQUIRE( mummy_type.max_duration.min.dbl_val.value() == 0 ); REQUIRE( mummy_spell.has_flag( spell_flag::PERMANENT ) ); - REQUIRE( mummy_spell.get_max_level() > 0 ); + REQUIRE( mummy_spell.get_max_level( guy ) > 0 ); THEN( "spell has permanent duration at every level" ) { CHECK( spell_duration_string( spell_test_spell_tp_mummy, 0 ) == "Permanent" ); CHECK( spell_duration_string( spell_test_spell_tp_mummy, 1 ) == "Permanent" ); CHECK( spell_duration_string( spell_test_spell_tp_mummy, 2 ) == "Permanent" ); CHECK( spell_duration_string( spell_test_spell_tp_mummy, - mummy_spell.get_max_level() ) == "Permanent" ); + mummy_spell.get_max_level( guy ) ) == "Permanent" ); } } } @@ -413,7 +415,7 @@ static int spell_range( const spell_id &sp_id, const int spell_level ) npc guy; spell test_spell( sp_id ); test_spell.set_level( guy, spell_level ); - return test_spell.range(); + return test_spell.range( guy ); } TEST_CASE( "spell range", "[magic][spell][range]" ) @@ -422,17 +424,17 @@ TEST_CASE( "spell range", "[magic][spell][range]" ) const spell_type &pew_type = pew_id.obj(); // Level 0 range for this spell is 10 - REQUIRE( pew_type.min_range == 10 ); + REQUIRE( pew_type.min_range.min.dbl_val.value() == 10 ); // with 2.0 added at each level - REQUIRE( pew_type.range_increment == 2 ); + REQUIRE( pew_type.range_increment.min.dbl_val.value() == 2 ); // reaching a maximum range of 30 - REQUIRE( pew_type.max_range == 30 ); + REQUIRE( pew_type.max_range.min.dbl_val.value() == 30 ); // maximum range will be reached at level 10, when // // Lv10 range = 10 + 10 * 2.0 = 30 // min lvl inc max // This coincides with the maximum level of the spell - REQUIRE( pew_type.max_level == 10 ); + REQUIRE( pew_type.max_level.min.dbl_val.value() == 10 ); // giving an even spread of ranges across all levels. SECTION( "spell range varies from min_range to max_range as level increases" ) { @@ -468,7 +470,7 @@ static int spell_aoe( const spell_id &sp_id, const int spell_level ) npc guy; spell test_spell( sp_id ); test_spell.set_level( guy, spell_level ); - return test_spell.aoe(); + return test_spell.aoe( guy ); } TEST_CASE( "spell area of effect", "[magic][spell][aoe]" ) @@ -477,17 +479,17 @@ TEST_CASE( "spell area of effect", "[magic][spell][aoe]" ) const spell_type &lava_type = lava_id.obj(); // Level 0 AOE for this spell is 4 - REQUIRE( lava_type.min_aoe == 4 ); + REQUIRE( lava_type.min_aoe.min.dbl_val.value() == 4 ); // with 1.0 added at each level - REQUIRE( lava_type.aoe_increment == 1 ); + REQUIRE( lava_type.aoe_increment.min.dbl_val.value() == 1 ); // however, maximum AOE is 15 - REQUIRE( lava_type.max_aoe == 15 ); + REQUIRE( lava_type.max_aoe.min.dbl_val.value() == 15 ); // so maximum AOE will be reached at level 11, when // // Lv11 aoe = 4 + 11 * 1.0 = 15 // min lvl inc max // Because this spell has a maximum level of 20 - REQUIRE( lava_type.max_level == 20 ); + REQUIRE( lava_type.max_level.min.dbl_val.value() == 20 ); // AOE from level 12-20 remains 15. SECTION( "spell area of effect varies from min_aoe to max_aoe as level increases" ) { @@ -556,14 +558,14 @@ TEST_CASE( "spell effect - target_attack", "[magic][spell][effect][target_attack // Ensure the spell has the needed attributes const spell_type &pew_type = pew_id.obj(); REQUIRE( pew_type.effect_name == "attack" ); - REQUIRE( pew_type.min_damage > 0 ); - REQUIRE( pew_type.min_range >= 2 ); + REQUIRE( pew_type.min_damage.min.dbl_val.value() > 0 ); + REQUIRE( pew_type.min_range.min.dbl_val.value() >= 2 ); // The spell itself spell pew_spell( pew_id ); pew_spell.set_level( dummy, 5 ); - REQUIRE( pew_spell.damage() > 0 ); - REQUIRE( pew_spell.range() >= 2 ); + REQUIRE( pew_spell.damage( dummy ) > 0 ); + REQUIRE( pew_spell.range( dummy ) >= 2 ); // Ensure avatar has enough mana to cast REQUIRE( dummy.magic->has_enough_energy( dummy, pew_spell ) ); @@ -574,7 +576,7 @@ TEST_CASE( "spell effect - target_attack", "[magic][spell][effect][target_attack after_hp = mummy.get_hp(); // Should do approximately the expected damage - CHECK( before_hp - pew_spell.damage() == Approx( after_hp ).margin( 1 ) ); + CHECK( before_hp - pew_spell.damage( dummy ) == Approx( after_hp ).margin( 1 ) ); } // spell_effect::spawn_summoned_monster @@ -638,13 +640,13 @@ TEST_CASE( "spell effect - recover_energy", "[magic][spell][effect][recover_ener REQUIRE( montage_type.effect_name == "recover_energy" ); REQUIRE( montage_type.effect_str == "STAMINA" ); // at the cost of a substantial amount of mana - REQUIRE( montage_type.base_energy_cost == 800 ); + REQUIRE( montage_type.base_energy_cost.min.dbl_val.value() == 800 ); REQUIRE( montage_type.energy_source == magic_energy_type::mana ); // At level 0, recovers 1000 stamina (10% of maximum) - REQUIRE( montage_type.min_damage == 1000 ); + REQUIRE( montage_type.min_damage.min.dbl_val.value() == 1000 ); // and at level 10, recovers 10000 stamina (all of it) - REQUIRE( montage_type.max_damage == 10000 ); + REQUIRE( montage_type.max_damage.min.dbl_val.value() == 10000 ); // Ensure avatar needs some stamina int start_stamina = dummy.get_stamina_max() / 2; @@ -656,7 +658,7 @@ TEST_CASE( "spell effect - recover_energy", "[magic][spell][effect][recover_ener montage_spell.cast_spell_effect( dummy, dummy.pos() ); // Get stamina back equal to min_damage (at level 0) - CHECK( dummy.get_stamina() == start_stamina + montage_type.min_damage ); + CHECK( dummy.get_stamina() == start_stamina + montage_type.min_damage.min.dbl_val.value() ); } SECTION( "reduce pain" ) { @@ -666,9 +668,9 @@ TEST_CASE( "spell effect - recover_energy", "[magic][spell][effect][recover_ener REQUIRE( kiss_type.effect_name == "recover_energy" ); REQUIRE( kiss_type.effect_str == "PAIN" ); // Positive "damage" for pain gives relief from pain - REQUIRE( kiss_type.min_damage == 1 ); - REQUIRE( kiss_type.max_damage == 10 ); - REQUIRE( kiss_type.damage_increment == 1 ); + REQUIRE( kiss_type.min_damage.min.dbl_val.value() == 1 ); + REQUIRE( kiss_type.max_damage.min.dbl_val.value() == 10 ); + REQUIRE( kiss_type.damage_increment.min.dbl_val.value() == 1 ); spell kiss_spell( kiss_id );