From d7e779b913040e8e85114ab4c56c15df034c3919 Mon Sep 17 00:00:00 2001 From: Chaosvolt Date: Wed, 21 Feb 2024 19:17:27 -0600 Subject: [PATCH] feat(balance): rework how ranged bash data checks `destroy_threshold` (#4223) * feat(balance): rework how ranged bash data checks `destroy_threshold` * style(autofix.ci): automated formatting * commit for remote * style(autofix.ci): automated formatting * Update map.cpp * screm --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../furniture-barriers.json | 12 +++---- .../furniture_and_terrain/terrain-doors.json | 36 +++++++++---------- .../furniture_and_terrain/terrain-walls.json | 32 ++++++++--------- src/map.cpp | 25 +++++++++---- src/mapdata.h | 3 +- 5 files changed, 60 insertions(+), 48 deletions(-) diff --git a/data/json/furniture_and_terrain/furniture-barriers.json b/data/json/furniture_and_terrain/furniture-barriers.json index 382ca8dc94f0..0e36427472a0 100644 --- a/data/json/furniture_and_terrain/furniture-barriers.json +++ b/data/json/furniture_and_terrain/furniture-barriers.json @@ -52,7 +52,7 @@ "sound_fail": "whump.", "items": [ { "item": "bag_canvas", "count": [ 10, 16 ] }, { "item": "material_soil", "charges": [ 40, 48 ] } ], "//": "reduction and destroy_threshold equal to str_max of full-sized wall version, designed for ballistic protection", - "ranged": { "reduction": [ 80, 80 ], "destroy_threshold": 80, "block_unaimed_chance": "50%" } + "ranged": { "reduction": [ 100, 100 ], "destroy_threshold": 100, "block_unaimed_chance": "50%" } } }, { @@ -70,13 +70,13 @@ "deconstruct": { "items": [ { "item": "earthbag", "count": 20 } ], "furn_set": "f_earthbag_half" }, "bash": { "str_min": 24, - "str_max": 80, + "str_max": 100, "sound": "rrrip!", "sound_fail": "whump.", "furn_set": "f_earthbag_half", "items": [ { "item": "bag_canvas", "count": [ 15, 20 ] }, { "item": "material_soil", "charges": [ 50, 60 ] } ], "//": "reduction equal to str_max due to being designed for ballistic protection", - "ranged": { "reduction": [ 80, 80 ], "destroy_threshold": 80 } + "ranged": { "reduction": [ 100, 100 ], "destroy_threshold": 100 } } }, { @@ -127,7 +127,7 @@ "sound_fail": "whump.", "items": [ { "item": "bag_canvas", "count": [ 10, 16 ] }, { "item": "material_sand", "charges": [ 800, 960 ] } ], "//": "reduction and destroy_threshold equal to str_max of full-sized wall version, designed for ballistic protection", - "ranged": { "reduction": [ 80, 80 ], "destroy_threshold": 80, "block_unaimed_chance": "50%" } + "ranged": { "reduction": [ 100, 100 ], "destroy_threshold": 100, "block_unaimed_chance": "50%" } } }, { @@ -144,13 +144,13 @@ "deconstruct": { "items": [ { "item": "sandbag", "count": 20 } ], "furn_set": "f_sandbag_half" }, "bash": { "str_min": 24, - "str_max": 80, + "str_max": 100, "sound": "rrrip!", "sound_fail": "whump.", "furn_set": "f_sandbag_half", "items": [ { "item": "bag_canvas", "count": [ 15, 20 ] }, { "item": "material_sand", "charges": [ 1000, 1200 ] } ], "//": "reduction equal to str_max due to being designed for ballistic protection", - "ranged": { "reduction": [ 80, 80 ], "destroy_threshold": 80 } + "ranged": { "reduction": [ 100, 100 ], "destroy_threshold": 100 } } } ] diff --git a/data/json/furniture_and_terrain/terrain-doors.json b/data/json/furniture_and_terrain/terrain-doors.json index e2f3b6f8b006..07fbcce2b823 100644 --- a/data/json/furniture_and_terrain/terrain-doors.json +++ b/data/json/furniture_and_terrain/terrain-doors.json @@ -12,16 +12,16 @@ "flags": [ "TRANSPARENT", "DOOR", "NOITEM", "CONNECT_TO_WALL", "MINEABLE", "BLOCK_WIND" ], "open": "t_laminated_door_glass_o", "bash": { - "str_min": 100, - "str_max": 180, + "str_min": 60, + "str_max": 200, "sound": "glass breaking!", "sound_fail": "whack!", "sound_vol": 20, "sound_fail_vol": 14, "ter_set": "t_mdoor_frame", "items": [ { "item": "glass_shard", "count": [ 3, 6 ] }, { "item": "scrap", "count": [ 3, 5 ] } ], - "//": "half that of ballistic glass doors", - "ranged": { "reduction": [ 20, 20 ], "reduction_laser": [ 0, 5 ], "destroy_threshold": 20 } + "//": "reduction half str_min for ballistic-style glass, destroy_threshold equal to str_min for glass", + "ranged": { "reduction": [ 30, 30 ], "reduction_laser": [ 0, 5 ], "destroy_threshold": 60 } } }, { @@ -37,8 +37,8 @@ "flags": [ "TRANSPARENT", "FLAT", "CONNECT_TO_WALL", "ROAD", "MINEABLE" ], "close": "t_laminated_door_glass_c", "bash": { - "str_min": 100, - "str_max": 180, + "str_min": 60, + "str_max": 200, "sound": "glass breaking!", "sound_fail": "whack!", "sound_vol": 20, @@ -60,7 +60,7 @@ "flags": [ "TRANSPARENT", "DOOR", "NOITEM", "CONNECT_TO_WALL", "MINEABLE", "BLOCK_WIND" ], "open": "t_ballistic_door_glass_o", "bash": { - "str_min": 200, + "str_min": 100, "str_max": 400, "sound": "glass breaking!", "sound_fail": "whack!", @@ -68,8 +68,8 @@ "sound_fail_vol": 14, "ter_set": "t_mdoor_frame", "items": [ { "item": "glass_shard", "count": [ 3, 6 ] }, { "item": "scrap", "count": [ 3, 5 ] } ], - "//": "matches that of the other ballistic glass doors", - "ranged": { "reduction": [ 40, 40 ], "reduction_laser": [ 0, 5 ], "destroy_threshold": 40 } + "//": "reduction half str_min for ballistic-style glass, destroy_threshold equal to str_min for glass", + "ranged": { "reduction": [ 50, 50 ], "reduction_laser": [ 0, 5 ], "destroy_threshold": 100 } } }, { @@ -85,7 +85,7 @@ "flags": [ "TRANSPARENT", "FLAT", "CONNECT_TO_WALL", "ROAD", "MINEABLE" ], "close": "t_ballistic_door_glass_c", "bash": { - "str_min": 200, + "str_min": 100, "str_max": 400, "sound": "glass breaking!", "sound_fail": "whack!", @@ -108,7 +108,7 @@ "flags": [ "TRANSPARENT", "DOOR", "NOITEM", "CONNECT_TO_WALL", "MINEABLE", "BLOCK_WIND" ], "open": "t_reinforced_door_glass_o", "bash": { - "str_min": 40, + "str_min": 80, "str_max": 210, "sound": "glass breaking!", "sound_fail": "whack!", @@ -116,8 +116,8 @@ "sound_fail_vol": 14, "ter_set": "t_mdoor_frame", "items": [ { "item": "glass_shard", "count": [ 3, 6 ] }, { "item": "wire", "prob": 20 }, { "item": "scrap", "count": [ 3, 5 ] } ], - "//": "reduction and destroy_threshold both match str_min for ballistic/reinforced glass doors", - "ranged": { "reduction": [ 40, 40 ], "reduction_laser": [ 0, 8 ], "destroy_threshold": 40 } + "//": "reduction half str_min for ballistic-style glass, destroy_threshold equal to str_min for glass", + "ranged": { "reduction": [ 40, 40 ], "reduction_laser": [ 0, 8 ], "destroy_threshold": 80 } } }, { @@ -133,7 +133,7 @@ "flags": [ "TRANSPARENT", "DOOR", "NOITEM", "CONNECT_TO_WALL", "MINEABLE" ], "open": "t_reinforced_door_glass_lab_o", "bash": { - "str_min": 40, + "str_min": 80, "str_max": 210, "sound": "glass breaking!", "sound_fail": "whack!", @@ -141,8 +141,8 @@ "sound_fail_vol": 14, "ter_set": "t_thconc_floor", "items": [ { "item": "glass_shard", "count": [ 3, 6 ] }, { "item": "wire", "prob": 20 }, { "item": "scrap", "count": [ 3, 5 ] } ], - "//": "reduction and destroy_threshold both match str_min for ballistic/reinforced glass doors", - "ranged": { "reduction": [ 40, 40 ], "reduction_laser": [ 0, 8 ], "destroy_threshold": 40 } + "//": "reduction half str_min for ballistic-style glass, destroy_threshold equal to str_min for glass", + "ranged": { "reduction": [ 40, 40 ], "reduction_laser": [ 0, 8 ], "destroy_threshold": 80 } } }, { @@ -158,7 +158,7 @@ "flags": [ "TRANSPARENT", "FLAT", "CONNECT_TO_WALL", "ROAD", "MINEABLE" ], "close": "t_reinforced_door_glass_c", "bash": { - "str_min": 40, + "str_min": 80, "str_max": 210, "sound": "glass breaking!", "sound_fail": "whack!", @@ -181,7 +181,7 @@ "flags": [ "TRANSPARENT", "FLAT", "CONNECT_TO_WALL", "ROAD", "MINEABLE" ], "close": "t_reinforced_door_glass_lab_c", "bash": { - "str_min": 40, + "str_min": 80, "str_max": 210, "sound": "glass breaking!", "sound_fail": "whack!", diff --git a/data/json/furniture_and_terrain/terrain-walls.json b/data/json/furniture_and_terrain/terrain-walls.json index a651dd2492fc..a1322a643993 100644 --- a/data/json/furniture_and_terrain/terrain-walls.json +++ b/data/json/furniture_and_terrain/terrain-walls.json @@ -1062,16 +1062,16 @@ "roof": "t_flat_roof", "flags": [ "TRANSPARENT", "NOITEM", "WALL", "NO_SCENT", "AUTO_WALL_SYMBOL", "MINEABLE", "BLOCK_WIND" ], "bash": { - "str_min": 100, - "str_max": 180, + "str_min": 60, + "str_max": 200, "sound": "glass breaking!", "sound_fail": "whack!", "sound_vol": 20, "sound_fail_vol": 14, "ter_set": "t_floor", "items": [ { "item": "glass_shard", "count": [ 3, 6 ] } ], - "//": "half that of ballistic glass doors", - "ranged": { "reduction": [ 20, 20 ], "reduction_laser": [ 0, 5 ], "destroy_threshold": 20 } + "//": "reduction half str_min for ballistic-style glass, destroy_threshold equal to str_min for glass", + "ranged": { "reduction": [ 30, 30 ], "reduction_laser": [ 0, 5 ], "destroy_threshold": 60 } } }, { @@ -1087,7 +1087,7 @@ "roof": "t_flat_roof", "flags": [ "TRANSPARENT", "NOITEM", "WALL", "NO_SCENT", "AUTO_WALL_SYMBOL", "MINEABLE", "BLOCK_WIND" ], "bash": { - "str_min": 200, + "str_min": 100, "str_max": 400, "sound": "glass breaking!", "sound_fail": "whack!", @@ -1095,8 +1095,8 @@ "sound_fail_vol": 14, "ter_set": "t_floor", "items": [ { "item": "glass_shard", "count": [ 3, 6 ] } ], - "//": "matches that of ballistic glass doors", - "ranged": { "reduction": [ 40, 40 ], "reduction_laser": [ 0, 8 ], "destroy_threshold": 40 } + "//": "reduction half str_min for ballistic-style glass, destroy_threshold equal to str_min for glass", + "ranged": { "reduction": [ 50, 50 ], "reduction_laser": [ 0, 8 ], "destroy_threshold": 100 } } }, { @@ -1111,7 +1111,7 @@ "roof": "t_flat_roof", "flags": [ "TRANSPARENT", "NOITEM", "WALL", "NO_SCENT", "AUTO_WALL_SYMBOL", "MINEABLE", "BLOCK_WIND" ], "bash": { - "str_min": 40, + "str_min": 80, "str_max": 210, "sound": "glass breaking!", "sound_fail": "whack!", @@ -1119,8 +1119,8 @@ "sound_fail_vol": 14, "ter_set": "t_floor", "items": [ { "item": "glass_shard", "count": [ 3, 6 ] }, { "item": "wire", "prob": 20 } ], - "//": "matches that of ballistic glass doors", - "ranged": { "reduction": [ 40, 40 ], "reduction_laser": [ 0, 8 ], "destroy_threshold": 40 } + "//": "reduction half str_min for ballistic-style glass, destroy_threshold equal to str_min for glass", + "ranged": { "reduction": [ 40, 40 ], "reduction_laser": [ 0, 8 ], "destroy_threshold": 80 } } }, { @@ -1135,7 +1135,7 @@ "flags": [ "NOITEM", "WALL", "NO_SCENT", "AUTO_WALL_SYMBOL", "OPENCLOSE_INSIDE", "MINEABLE", "BLOCK_WIND" ], "open": "t_reinforced_glass_shutter_open", "bash": { - "str_min": 60, + "str_min": 80, "str_max": 210, "sound": "glass breaking!", "sound_fail": "whack!", @@ -1143,8 +1143,8 @@ "sound_fail_vol": 14, "ter_set": "t_floor", "items": [ { "item": "glass_shard", "count": [ 3, 6 ] }, { "item": "wire", "prob": 20 }, { "item": "scrap", "count": [ 3, 5 ] } ], - "//": "matches that of ballistic glass doors, increased laser protection since closed shutters", - "ranged": { "reduction": [ 40, 40 ], "reduction_laser": [ 1, 10 ], "destroy_threshold": 40 } + "//": "reduction half str_min for ballistic-style glass, destroy_threshold equal to str_min for glass", + "ranged": { "reduction": [ 40, 40 ], "reduction_laser": [ 1, 10 ], "destroy_threshold": 80 } } }, { @@ -1160,7 +1160,7 @@ "flags": [ "TRANSPARENT", "NOITEM", "WALL", "NO_SCENT", "AUTO_WALL_SYMBOL", "OPENCLOSE_INSIDE", "MINEABLE", "BLOCK_WIND" ], "close": "t_reinforced_glass_shutter", "bash": { - "str_min": 40, + "str_min": 80, "str_max": 210, "sound": "glass breaking!", "sound_fail": "whack!", @@ -1168,8 +1168,8 @@ "sound_fail_vol": 14, "ter_set": "t_floor", "items": [ { "item": "glass_shard", "count": [ 3, 6 ] }, { "item": "wire", "prob": 20 }, { "item": "scrap", "count": [ 3, 5 ] } ], - "//": "matches that of ballistic glass doors", - "ranged": { "reduction": [ 40, 40 ], "reduction_laser": [ 0, 8 ], "destroy_threshold": 40 } + "//": "reduction half str_min for ballistic-style glass, destroy_threshold equal to str_min for glass", + "ranged": { "reduction": [ 40, 40 ], "reduction_laser": [ 0, 8 ], "destroy_threshold": 80 } } }, { diff --git a/src/map.cpp b/src/map.cpp index db8656f008f6..396afe38867b 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -3807,9 +3807,12 @@ void map::crush( const tripoint &p ) void map::shoot( const tripoint &origin, const tripoint &p, projectile &proj, const bool hit_items ) { float initial_damage = 0.0; + float initial_arpen = 0.0; + float initial_armor_mult = 1.0; for( const damage_unit &dam : proj.impact ) { initial_damage += dam.amount * dam.damage_multiplier; - initial_damage += dam.res_pen; + initial_arpen += dam.res_pen; + initial_armor_mult *= dam.res_mult; } if( initial_damage < 0 ) { return; @@ -3839,13 +3842,17 @@ void map::shoot( const tripoint &origin, const tripoint &p, projectile &proj, co if( furn.bash.ranged ) { double range = rl_dist( origin, p ); const ranged_bash_info &rfi = *furn.bash.ranged; + float destroy_roll = dam * rng_float( 0.9, 1.1 ); if( !hit_items && ( !check( rfi.block_unaimed_chance ) || ( rfi.block_unaimed_chance < 100_pct && range <= 1 ) ) ) { // Nothing, it's a miss or we're shooting over nearby furniture } else if( rfi.reduction_laser && proj.has_effect( ammo_effect_LASER ) ) { - dam -= rng( rfi.reduction_laser->min, rfi.reduction_laser->max ); + dam -= std::max( ( rng( rfi.reduction_laser->min, + rfi.reduction_laser->max ) - initial_arpen ) * initial_armor_mult, 0.0f ); } else { - dam -= rng( rfi.reduction.min, rfi.reduction.max ); + // Roll damage reduction value, reduce result by arpen, multiply by any armor mult, then finally set to zero if negative result + dam -= std::max( ( rng( rfi.reduction.min, + rfi.reduction.max ) - initial_arpen ) * initial_armor_mult, 0.0f ); // Only print if we hit something we can see enemies through, so we know cover did its job if( get_avatar().sees( p ) && rfi.block_unaimed_chance < 100_pct ) { if( dam <= 0 ) { @@ -3854,7 +3861,7 @@ void map::shoot( const tripoint &origin, const tripoint &p, projectile &proj, co add_msg( _( "The shot hits the %s and punches through!" ), furnname( p ) ); } } - if( dam > rfi.destroy_threshold ) { + if( destroy_roll > rfi.destroy_threshold ) { bash_params params{0, false, true, hit_items, 1.0, false}; bash_furn_success( p, params ); } @@ -3865,13 +3872,17 @@ void map::shoot( const tripoint &origin, const tripoint &p, projectile &proj, co } else if( ter.bash.ranged ) { double range = rl_dist( origin, p ); const ranged_bash_info &ri = *ter.bash.ranged; + float destroy_roll = dam * rng_float( 0.9, 1.1 ); if( !hit_items && ( !check( ri.block_unaimed_chance ) || ( ri.block_unaimed_chance < 100_pct && range <= 1 ) ) ) { // Nothing, it's a miss or we're shooting over nearby terrain } else if( ri.reduction_laser && proj.has_effect( ammo_effect_LASER ) ) { - dam -= rng( ri.reduction_laser->min, ri.reduction_laser->max ); + dam -= std::max( ( rng( ri.reduction_laser->min, + ri.reduction_laser->max ) - initial_arpen ) * initial_armor_mult, 0.0f ); } else { - dam -= rng( ri.reduction.min, ri.reduction.max ); + // Roll damage reduction value, reduce result by arpen, multiply by any armor mult, then finally set to zero if negative result + dam -= std::max( ( rng( ri.reduction.min, + ri.reduction.max ) - initial_arpen ) * initial_armor_mult, 0.0f ); // Only print if we hit something we can see enemies through, so we know cover did its job if( get_avatar().sees( p ) && ri.block_unaimed_chance < 100_pct ) { if( dam <= 0 ) { @@ -3880,7 +3891,7 @@ void map::shoot( const tripoint &origin, const tripoint &p, projectile &proj, co add_msg( _( "The shot hits the %s and punches through!" ), tername( p ) ); } } - if( dam > ri.destroy_threshold ) { + if( destroy_roll > ri.destroy_threshold ) { bash_params params{0, false, true, hit_items, 1.0, false}; bash_ter_success( p, params ); } diff --git a/src/mapdata.h b/src/mapdata.h index ba8ee7878380..f8bdd50a8e6f 100644 --- a/src/mapdata.h +++ b/src/mapdata.h @@ -37,7 +37,8 @@ struct ranged_bash_info { numeric_interval reduction; // Damage reduction when shot. Rolled like rng(min, max). // As above, but for lasers. If set, lasers won't destroy us. std::optional> reduction_laser; - int destroy_threshold = 0; // If reduced dmg is still above this value, destroy us. + int destroy_threshold = + 0; // If dmg (times 0.9 to 1.1) before reduction is above this value, destroy us. bool flammable = false; // If true, getting hit with any heat damage creates a fire. units::probability block_unaimed_chance = 100_pct; // Chance to intercept projectiles not aimed at this tile