diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index 5eff03fcedc5b..6656042b98f01 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -4057,7 +4057,7 @@ void activity_handlers::spellcasting_finish( player_activity *act, player *p ) } if( !no_mana ) { // pay the cost - int cost = casting.energy_cost(); + int cost = casting.energy_cost( *p ); switch( casting.energy_source() ) { case mana_energy: p->magic.mod_mana( *p, -cost ); diff --git a/src/handle_action.cpp b/src/handle_action.cpp index fd2b953354e7b..70232228e46e4 100644 --- a/src/handle_action.cpp +++ b/src/handle_action.cpp @@ -1330,7 +1330,7 @@ static void cast_spell() return; } - player_activity cast_spell( activity_id( "ACT_SPELLCASTING" ), sp.casting_time() ); + player_activity cast_spell( activity_id( "ACT_SPELLCASTING" ), sp.casting_time( u ) ); // [0] this is used as a spell level override for items casting spells cast_spell.values.emplace_back( -1 ); // [1] if this value is 1, the spell never fails diff --git a/src/iuse_actor.cpp b/src/iuse_actor.cpp index a51fbc0db7204..4872299d01258 100644 --- a/src/iuse_actor.cpp +++ b/src/iuse_actor.cpp @@ -2304,7 +2304,7 @@ int cast_spell_actor::use( player &p, item &itm, bool, const tripoint & ) const spell casting = spell( spell_id( item_spell ) ); int charges = itm.type->charges_to_use(); - player_activity cast_spell( activity_id( "ACT_SPELLCASTING" ), casting.casting_time() ); + player_activity cast_spell( activity_id( "ACT_SPELLCASTING" ), casting.casting_time( p ) ); // [0] this is used as a spell level override for items casting spells cast_spell.values.emplace_back( spell_level ); if( no_fail ) { diff --git a/src/magic.cpp b/src/magic.cpp index 4ddcc62fb81f9..6736cd6d631e8 100644 --- a/src/magic.cpp +++ b/src/magic.cpp @@ -276,6 +276,11 @@ spell_id spell::id() const return type->id; } +trait_id spell::spell_class() const +{ + return type->spell_class; +} + int spell::damage() const { if( type->min_damage >= 0 ) { @@ -329,17 +334,24 @@ bool spell::can_learn( const player &p ) const return p.has_trait( type->spell_class ); } -int spell::energy_cost() const +int spell::energy_cost( const player &p ) const { + int cost; if( type->base_energy_cost < type->final_energy_cost ) { - return std::min( type->final_energy_cost, + cost = std::min( type->final_energy_cost, static_cast( round( type->base_energy_cost + type->energy_increment * get_level() ) ) ); } else if( type->base_energy_cost > type->final_energy_cost ) { - return std::max( type->final_energy_cost, + cost = std::max( type->final_energy_cost, static_cast( round( type->base_energy_cost + type->energy_increment * get_level() ) ) ); } else { - return type->base_energy_cost; + cost = type->base_energy_cost; } + if( !has_flag( spell_flag::NO_HANDS ) ) { + // the first 10 points of combined encumbrance is ignored, but quickly adds up + const int hands_encumb = std::max( 0, p.encumb( bp_hand_l ) + p.encumb( bp_hand_r ) - 10 ); + cost += 10 * hands_encumb; + } + return cost; } bool spell::has_flag( const spell_flag &flag ) const @@ -361,19 +373,19 @@ bool spell::can_cast( const player &p ) const } switch( type->energy_source ) { case mana_energy: - return p.magic.available_mana() >= energy_cost(); + return p.magic.available_mana() >= energy_cost( p ); case stamina_energy: - return p.stamina >= energy_cost(); + return p.stamina >= energy_cost( p ); case hp_energy: { for( int i = 0; i < num_hp_parts; i++ ) { - if( energy_cost() < p.hp_cur[i] ) { + if( energy_cost( p ) < p.hp_cur[i] ) { return true; } } return false; } case bionic_energy: - return p.power_level >= energy_cost(); + return p.power_level >= energy_cost( p ); case fatigue_energy: return p.get_fatigue() < EXHAUSTED; case none_energy: @@ -387,17 +399,30 @@ int spell::get_difficulty() const return type->difficulty; } -int spell::casting_time() const +int spell::casting_time( const player &p ) const { + // casting time in moves + int casting_time = 0; if( type->base_casting_time < type->final_casting_time ) { - return std::min( type->final_casting_time, - static_cast( round( type->base_casting_time + type->casting_time_increment * get_level() ) ) ); + casting_time = std::min( type->final_casting_time, + static_cast( round( type->base_casting_time + type->casting_time_increment * get_level() ) ) ); } else if( type->base_casting_time > type->final_casting_time ) { - return std::max( type->final_casting_time, - static_cast( round( type->base_casting_time + type->casting_time_increment * get_level() ) ) ); + casting_time = std::max( type->final_casting_time, + static_cast( round( type->base_casting_time + type->casting_time_increment * get_level() ) ) ); } else { - return type->base_casting_time; + casting_time = type->base_casting_time; + } + if( !has_flag( spell_flag::NO_LEGS ) ) { + // the first 20 points of encumbrance combined is ignored + const int legs_encumb = std::max( 0, p.encumb( bp_leg_l ) + p.encumb( bp_leg_r ) - 20 ); + casting_time += legs_encumb * 3; } + if( has_flag( spell_flag::SOMATIC ) ) { + // the first 20 points of encumbrance combined is ignored + const int arms_encumb = std::max( 0, p.encumb( bp_arm_l ) + p.encumb( bp_arm_r ) - 20 ); + casting_time += arms_encumb * 2; + } + return casting_time; } std::string spell::name() const @@ -420,7 +445,25 @@ float spell::spell_fail( const player &p ) const } else if( effective_skill < 0.0f ) { return 1.0f; } - const float fail_chance = pow( ( effective_skill - 30.0f ) / 30.0f, 2 ); + float fail_chance = pow( ( effective_skill - 30.0f ) / 30.0f, 2 ); + if( has_flag( spell_flag::SOMATIC ) ) { + // the first 20 points of encumbrance combined is ignored + const int arms_encumb = std::max( 0, p.encumb( bp_arm_l ) + p.encumb( bp_arm_r ) - 20 ); + // each encumbrance point beyond the "gray" color counts as half an additional fail % + fail_chance += arms_encumb / 200.0f; + } + if( has_flag( spell_flag::VERBAL ) ) { + // a little bit of mouth encumbrance is allowed, but not much + const int mouth_encumb = std::max( 0, p.encumb( bp_mouth ) - 5 ); + fail_chance += mouth_encumb / 100.0f; + } + // concentration spells work better than you'd expect with a higher focus pool + if( has_flag( spell_flag::CONCENTRATE ) ) { + if( p.focus_pool <= 0 ) { + return 0.0f; + } + fail_chance /= p.focus_pool / 100.0f; + } return clamp( fail_chance, 0.0f, 1.0f ); } @@ -481,18 +524,18 @@ std::string spell::energy_cost_string( const player &p ) const return _( "none" ); } if( energy_source() == bionic_energy || energy_source() == mana_energy ) { - return colorize( to_string( energy_cost() ), c_light_blue ); + return colorize( to_string( energy_cost( p ) ), c_light_blue ); } if( energy_source() == hp_energy ) { - auto pair = get_hp_bar( energy_cost(), p.get_hp_max() / num_hp_parts ); + auto pair = get_hp_bar( energy_cost( p ), p.get_hp_max() / num_hp_parts ); return colorize( pair.first, pair.second ); } if( energy_source() == stamina_energy ) { - auto pair = get_hp_bar( energy_cost(), p.get_stamina_max() ); + auto pair = get_hp_bar( energy_cost( p ), p.get_stamina_max() ); return colorize( pair.first, pair.second ); } if( energy_source() == fatigue_energy ) { - return colorize( to_string( energy_cost() ), c_cyan ); + return colorize( to_string( energy_cost( p ) ), c_cyan ); } debugmsg( "ERROR: Spell %s has invalid energy source.", id().c_str() ); return _( "error: energy_type" ); @@ -952,7 +995,7 @@ std::vector known_magic::spells() const // does the player have enough energy (of the type of the spell) to cast the spell? bool known_magic::has_enough_energy( const player &p, spell &sp ) const { - int cost = sp.energy_cost(); + int cost = sp.energy_cost( p ); switch( sp.energy_source() ) { case mana_energy: return available_mana() >= cost; @@ -1045,6 +1088,59 @@ static std::string moves_to_string( const int moves ) } } +static bool casting_time_encumbered( const spell &sp, const player &p ) +{ + int encumb = 0; + if( !sp.has_flag( spell_flag::NO_LEGS ) ) { + // the first 20 points of encumbrance combined is ignored + encumb += std::max( 0, p.encumb( bp_leg_l ) + p.encumb( bp_leg_r ) - 20 ); + } + if( sp.has_flag( spell_flag::SOMATIC ) ) { + // the first 20 points of encumbrance combined is ignored + encumb += std::max( 0, p.encumb( bp_arm_l ) + p.encumb( bp_arm_r ) - 20 ); + } + if( encumb > 0 ) { + return true; + } + return false; +} + +static bool energy_cost_encumbered( const spell &sp, const player &p ) +{ + if( !sp.has_flag( spell_flag::NO_HANDS ) ) { + return std::max( 0, p.encumb( bp_hand_l ) + p.encumb( bp_hand_r ) - 10 ) > 0; + } + return false; +} + +// this prints various things about the spell out in a list +// including flags and things like "goes through walls" +static std::string enumerate_spell_data( const spell &sp ) +{ + std::vector spell_data; + if( sp.has_flag( spell_flag::CONCENTRATE ) ) { + spell_data.emplace_back( _( "requires concentration" ) ); + } + if( sp.has_flag( spell_flag::VERBAL ) ) { + spell_data.emplace_back( _( "verbal" ) ); + } + if( sp.has_flag( spell_flag::SOMATIC ) ) { + spell_data.emplace_back( _( "somatic" ) ); + } + if( !sp.has_flag( spell_flag::NO_HANDS ) ) { + spell_data.emplace_back( _( "impeded by gloves" ) ); + } else { + spell_data.emplace_back( _( "does not require hands" ) ); + } + if( !sp.has_flag( spell_flag::NO_LEGS ) ) { + spell_data.emplace_back( _( "requires mobility" ) ); + } + if( sp.effect() == "target_attack" && sp.range() > 1 ) { + spell_data.emplace_back( _( "can be cast through walls" ) ); + } + return enumerate_as_string( spell_data ); +} + void spellcasting_callback::draw_spell_info( const spell &sp, const uilist *menu ) { const int h_offset = menu->w_width - menu->pad_right + 1; @@ -1058,11 +1154,19 @@ void spellcasting_callback::draw_spell_info( const spell &sp, const uilist *menu int line = 1; nc_color gray = c_light_gray; nc_color light_green = c_light_green; + nc_color yellow = c_yellow; + + print_colored_text( w_menu, line++, h_col1, yellow, yellow, + sp.spell_class() == trait_id( "NONE" ) ? _( "Classless" ) : sp.spell_class()->name() ); line += fold_and_print( w_menu, line, h_col1, info_width, gray, sp.description() ); line++; + line += fold_and_print( w_menu, line, h_col1, info_width, gray, enumerate_spell_data( sp ) ); + + line++; + print_colored_text( w_menu, line, h_col1, gray, gray, string_format( "%s: %d %s", _( "Spell Level" ), sp.get_level(), sp.is_max_level() ? _( "(MAX)" ) : "" ) ); @@ -1082,14 +1186,17 @@ void spellcasting_callback::draw_spell_info( const spell &sp, const uilist *menu line++; + const bool cost_encumb = energy_cost_encumbered( sp, g->u ); print_colored_text( w_menu, line++, h_col1, gray, gray, - string_format( "%s: %s %s%s", _( "Casting Cost" ), sp.energy_cost_string( g->u ), - sp.energy_string(), + string_format( "%s: %s %s%s", cost_encumb ? _( "Casting Cost (impeded)" ) : _( "Casting Cost" ), + sp.energy_cost_string( g->u ), sp.energy_string(), sp.energy_source() == hp_energy ? "" : string_format( " ( %s current )", sp.energy_cur_string( g->u ) ) ) ); - - print_colored_text( w_menu, line++, h_col1, gray, gray, - string_format( "%s: %s", _( "Casting Time" ), moves_to_string( sp.casting_time() ) ) ); + const bool c_t_encumb = casting_time_encumbered( sp, g->u ); + print_colored_text( w_menu, line++, h_col1, gray, gray, colorize( + string_format( "%s: %s", c_t_encumb ? _( "Casting Time (impeded)" ) : _( "Casting Time" ), + moves_to_string( sp.casting_time( g->u ) ) ), + c_t_encumb ? c_red : c_light_gray ) ); line++; @@ -1110,11 +1217,11 @@ void spellcasting_callback::draw_spell_info( const spell &sp, const uilist *menu // if it's any type of attack spell, the stats are normal. if( fx == "target_attack" || fx == "projectile_attack" || fx == "cone_attack" || fx == "line_attack" ) { - if( damage >= 0 ) { + if( damage > 0 ) { damage_string = string_format( "%s: %s %s", _( "Damage" ), colorize( to_string( damage ), sp.damage_type_color() ), colorize( sp.damage_type_string(), sp.damage_type_color() ) ); - } else { + } else if( damage < 0 ) { damage_string = string_format( "%s: %s", _( "Healing" ), colorize( "+" + to_string( -damage ), light_green ) ); } diff --git a/src/magic.h b/src/magic.h index 326e09d230e11..2de4bd3d36dd1 100644 --- a/src/magic.h +++ b/src/magic.h @@ -228,7 +228,7 @@ class spell // distance spell can be cast int range() const; // how much energy does the spell cost - int energy_cost() const; + int energy_cost( const player &p ) const; // how long does this spell's effect last int duration() const; time_duration duration_turns() const; @@ -237,7 +237,7 @@ class spell float spell_fail( const player &p ) const; std::string colorized_fail_percent( const player &p ) const; // how long does it take to cast the spell - int casting_time() const; + int casting_time( const player &p ) const; // can the player cast this spell? bool can_cast( const player &p ) const; @@ -254,6 +254,8 @@ class spell // get spell id (from type) spell_id id() const; + // get spell class (from type) + trait_id spell_class() const; // get spell effect string (from type) std::string effect() const; // get spell effect_str data diff --git a/src/magic_spell_effect.cpp b/src/magic_spell_effect.cpp index 84941e07c40cf..1218e2bc17878 100644 --- a/src/magic_spell_effect.cpp +++ b/src/magic_spell_effect.cpp @@ -319,7 +319,7 @@ static void damage_targets( const spell &sp, const std::set &targets ) } if( sp.damage() > 0 ) { cr->deal_projectile_attack( &g->u, atk, true ); - } else { + } else if( sp.damage() < 0 ) { sp.heal( target ); add_msg( m_good, _( "%s wounds are closing up!" ), cr->disp_name( true ) ); }