diff --git a/data/json/player_activities.json b/data/json/player_activities.json index d8a41e495b2a3..e25270baae09a 100644 --- a/data/json/player_activities.json +++ b/data/json/player_activities.json @@ -419,6 +419,14 @@ "verb": "mining", "based_on": "speed" }, + { + "id": "ACT_ASSISTED_PULP", + "type": "activity_type", + "activity_level": "NO_EXERCISE", + "verb": "pulping", + "based_on": "speed", + "can_resume": false + }, { "id": "ACT_PULP", "type": "activity_type", diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index c4c0bf2d92e4f..e0db7bff80210 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -109,6 +109,7 @@ #include "vpart_range.h" static const activity_id ACT_AIM( "ACT_AIM" ); +static const activity_id ACT_ASSISTED_PULP( "ASSISTED_PULP" ); static const activity_id ACT_AUTODRIVE( "ACT_AUTODRIVE" ); static const activity_id ACT_BASH( "ACT_BASH" ); static const activity_id ACT_BIKERACK_RACKING( "ACT_BIKERACK_RACKING" ); @@ -8691,6 +8692,111 @@ std::unique_ptr wash_activity_actor::deserialize( JsonValue &jsi return actor.clone(); } +namespace io +{ +template<> +std::string enum_to_string( assisted_pulp_type type ) +{ + switch( type ) { + case assisted_pulp_type::SPELL: return "SPELL"; + default: + cata_fatal( "Invalid based_on_type in enum_to_string" ); + } +} +} // namespace io + +bool assisted_pulp_activity_actor::calculate_corpses_in_area( Character &you ) +{ + map &here = get_map(); + corpses = {}; + if( assist_type == assisted_pulp_type::SPELL ) { + const std::set area = spell_effect::spell_effect_area( (*sp), target, you ); + + for( const tripoint_bub_ms &potential_target : area ) { + if( !(*sp).is_valid_target( you, potential_target ) ) { + continue; + } + for( item &potential_corpse : here.i_at( potential_target ) ) { + if( potential_corpse.can_revive() ) { + corpses.insert( potential_corpse ); + } + } + } + } + return !corpses.empty(); +} + +void assisted_pulp_activity_actor::start( player_activity &act, Character &you ) +{ + // indefinitely long so activity won't end until we pulp all the corpses + // we then end the activity manually + act.moves_total = calendar::INDEFINITELY_LONG; + act.moves_left = calendar::INDEFINITELY_LONG; + + if( assist_type == assisted_pulp_type::SPELL) { + if( !calculate_corpses_in_area( you ) ) { // Immediately stop activity if no corpses to pulp + act.set_to_null(); + return; + } + } else { + debugmsg("%s tried assisted pulping without a valid assisted pulp type!", you.name ); + act.set_to_null(); + return; + } +} + +void assisted_pulp_activity_actor::do_turn( player_activity &act, Character &you ) +{ + + if( assist_type == assisted_pulp_type::SPELL ) { + you.cast_spell( (*sp), false, target ); + } + add_msg("assisted_pulp_activity_actor: spell=%s", (*sp).id().c_str() ); + + // If nothing to pulp, stop the activity. + // if( !calculate_corpses_in_area( you ) ) { + if( true ) { + act.moves_total = 0; + act.moves_left = 0; + } +} + +void assisted_pulp_activity_actor::finish( player_activity &act, Character &you ) +{ + act.moves_total = 0; + act.moves_left = 0; + + act.set_to_null(); +} + +void assisted_pulp_activity_actor::serialize( JsonOut &jsout ) const +{ + jsout.start_object(); + + jsout.member( "target", target ); + jsout.member( "assist_type", assist_type ); + // jsout.member( "corpses", corpses ); + // jsout.member( "num_corpses", num_corpses ); + // jsout.member( "sp", sp ); + + jsout.end_object(); +} + +std::unique_ptr assisted_pulp_activity_actor::deserialize( JsonValue &jsin ) +{ + assisted_pulp_activity_actor actor; + + JsonObject data = jsin.get_object(); + + data.read( "target", actor.target ); + data.read( "assist_type", actor.assist_type ); + // data.read( "corpses", actor.corpses ); + // data.read( "num_corpses", actor.num_corpses ); + // data.read( "sp", actor.sp ); + + return actor.clone(); +} + void pulp_activity_actor::start( player_activity &act, Character &you ) { // indefinitely long so activity won't end until we pulp all the corpses diff --git a/src/activity_actor_definitions.h b/src/activity_actor_definitions.h index bf0327887cb28..fa29f4461288f 100644 --- a/src/activity_actor_definitions.h +++ b/src/activity_actor_definitions.h @@ -2450,6 +2450,53 @@ class unload_loot_activity_actor : public activity_actor tripoint_abs_ms placement; }; +enum class assisted_pulp_type : int { SPELL }; +template<> +struct enum_traits { + static constexpr assisted_pulp_type last = assisted_pulp_type::SPELL; +}; + +namespace io +{ +template<> +std::string enum_to_string( assisted_pulp_type type ); +} // namespace io + +class assisted_pulp_activity_actor : public activity_actor +{ + public: + + assisted_pulp_activity_actor() {}; + assisted_pulp_activity_actor( const tripoint_bub_ms &target, spell *sp ) : assist_type( assisted_pulp_type::SPELL ), target( target ), sp( sp ) {} + const activity_id &get_type() const override { + static const activity_id ACT_ASSISTED_PULP( "ACT_ASSISTED_PULP" ); + return ACT_ASSISTED_PULP; + } + + void start( player_activity &act, Character &you ) override; + void do_turn( player_activity &act, Character &you ) override; + void finish( player_activity &, Character & ) override; + + std::unique_ptr clone() const override { + return std::make_unique( *this ); + } + + void serialize( JsonOut &jsout ) const override; + static std::unique_ptr deserialize( JsonValue &jsin ); + + private: + // recalculates the corpses set. Returns if there are any pulpable corpses left. + bool calculate_corpses_in_area( Character &you ); + + tripoint_bub_ms target; + assisted_pulp_type assist_type; + std::set corpses; + int num_corpses = 0; + + // spell pulping + spell *sp; +}; + class pulp_activity_actor : public activity_actor { public: diff --git a/src/magic.h b/src/magic.h index 8cc551b4ad33d..0dd1daf4905fb 100644 --- a/src/magic.h +++ b/src/magic.h @@ -819,6 +819,8 @@ struct override_parameters { ignore_walls = sp.has_flag( spell_flag::IGNORE_WALLS ); } }; +std::set spell_effect_area( const spell &sp, const tripoint_bub_ms &target, + const Creature &caster ); void short_range_teleport( const spell &sp, Creature &caster, const tripoint_bub_ms &target ); void pain_split( const spell &, Creature &, const tripoint_bub_ms & ); diff --git a/src/magic_spell_effect.cpp b/src/magic_spell_effect.cpp index 4c06d011dcb59..cdaca3c7cbe98 100644 --- a/src/magic_spell_effect.cpp +++ b/src/magic_spell_effect.cpp @@ -15,6 +15,7 @@ #include #include +#include "activity_actor_definitions.h" #include "avatar.h" #include "bodypart.h" #include "calendar.h" @@ -473,7 +474,7 @@ std::set calculate_spell_effect_area( const spell &sp, return targets; } -static std::set spell_effect_area( const spell &sp, const tripoint_bub_ms &target, +std::set spell_effect::spell_effect_area( const spell &sp, const tripoint_bub_ms &target, const Creature &caster ) { // calculate spell's effect area @@ -660,6 +661,65 @@ static void damage_targets( const spell &sp, Creature &caster, } } +// Attempts to pulp a corpse with a spell. Returns if, after processing, the corpse is still pulpable. +static bool pulp_corpse( item &corpse, int spell_bash_damage, tripoint_bub_ms pos, map &here ) +{ + if( spell_bash_damage <= 0 || !corpse.can_revive() ) { + return false; + } + + const mtype *corpse_mtype = corpse.get_mtype(); + if( corpse_mtype == nullptr ) { + debugmsg( string_format( "Tried to pulp not-a-corpse (id %s)", corpse.typeId().c_str() ) ); + return false; + } + int m_health = corpse_mtype->hp; + float proportion_to_damage = static_cast( spell_bash_damage ) / static_cast( m_health ); + // Item max HP is 4000. Not sure of a good non-magic number source for this. + int corpse_damage = proportion_to_damage * 4000; + add_msg("pulp_corpse: spell_damage=%d, corpse_damage=%d", spell_bash_damage, corpse_damage); + corpse.mod_damage( corpse_damage ); + + const int radius = 1 + std::min(2.0f, proportion_to_damage / 2); + const field_type_id type_blood = proportion_to_damage > 1 ? + corpse.get_mtype()->gibType() : + corpse.get_mtype()->bloodType(); + here.add_splash( type_blood, pos, radius, 2 ); + + + return corpse.can_revive(); +} + +static void spell_bash_area( const spell &sp, Creature &caster, const std::set area, const tripoint_bub_ms &epicenter, double damage_modifier = 1.0 ) +{ + ::map &here = get_map(); + for( const tripoint_bub_ms &potential_target : area ) { + if( !sp.is_valid_target( caster, potential_target ) ) { + continue; + } + int spell_bash_damage = sp.damage( caster ) * damage_modifier; + // the bash already makes noise, so no need for spell::make_sound() + here.bash( potential_target, spell_bash_damage, + sp.has_flag( spell_flag::SILENT ) ); + + bool unpulped_corpses_remaining = false; + for( item &potential_corpse : here.i_at( potential_target ) ) { + if( potential_corpse.can_revive() && pulp_corpse( potential_corpse, spell_bash_damage, potential_target, here )) { + add_msg("spell_bash_area: unpulped_corpses_remaining = true"); + unpulped_corpses_remaining = true; + } + } + // Add query here before starting activity + avatar *av = caster.as_avatar(); + if( av != nullptr && unpulped_corpses_remaining ) { + add_msg("spell_bash_area: starting activity"); + spell non_const_spell = av->magic->get_spell( sp.id() ); + add_msg("spell=%s", non_const_spell.id().c_str() ); + av->assign_activity( assisted_pulp_activity_actor( epicenter, &non_const_spell ) ); + } + } +} + void spell_effect::attack( const spell &sp, Creature &caster, const tripoint_bub_ms &epicenter ) { const std::set area = spell_effect_area( sp, epicenter, caster ); @@ -669,15 +729,7 @@ void spell_effect::attack( const spell &sp, Creature &caster, const tripoint_bub } const double bash_scaling = sp.bash_scaling( caster ); if( bash_scaling > 0 ) { - ::map &here = get_map(); - for( const tripoint_bub_ms &potential_target : area ) { - if( !sp.is_valid_target( caster, potential_target ) ) { - continue; - } - // the bash already makes noise, so no need for spell::make_sound() - here.bash( potential_target, sp.damage( caster ) * bash_scaling, - sp.has_flag( spell_flag::SILENT ) ); - } + spell_bash_area( sp, caster, area, epicenter, bash_scaling ); } } @@ -1762,15 +1814,7 @@ void spell_effect::mutate( const spell &sp, Creature &caster, const tripoint_bub void spell_effect::bash( const spell &sp, Creature &caster, const tripoint_bub_ms &target ) { - ::map &here = get_map(); - const std::set area = spell_effect_area( sp, target, caster ); - for( const tripoint_bub_ms &potential_target : area ) { - if( !sp.is_valid_target( caster, potential_target ) ) { - continue; - } - // the bash already makes noise, so no need for spell::make_sound() - here.bash( potential_target, sp.damage( caster ), sp.has_flag( spell_flag::SILENT ) ); - } + spell_bash_area( sp, caster, spell_effect_area( sp, target, caster ), target, 1.0 ); } void spell_effect::dash( const spell &sp, Creature &caster, const tripoint_bub_ms &target )