From e47683f55649e818df03f936833e050d4ac58272 Mon Sep 17 00:00:00 2001 From: RenechCDDA <84619419+RenechCDDA@users.noreply.github.com> Date: Mon, 20 May 2024 15:14:46 +0100 Subject: [PATCH] Merge pull request #72615 from RenechCDDA/dropping_safes_is_unsafe [CR] Reign in the damage from items dropped from height, with math --- src/item.cpp | 4 --- src/item.h | 6 ---- src/map.cpp | 92 +++++++++++++++++++++++++++++++++++----------------- 3 files changed, 62 insertions(+), 40 deletions(-) diff --git a/src/item.cpp b/src/item.cpp index 53d71b032bed7..375ef3048eb0c 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -202,7 +202,6 @@ static const quality_id qual_LIFT( "LIFT" ); static const skill_id skill_cooking( "cooking" ); static const skill_id skill_melee( "melee" ); static const skill_id skill_survival( "survival" ); -static const skill_id skill_throw( "throw" ); static const skill_id skill_weapon( "weapon" ); static const species_id species_ROBOT( "ROBOT" ); @@ -14478,9 +14477,6 @@ bool item::on_drop( const tripoint &pos, map &m ) avatar &player_character = get_avatar(); - // set variable storing information of character dropping item - dropped_char_stats.throwing = player_character.get_skill_level( skill_throw ); - return type->drop_action && type->drop_action.call( &player_character, *this, pos ); } diff --git a/src/item.h b/src/item.h index 8761fdca47b13..aca0035f6dcca 100644 --- a/src/item.h +++ b/src/item.h @@ -174,10 +174,6 @@ template<> struct enum_traits { static constexpr bool is_flag_enum = true; }; -// Currently used to store the only throwing stats of character dropping this item. Default is -1 -struct dropped_by_character_stats { - float throwing; -}; iteminfo vol_to_info( const std::string &type, const std::string &left, const units::volume &vol, int decimal_places = 2, bool lower_is_better = true ); @@ -226,8 +222,6 @@ class item : public visitable ~item() override; - struct dropped_by_character_stats dropped_char_stats = { -1.0f }; - /** Return a pointer-like type that's automatically invalidated if this * item is destroyed or assigned-to */ safe_reference get_safe_reference(); diff --git a/src/map.cpp b/src/map.cpp index 8ad1a140c45cf..3a20067d10028 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -2865,10 +2865,10 @@ void map::drop_items( const tripoint &p ) // rather than disappearing if it would be overloaded tripoint below( p ); - int height_fallen = 0; + int max_height_fallen = 0; while( !has_floor_or_water( below ) ) { below.z--; - height_fallen++; + max_height_fallen++; } if( below == p ) { @@ -2878,53 +2878,85 @@ void map::drop_items( const tripoint &p ) float damage_total = 0.0f; for( item &i : items ) { units::mass wt_dropped = i.weight(); - float item_density = i.get_base_material().density(); - float damage = 5 * to_kilogram( wt_dropped ) * height_fallen * item_density; + + if( max_height_fallen <= 0 ) { + debugmsg( "Tried to calculate damage for falling item, but item somehow fell less than one z-level!" ); + max_height_fallen = 1; + } + Creature *creature_below = get_creature_tracker().creature_at( below ); + double height_fallen = max_height_fallen; + if( creature_below ) { + // Discount most of the first z-level's falling distance, depending on how big the creature is + // Characters (player/NPC) are normally medium, which is 0.5 or standing about 1.2m tall. + // This is shorter than the average adult, but it's an *okay* approximation. + height_fallen -= occupied_tile_fraction( creature_below->get_size() ); + } + // in meters, assuming one z-level is ~2.5m. + const double distance_to_fall = height_fallen * 2.5; + + // in meters per second (squared). + const double gravity_acceleration_constant = 9.8; + + // in seconds. + double falling_time = sqrt( 2 * distance_to_fall / gravity_acceleration_constant ); + + // in meters per second. + double velocity_at_impact = gravity_acceleration_constant * falling_time; + + // in joules. + double impact_energy = to_kilogram( wt_dropped ) * std::pow( velocity_at_impact, 2.0 ) / 2.0; + + // in faces smashed (parts per hundred). + double damage = sqrt( impact_energy ); damage_total += damage; add_item_or_charges( below, i ); // Bash creature standing below - Creature *creature_below = get_creature_tracker().creature_at( below ); if( creature_below ) { // creature's dodge modifier float dodge_mod = creature_below->dodge_roll(); - // if item dropped by character their throwing skill modifier -1 if not dropped by a character - float throwing_mod = i.dropped_char_stats.throwing == -1.0f ? 0.0f : 5 * - i.dropped_char_stats.throwing; - // values calibrated so that %hit chance starts from 60% going up and down according to the two modifiers - float hit_mod = ( throwing_mod + 18 ) / ( dodge_mod + 15 ); + // Most of the threat comes from the projectile going very fast before it enters a creature's vertical FOV + // or e.g. it fell from so high up that there was no indication of something coming down before it (possibly) hits + float hit_mod = velocity_at_impact / ( dodge_mod + 5 ); int creature_hit_chance = rng( 0, 100 ); - creature_hit_chance /= hit_mod * occupied_tile_fraction( creature_below->get_size() ); + double avoid_chance = hit_mod * occupied_tile_fraction( creature_below->get_size() ); + // We use sqrt here because it trends back towards 1. Spectacularly low hit_mod will still have SOME chance to hit while + // hits of >1 avoid_chance are not always going to strike the head + creature_hit_chance /= sqrt( avoid_chance ); + bodypart_id hit_part; if( creature_hit_chance < 15 ) { - add_msg_if_player_sees( creature_below->pos(), _( "Falling %s hits %s in the head!" ), i.tname(), - creature_below->get_name() ); - creature_below->deal_damage( nullptr, bodypart_id( "head" ), damage_instance( damage_bash, - damage ) ); + hit_part = creature_below->get_random_body_part_of_type( body_part_type::type::head ); } else if( creature_hit_chance < 30 ) { - add_msg_if_player_sees( creature_below->pos(), _( "Falling %s hits %s in the torso!" ), i.tname(), - creature_below->get_name() ); - creature_below->deal_damage( nullptr, bodypart_id( "torso" ), damage_instance( damage_bash, - damage ) ); - } else if( creature_hit_chance < 65 ) { - add_msg_if_player_sees( creature_below->pos(), _( "Falling %s hits %s in the left arm!" ), - i.tname(), creature_below->get_name() ); - creature_below->deal_damage( nullptr, bodypart_id( "arm_l" ), damage_instance( damage_bash, - damage ) ); + hit_part = creature_below->get_random_body_part_of_type( body_part_type::type::torso ); + } else if( creature_hit_chance < 90 ) { + hit_part = creature_below->get_random_body_part_of_type( body_part_type::type::arm ); } else if( creature_hit_chance < 100 ) { - add_msg_if_player_sees( creature_below->pos(), _( "Falling %s hits %s in the right arm!" ), - i.tname(), creature_below->get_name() ); - creature_below->deal_damage( nullptr, bodypart_id( "arm_r" ), damage_instance( damage_bash, - damage ) ); + hit_part = creature_below->get_random_body_part_of_type( body_part_type::type::leg ); } else { - add_msg_if_player_sees( creature_below->pos(), _( "Falling %s misses the %s!" ), i.tname(), - creature_below->get_name() ); + add_msg_if_player_sees( creature_below->pos(), _( "Falling %1$s misses %2$s!" ), i.tname(), + creature_below->disp_name() ); + } + // Did we hit at all? Then run the message. + if( creature_hit_chance < 100 ) { + if( hit_part.is_valid() && !creature_below->is_monster() ) { + //~First positional argument: Item name. Second: Name of a person (e.g. "Jane") or player (e.g. "you"). Third: Body part name, accusative. + add_msg_if_player_sees( creature_below->pos(), _( + "Falling %1$s hits %2$s on the %3$s for %4$i damage!" ), + i.tname(), creature_below->disp_name(), hit_part->accusative, static_cast( damage ) ); + } else { + add_msg_if_player_sees( creature_below->pos(), _( "Falling %1$s hits %2$s for %3$i damage!" ), + i.tname(), creature_below->disp_name(), static_cast( damage ) ); + } + // FIXME: Hardcoded damage type! + creature_below->deal_damage( nullptr, hit_part, damage_instance( damage_bash, damage ) ); } } // Bash items at bottom since currently bash_items only bash glass items + // FIXME: Hardcoded damage type! int chance = static_cast( 200 * i.resist( damage_bash, true ) / damage + 1 ); if( one_in( chance ) ) { i.inc_damage();