diff --git a/data/json/monsters/monster_flags.json b/data/json/monsters/monster_flags.json index 78ef451f30bec..b05cf92a5194e 100644 --- a/data/json/monsters/monster_flags.json +++ b/data/json/monsters/monster_flags.json @@ -643,5 +643,11 @@ "id": "TEEP_IMMUNE", "type": "monster_flag", "//": "Immune to telepathic damage/detection" + }, + { + "id": "PULP_PRYING", + "type": "monster_flag", + "//": "This monster has a great armor, and it is much harder to pulp it without a proper tool", + "//2": "Rule of thumb: if monster has something like a spine character wants to break, and it is covered by any armor, use this flag" } ] diff --git a/data/json/player_activities.json b/data/json/player_activities.json index 6877412b06047..36fe8e9f0b1c8 100644 --- a/data/json/player_activities.json +++ b/data/json/player_activities.json @@ -424,7 +424,7 @@ "type": "activity_type", "activity_level": "EXTRA_EXERCISE", "verb": "smashing", - "based_on": "neither", + "based_on": "speed", "can_resume": false }, { diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index d4aad1ad21d05..fb16ed3a470f3 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -254,6 +254,7 @@ static const proficiency_id proficiency_prof_lockpicking( "prof_lockpicking" ); static const proficiency_id proficiency_prof_lockpicking_expert( "prof_lockpicking_expert" ); static const proficiency_id proficiency_prof_safecracking( "prof_safecracking" ); +static const quality_id qual_BUTCHER( "BUTCHER" ); static const quality_id qual_CUT( "CUT" ); static const quality_id qual_HACK( "HACK" ); static const quality_id qual_LOCKPICK( "LOCKPICK" ); @@ -268,6 +269,7 @@ static const skill_id skill_fabrication( "fabrication" ); static const skill_id skill_gun( "gun" ); static const skill_id skill_mechanics( "mechanics" ); static const skill_id skill_survival( "survival" ); +static const skill_id skill_swimming( "swimming" ); static const skill_id skill_traps( "traps" ); static const species_id species_ZOMBIE( "ZOMBIE" ); @@ -8406,122 +8408,226 @@ std::unique_ptr wash_activity_actor::deserialize( JsonValue &jsi return actor.clone(); } -void pulp_activity_actor::start( player_activity &act, Character & ) +void 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; + you.recoil = MAX_RECOIL; + current_pos_iter = placement.begin(); + + double pulp_power_bash = 1; + mess_radius = 1; + + const std::pair pair_bash = you.get_best_weapon_by_damage_type( damage_bash ); + pulp_power_bash = pair_bash.first; + bash_tool = pair_bash.second.display_name(); + + if( pair_bash.second.has_flag( flag_MESSY ) ) { + mess_radius = 2; + } + + const double weight_factor = units::to_kilogram( you.get_weight() ) / 10; + const double athletic_factor = std::min( 6.0f, you.get_skill_level( skill_swimming ) + 1 ) * 3; + const double strength_factor = you.get_str() / 2; + // stomp deliver about 5000-13000 N of force, beginner boxer deal 2500 N of force in it's punch + // ballpark of 13 damage for untrained human vs 30+ for highly trained person of average weight + const double pulp_power_stomps = athletic_factor + weight_factor + strength_factor; + + // if no tool is bashy enough, use only stomps + // if stomps are too weak, use only bash + // if roughly same, use both + bash_factor = 0.0f; + if( pulp_power_bash < pulp_power_stomps * 0.5 ) { + bash_factor = pulp_power_stomps; + stomps_only = true; + } else if( pulp_power_stomps < pulp_power_bash * 0.5 ) { + bash_factor = pulp_power_bash; + weapon_only = true; + } else { + bash_factor = std::max( pulp_power_stomps, pulp_power_bash ); + } + + you.add_msg_if_player( m_info, + "bash weapon: %s, bash damage: %s, pulp_power_bash: %s, pulp_power_stomps: %s, final bash factor: %s, post bash factor: %s", + pair_bash.second.display_name().c_str(), pair_bash.first, pulp_power_bash, pulp_power_stomps, + bash_factor, bash_factor * bash_factor ); + + bash_factor = std::pow( bash_factor, 2 ); + const std::pair pair_cut; + if( you.max_quality( qual_BUTCHER ) > 5 ) { + can_severe_cutting = true; + const std::pair pair_cut = you.get_best_tool( qual_BUTCHER ); + cut_tool = pair_cut.second.display_name(); + } + + const std::pair pair_pry; + if( you.max_quality( qual_PRY ) > 0 ) { + can_pry_armor = true; + const std::pair pair_pry = you.get_best_tool( qual_PRY ); + pry_tool = pair_pry.second.display_name(); + } + + you.add_msg_if_player( m_bad, + "max butcher quality: %s, butcher tool display name: %s, butcher tool quality: %s; max prying quality: %s, prying tool display name: %s, prying tool quality: %s", + you.max_quality( qual_BUTCHER ), pair_cut.second.display_name(), pair_cut.first, + you.max_quality( qual_PRY ), pair_pry.second.display_name(), pair_pry.first ); + + + pulp_power = bash_factor + * ( std::sqrt( you.get_skill_level( skill_survival ) + 1 ) ) + * ( can_severe_cutting ? 1 : 0.85 ); + + pulp_effort = get_option( "PLAYER_BASE_STAMINA_REGEN_RATE" ) * 2.5; + } void pulp_activity_actor::do_turn( player_activity &act, Character &you ) { map &here = get_map(); - const item_location weapon = you.get_wielded_item(); - int weap_cut = 0; - int weap_stab = 0; - int weap_bash = 0; - int mess_radius = 1; + size_t map_stack_counter = 0; + tripoint_bub_ms pos = here.get_bub( *current_pos_iter ); + map_stack corpse_pile = here.i_at( pos ); + for( item &corpse : corpse_pile ) { + if( !corpse.is_corpse() || !corpse.can_revive() ) { + // Don't smash non-rezing corpses or random items + ++map_stack_counter; + continue; + } - if( weapon ) { - // FIXME: Hardcoded damage types - weap_cut = weapon->damage_melee( damage_cut ); - weap_stab = weapon->damage_melee( damage_stab ); - weap_bash = weapon->damage_melee( damage_bash ); - if( weapon->has_flag( flag_MESSY ) ) { - mess_radius = 2; + if( corpse.damage() < corpse.max_damage() ) { + bool can_pulp = punch_corpse_once( corpse, act, you, pos, here ); + if( !can_pulp ) { + ++unpulped_corpses_qty; + ++map_stack_counter; + continue; + } + break; } } - // Stabbing weapons are a lot less effective at pulping - const int cut_power = std::max( weap_cut, weap_stab / 2 ); + if( current_pos_iter == placement.end() ) { + // no more locations to pulp + act.moves_total = 0; + act.moves_left = 0; + } + if( corpse_pile.size() == map_stack_counter ) { + // smashed all possible corpses, moving to next tile + current_pos_iter++; + map_stack_counter = 0; + } +} - ///\EFFECT_STR increases pulping power, with diminishing returns - float pulp_power = std::sqrt( ( you.get_arm_str() + weap_bash ) * ( cut_power + 1.0f ) ); - float pulp_effort = you.str_cur + weap_bash; +bool pulp_activity_actor::punch_corpse_once( item &corpse, player_activity &act, Character &you, + tripoint_bub_ms pos, map &here ) +{ + const mtype *corpse_mtype = corpse.get_mtype(); - // Multiplier to get the chance right + some bonus for survival skill - pulp_power *= 40 + you.get_skill_level( skill_survival ) * 5; + // if something is so big, chances are it has hide and muscles strong enough + // that you can't stomp it without a better tool + // threshold is 30 bash for size 4, bash 45 for size 5 + if( corpse_mtype->size == 4 && bash_factor < std::pow( 33, 2 ) ) { + too_big_corpse = true; + return false; + } + if( corpse_mtype->size == 5 && bash_factor < std::pow( 45, 2 ) ) { + too_big_corpse = true; + return false; + } - int moves = 0; - for( auto pos_iter = placement.cbegin(); pos_iter != placement.end();/*left - out*/ ) { - const tripoint_bub_ms &pos = here.get_bub( *pos_iter ); - map_stack corpse_pile = here.i_at( pos ); - for( item &corpse : corpse_pile ) { - if( !corpse.is_corpse() || !corpse.can_revive() ) { - // Don't smash non-rezing corpses - continue; - } + int time_to_pulp = units::to_milliliter( corpse.volume() ) / pulp_power; - const mtype *corpse_mtype = corpse.get_mtype(); - const bool acid_immune = you.is_immune_damage( damage_acid ) || - you.is_immune_field( fd_acid ); - if( !pulp_acid && corpse_mtype->bloodType().obj().has_acid && !acid_immune ) { - //don't smash acid zombies when auto pulping unprotected - continue; + std::optional unknown_prof; + // +25% to pulp time if char knows no weakpoints of monster + // -25% if knows all of them + if( !corpse_mtype->families.families.empty() ) { + int wp_known = 0; + for( const weakpoint_family &wf : corpse_mtype->families.families ) { + if( you.has_proficiency( wf.proficiency ) ) { + ++wp_known; + } else if( !unknown_prof.has_value() ) { + unknown_prof = wf.proficiency; } - while( corpse.damage() < corpse.max_damage() ) { - // Increase damage as we keep smashing ensuring we eventually smash the target. - if( x_in_y( pulp_power, corpse.volume() / 250_ml ) ) { - corpse.inc_damage(); - if( corpse.damage() == corpse.max_damage() ) { - num_corpses++; - } - } + } + time_to_pulp /= wp_known / corpse_mtype->families.families.size() * 2 + 0.75; + } - if( x_in_y( pulp_power, corpse.volume() / 250_ml ) ) { - // Splatter some blood around - // Splatter a bit more randomly, so that it looks cooler - const int radius = mess_radius + x_in_y( pulp_power, 500 ) + x_in_y( pulp_power, 1000 ); - const tripoint_bub_ms dest( pos + point( rng( -radius, radius ), rng( -radius, radius ) ) ); - const field_type_id type_blood = ( mess_radius > 1 && x_in_y( pulp_power, 10000 ) ) ? - corpse.get_mtype()->gibType() : - corpse.get_mtype()->bloodType(); - here.add_splatter_trail( type_blood, pos, dest ); - } + const bool acid_immune = you.is_immune_damage( damage_acid ) || + you.is_immune_field( fd_acid ); + // this corpse is acid, and you are not immune to it + bool acid_corpse = corpse_mtype->bloodType().obj().has_acid && !acid_immune; - // mixture of isaac clarke stomps and swinging your weapon - you.burn_energy_all( -pulp_effort ); - you.recoil = MAX_RECOIL; + // 2.5 times longer if acid, to not get sprayed by accident + if( acid_corpse ) { + time_to_pulp *= 2.5; + } - if( one_in( 4 ) ) { - // Smashing may not be butchery, but it involves some zombie anatomy - you.practice( skill_survival, 2, 2 ); - } + // you have a hard time pulling armor to reach important parts of this monster - float stamina_ratio = static_cast( you.get_stamina() ) / you.get_stamina_max(); - moves += to_moves( 6_seconds ) / std::max( 0.25f, - stamina_ratio ) * you.exertion_adjusted_move_multiplier( act.exertion_level() ); - if( stamina_ratio < 0.33 || you.is_npc() ) { - you.set_moves( std::min( 0, you.get_moves() - moves ) ); - return; - } - if( moves >= you.get_moves() ) { - // Enough for this turn; - you.set_moves( you.get_moves() - moves ); - return; - } - } - corpse.set_flag( flag_PULPED ); + if( corpse_mtype->has_flag( mon_flag_PULP_PRYING ) ) { + if( can_pry_armor ) { + used_pry = true; + } else { + time_to_pulp *= 1.75; + couldnt_use_pry = true; } - //Upon reach here, we have cleared one maptile - pos_iter = placement.erase( pos_iter ); } - // If we reach this, all corpses have been pulped, finish the activity - act.moves_left = 0; - if( num_corpses == 0 ) { - you.add_msg_if_player( m_bad, _( "The corpse moved before you could finish smashing it!" ) ); - return; + add_msg_debug( debugmode::DF_ACTIVITY, + "Activity: %s, corpse: %s, time to pulp: %s, bash tool: %s, cut tool: %s, pry tool: %s", + act.id().str(), corpse_mtype->id.str(), to_string( time_duration::from_seconds( time_to_pulp ) ), + bash_tool, cut_tool, pry_tool ); + + // how much damage you need to deal each second to match expected pulp time + const int corpse_damage = 4000 / time_to_pulp; + + // Increase damage as we keep smashing ensuring we eventually smash the target. + // Slow down the speed if corpse is acid + corpse.mod_damage( corpse_damage ); + if( corpse.damage() == corpse.max_damage() ) { + ++num_corpses; + corpse.set_flag( flag_PULPED ); + } + + if( one_in( time_to_pulp / 20 + 1 ) ) { + // Splatter some blood around + // Splatter a bit more randomly, so that it looks cooler + const int radius = acid_corpse ? 0 + : mess_radius + x_in_y( pulp_power, 500 ) + x_in_y( pulp_power, 1000 ); + const tripoint_bub_ms dest( pos + point( rng( -radius, radius ), rng( -radius, radius ) ) ); + const field_type_id type_blood = ( mess_radius > 1 && x_in_y( pulp_power, 10000 ) ) ? + corpse.get_mtype()->gibType() : + corpse.get_mtype()->bloodType(); + here.add_splatter_trail( type_blood, pos, dest ); + } + + if( stomps_only ) { + you.burn_energy_legs( -pulp_effort ); + } else if( weapon_only ) { + you.burn_energy_arms( -pulp_effort ); + } else { + you.burn_energy_all( -pulp_effort ); + } + + if( one_in( 16 ) ) { + you.practice( skill_survival, 2, 2 ); + you.practice( skill_swimming, 2, 2 ); + if( unknown_prof.has_value() ) { + you.practice_proficiency( unknown_prof.value(), 2_seconds ); + } } - // TODO: Factor in how long it took to do the smashing. - you.add_msg_player_or_npc( n_gettext( "The corpse is thoroughly pulped.", - "The corpses are thoroughly pulped.", num_corpses ), - n_gettext( " finished pulping the corpse.", - " finished pulping the corpses.", num_corpses ) ); + + return true; } void pulp_activity_actor::finish( player_activity &act, Character &you ) { + act.moves_left = 0; + + send_final_message( you ); + if( you.is_npc() ) { you.as_npc()->revert_after_activity(); you.as_npc()->pulp_location.reset(); @@ -8530,13 +8636,88 @@ void pulp_activity_actor::finish( player_activity &act, Character &you ) } } +void pulp_activity_actor::send_final_message( Character &you ) const +{ + + if( too_big_corpse && num_corpses == 0 ) { + you.add_msg_player_or_npc( n_gettext( + _( "You cannot pulp this corpse, for you have no tool heavy enough to deal with it." ), + _( "You cannot pulp these corpses, for you have no tool heavy enough to deal with it." ), + unpulped_corpses_qty ), + n_gettext( + _( " cannot pulp this corpse without a heavier tool." ), + _( " cannot pulp these corpses without a heavier tool." ), + unpulped_corpses_qty ) ); + return; + } + + std::string tools; + if( stomps_only ) { + tools = string_format( "with nothing but your %s", + you.get_random_body_part_of_type( body_part_type::type::leg )->accusative_multiple ); + } else if( weapon_only ) { + tools = string_format( "with your %s", bash_tool ); + } else { + tools = string_format( "mixing your %s with powerful stomps", bash_tool ); + } + if( can_severe_cutting ) { + tools += string_format( " and a trusty %s", cut_tool ); + } + + std::string skill; + if( you.get_skill_level( skill_survival ) < 2 ) { + skill = string_format( "even if your moves still lack the experience" ); + if( you.get_skill_level( skill_swimming ) < 2 ) { + skill += " and your body is not fit"; + } + } else if( you.get_skill_level( skill_swimming ) < 2 ) { + skill = "even if your body is not fit"; + } else { + skill = "with fast and strong moves"; + } + + std::string pry; + if( pry_tool == bash_tool ) { + pry = "."; + } else if( used_pry ) { + pry = string_format( ". You also were lucky to carry %, it helped a lot with more armoured bodies.", + pry_tool ); + } else if( couldnt_use_pry ) { + pry = string_format( ". You wish you had anything like a crowbar, you had a really hard time without it." ); + } else { + pry = "."; + } + + you.add_msg_player_or_npc( + n_gettext( "You finished pulping the corpse %1$s, %2$s%3$s", + "You finished pulping the corpses %1$s, %2$s%3$s", unpulped_corpses_qty ), + n_gettext( " finished pulping the corpse.", + " finished pulping the corpses.", unpulped_corpses_qty ), + tools, skill, pry + ); + + if( too_big_corpse ) { + you.add_msg_player_or_npc( + n_gettext( "You left one corpse unpulped, for you have no tool heavy enough to deal with it.", + "You left some corpses unpulped, for you have no tool heavy enough to deal with it.", + unpulped_corpses_qty ), + n_gettext( " left one corpse unpulped, for %1$s has no tool heavy enough to deal with it.", + " left some corpses unpulped, for %1$s has no tool heavy enough to deal with it.", + unpulped_corpses_qty ), + you.male ? pgettext( "npc", "he" ) : pgettext( "npc", "she" ) + ); + } +} + void pulp_activity_actor::serialize( JsonOut &jsout ) const { jsout.start_object(); jsout.member( "num_corpses", num_corpses ); jsout.member( "placement", placement ); - jsout.member( "pulp_acid", pulp_acid ); + jsout.member( "pulp_power", pulp_power ); + jsout.member( "pulp_effort", pulp_effort ); + jsout.member( "mess_radius", mess_radius ); jsout.end_object(); } @@ -8549,7 +8730,9 @@ std::unique_ptr pulp_activity_actor::deserialize( JsonValue &jsi data.read( "num_corpses", actor.num_corpses ); data.read( "placement", actor.placement ); - data.read( "pulp_acid", actor.pulp_acid ); + data.read( "pulp_power", actor.pulp_power ); + data.read( "pulp_effort", actor.pulp_effort ); + data.read( "mess_radius", actor.mess_radius ); return actor.clone(); } diff --git a/src/activity_actor_definitions.h b/src/activity_actor_definitions.h index 9bf14638e8cc7..b4b64b8ecc9de 100644 --- a/src/activity_actor_definitions.h +++ b/src/activity_actor_definitions.h @@ -2407,11 +2407,10 @@ class pulp_activity_actor : public activity_actor { public: pulp_activity_actor() = default; - explicit pulp_activity_actor( const tripoint_abs_ms placement, - const bool pulp_acid = false ) : placement( { placement } ), - num_corpses( 0 ), pulp_acid( pulp_acid ) {} - explicit pulp_activity_actor( const std::set &placement, - const bool pulp_acid = false ) : placement( placement ), num_corpses( 0 ), pulp_acid( pulp_acid ) {} + explicit pulp_activity_actor( const tripoint_abs_ms placement ) : placement( { placement } ), + num_corpses( 0 ), pulp_power( 0 ), pulp_effort( 0 ), mess_radius( 1 ) {} + explicit pulp_activity_actor( const std::set &placement ) : placement( placement ), + num_corpses( 0 ), pulp_power( 0 ), pulp_effort( 0 ), mess_radius( 1 ) {} const activity_id &get_type() const override { static const activity_id ACT_PULP( "ACT_PULP" ); return ACT_PULP; @@ -2419,24 +2418,52 @@ class pulp_activity_actor : public activity_actor void start( player_activity &act, Character &who ) override; void do_turn( player_activity &act, Character &you ) override; + bool punch_corpse_once( item &corpse, player_activity &act, Character &you, tripoint_bub_ms pos, + map &here ); + // void canceled( player_activity &act, Character &p ) override; void finish( player_activity &, Character & ) override; + void send_final_message( Character &you ) const; + std::unique_ptr clone() const override { - return std::make_unique( *this ); + auto plp = std::make_unique( *this ); + plp.get()->current_pos_iter = plp.get()->placement.begin(); + return plp; } void serialize( JsonOut &jsout ) const override; static std::unique_ptr deserialize( JsonValue &jsin ); private: - bool can_resume_with_internal( const activity_actor &other, - const Character &/*who*/ ) const override { - const pulp_activity_actor &actor = static_cast( other ); - return actor.pulp_acid == pulp_acid; - } + // tripoints with corpses we need to pulp; + // either single tripoint shoved into set, or 3x3 zone around u/npc std::set placement; + + int unpulped_corpses_qty = 0; + // for tanky monsters + bool can_pry_armor = false; + + double bash_factor; + // bools for end message + bool stomps_only = false; + bool weapon_only = false; + bool can_severe_cutting = false; + bool used_pry = false; + bool couldnt_use_pry = false; + bool too_big_corpse = false; + std::string bash_tool; + std::string cut_tool; + std::string pry_tool; + // how many corpses we pulped int num_corpses; - bool pulp_acid; + // how much damage you deal to corpse every second, average of multiple values + float pulp_power; + // how much stamina is consumed after each punch + float pulp_effort; + // how far the splatter goes + int mess_radius; + // what `placement` we are currently at + std::set::const_iterator current_pos_iter; }; class wait_stamina_activity_actor : public activity_actor diff --git a/src/character.cpp b/src/character.cpp index 3eea6a98cfcc5..a211e8198071b 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -9681,6 +9681,44 @@ bool Character::use_charges_if_avail( const itype_id &it, int quantity ) return false; } +std::pair Character::get_best_weapon_by_damage_type( const damage_type_id dmg_type ) +const +{ + std::pair best_weapon = std::make_pair( 0, item() ); + + cache_visit_items_with( "best_damage", &item::is_melee, [&best_weapon, this, + dmg_type]( const item & it ) { + damage_instance di; + roll_damage( dmg_type, false, di, true, it, attack_vector_id::NULL_ID(), + sub_bodypart_str_id::NULL_ID(), 1.f ); + for( damage_unit du : di.damage_units ) { + if( du.type == dmg_type && best_weapon.first < du.amount ) { + best_weapon = std::make_pair( du.amount, it ); + } + } + } ); + + return best_weapon; +} + +std::pair Character::get_best_tool( const quality_id quality ) const +{ + // todo: check edge case of you having bionic tool/mutation, + // something that max_quality() is aware but max_quality() is not? + std::pair best_tool = std::make_pair( 0, item() ); + + cache_visit_items_with( "best_quality", &item::is_tool, [&best_tool, this, + quality]( const item & it ) { + int max_qlty = max_quality( quality ); + if( it.get_quality( quality ) == max_qlty ) { + best_tool = std::make_pair( max_qlty, it ); + return; + } + } ); + + return best_tool; +} + units::energy Character::available_ups() const { units::energy available_charges = 0_kJ; diff --git a/src/character.h b/src/character.h index ebb334b08c925..ef9d5fdaa137a 100644 --- a/src/character.h +++ b/src/character.h @@ -2968,6 +2968,10 @@ class Character : public Creature, public visitable // Uses up charges bool use_charges_if_avail( const itype_id &it, int quantity ); + std::pair get_best_weapon_by_damage_type( const damage_type_id dmg_type ) const; + + std::pair get_best_tool( const quality_id quality ) const; + /** * Available ups from all sources * Sum of mech, bionic UPS and UPS diff --git a/src/handle_action.cpp b/src/handle_action.cpp index c01e40d83ac7b..77fb0f9d5741a 100644 --- a/src/handle_action.cpp +++ b/src/handle_action.cpp @@ -903,12 +903,6 @@ static void haul_toggle() get_avatar().toggle_hauling(); } -static bool is_smashable_corpse( const item &maybe_corpse ) -{ - return maybe_corpse.is_corpse() && maybe_corpse.damage() < maybe_corpse.max_damage() && - maybe_corpse.can_revive(); -} - static void smash() { const bool allow_floor_bash = debug_mode; // Should later become "true" @@ -923,7 +917,7 @@ static void smash() // terrain smashing unless it's actually possible. bool smashable_corpse_at_target = false; for( const item &maybe_corpse : get_map().i_at( smashp ) ) { - if( is_smashable_corpse( maybe_corpse ) ) { + if( maybe_corpse.can_revive() ) { smashable_corpse_at_target = true; break; } @@ -1022,19 +1016,13 @@ avatar::smash_result avatar::smash( tripoint_bub_ms &smashp ) bool should_pulp = false; for( const item &maybe_corpse : here.i_at( smashp ) ) { - if( is_smashable_corpse( maybe_corpse ) ) { - if( maybe_corpse.get_mtype()->bloodType()->has_acid && - !is_immune_field( fd_acid ) ) { - if( !query_yn( _( "Are you sure you want to pulp an acid filled corpse?" ) ) ) { - return ret; // Player doesn't want an acid bath - } - } + if( maybe_corpse.can_revive() ) { should_pulp = true; // There is at least one corpse to pulp } } if( should_pulp ) { - assign_activity( pulp_activity_actor( here.get_abs( smashp ), true ) ); + assign_activity( pulp_activity_actor( here.get_abs( smashp ) ) ); return ret; // don't smash terrain if we've smashed a corpse } diff --git a/src/mtype.cpp b/src/mtype.cpp index db1c7ed1e39d6..e7663f12a2a48 100644 --- a/src/mtype.cpp +++ b/src/mtype.cpp @@ -131,6 +131,7 @@ mon_flag_id mon_flag_ACIDPROOF, mon_flag_PLASTIC, mon_flag_POISON, mon_flag_PRIORITIZE_TARGETS, + mon_flag_PULP_PRYING, mon_flag_PUSH_MON, mon_flag_PUSH_VEH, mon_flag_QUEEN, @@ -257,6 +258,7 @@ void set_mon_flag_ids() mon_flag_PLASTIC = mon_flag_id( "PLASTIC" ); mon_flag_POISON = mon_flag_id( "POISON" ); mon_flag_PRIORITIZE_TARGETS = mon_flag_id( "PRIORITIZE_TARGETS" ); + mon_flag_PULP_PRYING = mon_flag_id( "PULP_PRYING" ); mon_flag_PUSH_MON = mon_flag_id( "PUSH_MON" ); mon_flag_PUSH_VEH = mon_flag_id( "PUSH_VEH" ); mon_flag_QUEEN = mon_flag_id( "QUEEN" ); diff --git a/src/mtype.h b/src/mtype.h index 6650d190de80d..43ccbfd894f73 100644 --- a/src/mtype.h +++ b/src/mtype.h @@ -171,6 +171,7 @@ extern mon_flag_id mon_flag_ACIDPROOF, mon_flag_PLASTIC, mon_flag_POISON, mon_flag_PRIORITIZE_TARGETS, + mon_flag_PULP_PRYING, mon_flag_PUSH_MON, mon_flag_PUSH_VEH, mon_flag_QUEEN, diff --git a/src/npcmove.cpp b/src/npcmove.cpp index df94e70c35594..abdb46c9f05e3 100644 --- a/src/npcmove.cpp +++ b/src/npcmove.cpp @@ -147,6 +147,7 @@ static const itype_id itype_thorazine( "thorazine" ); static const npc_class_id NC_EVAC_SHOPKEEP( "NC_EVAC_SHOPKEEP" ); static const skill_id skill_firstaid( "firstaid" ); +static const skill_id skill_swimming( "swimming" ); static const trait_id trait_IGNORE_SOUND( "IGNORE_SOUND" ); static const trait_id trait_RETURN_TO_START_POS( "RETURN_TO_START_POS" ); @@ -3917,10 +3918,25 @@ bool npc::find_corpse_to_pulp() // Pulp only stuff that revives, but don't pulp acid stuff // That is, if you aren't protected from this stuff! if( it.can_revive() ) { - // If the first encountered corpse bleeds something dangerous then - // it is not safe to bash. - if( is_dangerous_field( field_entry( it.get_mtype()->bloodType(), 1, 0_turns ) ) ) { - return nullptr; + + const mtype &corpse = *it.get_corpse_mon(); + if( corpse.size > 3 ) { + + // stripped copy of calculations from pulp_activity_actor::start() + std::pair pair = get_best_weapon_by_damage_type( damage_bash ); + + const double weight_factor = units::to_kilogram( get_weight() ) / 10; + const double athletic_factor = std::min( 6.0f, get_skill_level( skill_swimming ) + 1 ) * 3; + const double strength_factor = get_str() / 2; + const float pulp_power_stomps = athletic_factor + weight_factor + strength_factor; + const double bash_factor = std::max( pulp_power_stomps, pair.first ); + + if( corpse.size == 4 && bash_factor < 33 ) { + continue; + } + if( corpse.size == 5 && bash_factor < 45 ) { + continue; + } } found = ⁢