Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CR] Reign in the damage from items dropped from height, with math #72615

Merged
merged 4 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 0 additions & 4 deletions src/item.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,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" );
Expand Down Expand Up @@ -14600,9 +14599,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 );
}

Expand Down
6 changes: 0 additions & 6 deletions src/item.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,6 @@ template<>
struct enum_traits<iteminfo::flags> {
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 );
Expand Down Expand Up @@ -236,8 +232,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<item> get_safe_reference();
Expand Down
92 changes: 62 additions & 30 deletions src/map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2864,10 +2864,10 @@
// 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 ) {
Expand All @@ -2877,53 +2877,85 @@
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.

Check failure on line 2944 in src/map.cpp

View workflow job for this annotation

GitHub Actions / build (src)

Translator comment without a matching raw string [cata-translator-comments,-warnings-as-errors]
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<int>( 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<int>( 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<int>( 200 * i.resist( damage_bash, true ) / damage + 1 );
if( one_in( chance ) ) {
i.inc_damage();
Expand Down