From b36311ddbf8dfff6fb6e175635f18a1465768a93 Mon Sep 17 00:00:00 2001 From: anoobindisguise <56016372+anoobindisguise@users.noreply.github.com> Date: Fri, 23 Jun 2023 05:46:37 -0700 Subject: [PATCH] Guns need more skill, performing much worse at low skills (#66347) * increase penalties to dispersion at low skills. * make skill effect on base_aim_speed_cap frontloaded * Update src/character.cpp Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * make aiming a combo of limb scores * Low marksman skill makes it harder to reorient your aim * add a final divisor to aim speed based on skill * Update src/character.cpp Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * attempt #1 to fix this test * test fix * more test fixes * more fixes * Update ranged_balance_test.cpp * Update ranged_balance_test.cpp --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- data/json/character_modifiers.json | 4 +-- src/character.cpp | 6 +++- src/ranged.cpp | 9 +++-- tests/iteminfo_test.cpp | 14 ++++---- tests/ranged_balance_test.cpp | 54 +++++++++++++++--------------- 5 files changed, 47 insertions(+), 40 deletions(-) diff --git a/data/json/character_modifiers.json b/data/json/character_modifiers.json index b1a2df613bdc4..c6565f1f0e0a9 100644 --- a/data/json/character_modifiers.json +++ b/data/json/character_modifiers.json @@ -16,9 +16,9 @@ { "type": "character_mod", "id": "aim_speed_mod", - "description": "Gun aim speed modifier (Manipulation)", + "description": "Gun aim speed modifier (Manipulation, Grip, Lift) ", "mod_type": "x", - "value": { "limb_score": "manip", "limb_type": "hand" } + "value": { "limb_score": [ [ "grip", 0.2 ], [ "manip", 0.2 ], [ "lift", 0.6 ] ], "limb_score_op": "+", "min": 0.1, "max": 1.0 } }, { "type": "character_mod", diff --git a/src/character.cpp b/src/character.cpp index e47e8b607e2c0..3273f42000490 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -1058,10 +1058,14 @@ double Character::aim_per_move( const item &gun, double recoil, aim_speed *= get_modifier( character_modifier_aim_speed_mod ); + // finally multiply everything by a harsh function that is eliminated by 7.5 gunskill + aim_speed /= std::max( 1.0, 2.5 - 0.2 * get_skill_level( gun_skill ) ); // Use a milder attenuation function to replace the previous logarithmic attenuation function when recoil is closed to 0. aim_speed *= std::max( recoil / MAX_RECOIL, 1 - logarithmic_range( 0, MAX_RECOIL, recoil ) ); - double base_aim_speed_cap = 20 + 1.0 * get_skill_level( gun_skill ); + // add 4 max aim speed per skill up to 5 skill, then 1 per skill for skill 5-10 + double base_aim_speed_cap = 5.0 + 1.0 * get_skill_level( gun_skill ) + std::max( 10.0, + 3.0 * get_skill_level( gun_skill ) ); // This upper limit usually only affects the first half of the aiming process // Pistols have a much higher aiming speed limit diff --git a/src/ranged.cpp b/src/ranged.cpp index 12d64754b37b7..5612940263ab9 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -2155,7 +2155,7 @@ static double dispersion_from_skill( double skill, double weapon_dispersion ) return 0.0; } double skill_shortfall = static_cast( MAX_SKILL ) - skill; - double dispersion_penalty = 3 * skill_shortfall; + double dispersion_penalty = 10 * skill_shortfall; double skill_threshold = 5; if( skill >= skill_threshold ) { double post_threshold_skill_shortfall = static_cast( MAX_SKILL ) - skill; @@ -2166,7 +2166,7 @@ static double dispersion_from_skill( double skill, double weapon_dispersion ) // Unskilled shooters suffer greater penalties, still scaling with weapon penalties. double pre_threshold_skill_shortfall = skill_threshold - skill; dispersion_penalty += weapon_dispersion * - ( 1.25 + pre_threshold_skill_shortfall * 3.75 / skill_threshold ); + ( 1.25 + pre_threshold_skill_shortfall * 10.0 / skill_threshold ); return dispersion_penalty; } @@ -3161,7 +3161,10 @@ void target_ui::recalc_aim_turning_penalty() } else { // Raise it proportionally to how much // the player has to turn from previous aiming point - const double recoil_per_degree = MAX_RECOIL / 180.0; + // the player loses their aim more quickly if less skilled, normalizing at 5 skill. + /** @EFFECT_GUN increases the penalty for reorienting aim while below 5 */ + const double skill_penalty = std::max( 0.0, 5.0 - you->get_skill_level( skill_gun ) ); + const double recoil_per_degree = skill_penalty * MAX_RECOIL / 180.0; const units::angle angle_curr = coord_to_angle( src, curr_recoil_pos ); const units::angle angle_desired = coord_to_angle( src, dst ); const units::angle phi = normalize( angle_curr - angle_desired ); diff --git a/tests/iteminfo_test.cpp b/tests/iteminfo_test.cpp index 998a6db4ae01e..d01d692abc427 100644 --- a/tests/iteminfo_test.cpp +++ b/tests/iteminfo_test.cpp @@ -1590,16 +1590,16 @@ TEST_CASE( "gun_or_other_ranged_weapon_attributes", "[iteminfo][weapon][gun]" ) std::vector aim_stats = { iteminfo_parts::GUN_AIMING_STATS }; CHECK( item_info_str( glock, aim_stats ) == "--\n" - "Base aim speed: 48\n" + "Base aim speed: 29\n" "Regular\n" - "Even chance of good hit at range: 3\n" - "Time to reach aim level: 99 moves\n" + "Even chance of good hit at range: 2\n" + "Time to reach aim level: 233 moves\n" "Careful\n" - "Even chance of good hit at range: 6\n" - "Time to reach aim level: 165 moves\n" + "Even chance of good hit at range: 3\n" + "Time to reach aim level: 399 moves\n" "Precise\n" - "Even chance of good hit at range: 8\n" - "Time to reach aim level: 263 moves\n" ); + "Even chance of good hit at range: 4\n" + "Time to reach aim level: 645 moves\n" ); } SECTION( "compatible magazines" ) { diff --git a/tests/ranged_balance_test.cpp b/tests/ranged_balance_test.cpp index 1a8fb302e20b9..0eef59d0c05d0 100644 --- a/tests/ranged_balance_test.cpp +++ b/tests/ranged_balance_test.cpp @@ -170,7 +170,7 @@ static void test_shooting_scenario( npc &shooter, const int min_quickdraw_range, CAPTURE( shooter.get_modifier( character_modifier_ranged_dispersion_manip_mod ) ); CAPTURE( good_stats.n() ); CAPTURE( good_stats.margin_of_error() ); - CHECK( good_stats.avg() > 0.5 ); + CHECK( good_stats.avg() > 0.0 ); } { const dispersion_sources dispersion = get_dispersion( shooter, 500, max_good_range ); @@ -190,7 +190,7 @@ static void test_shooting_scenario( npc &shooter, const int min_quickdraw_range, static void test_fast_shooting( npc &shooter, const int moves, float hit_rate ) { const int fast_shooting_range = 3; - const float hit_rate_cap = hit_rate + 0.3f; + const float hit_rate_cap = hit_rate + 0.5f; const dispersion_sources dispersion = get_dispersion( shooter, moves, fast_shooting_range ); firing_statistics fast_stats = firing_test( dispersion, fast_shooting_range, Threshold( accuracy_standard, hit_rate ) ); @@ -210,7 +210,7 @@ static void test_fast_shooting( npc &shooter, const int moves, float hit_rate ) CHECK( fast_stats.avg() > hit_rate ); CAPTURE( fast_stats_upper.n() ); CAPTURE( fast_stats_upper.margin_of_error() ); - CHECK( fast_stats_upper.avg() < hit_rate_cap ); + CHECK( fast_stats_upper.avg() <= hit_rate_cap ); } static void assert_encumbrance( npc &shooter, int encumbrance ) @@ -249,32 +249,32 @@ TEST_CASE( "unskilled_shooter_accuracy", "[ranged] [balance] [slow]" ) SECTION( "an unskilled shooter with a common pistol" ) { arm_shooter( shooter, "glock_19" ); test_shooting_scenario( shooter, 4, 5, 17 ); - test_fast_shooting( shooter, 60, 0.3 ); + test_fast_shooting( shooter, 60, 0.15 ); } SECTION( "an unskilled archer with a common bow" ) { arm_shooter( shooter, "shortbow", { "bow_sight_pin" }, "arrow_field_point_fletched" ); test_shooting_scenario( shooter, 4, 4, 13 ); - test_fast_shooting( shooter, 90, 0.3 ); + test_fast_shooting( shooter, 90, 0.1 ); } SECTION( "an unskilled archer with a common crossbow" ) { arm_shooter( shooter, "crossbow", {}, "bolt_makeshift" ); test_shooting_scenario( shooter, 4, 5, 17 ); - test_fast_shooting( shooter, 80, 0.3 ); + test_fast_shooting( shooter, 80, 0.15 ); } SECTION( "an unskilled shooter with a common shotgun" ) { arm_shooter( shooter, "remington_870" ); test_shooting_scenario( shooter, 4, 4, 19 ); - test_fast_shooting( shooter, 80, 0.3 ); + test_fast_shooting( shooter, 80, 0.15 ); } SECTION( "an unskilled shooter with a common smg" ) { arm_shooter( shooter, "mp40semi" ); test_shooting_scenario( shooter, 4, 5, 18 ); - test_fast_shooting( shooter, 80, 0.3 ); + test_fast_shooting( shooter, 80, 0.15 ); } SECTION( "an unskilled shooter with a common rifle" ) { arm_shooter( shooter, "ar15" ); test_shooting_scenario( shooter, 5, 5, 25 ); - test_fast_shooting( shooter, 100, 0.3 ); + test_fast_shooting( shooter, 100, 0.15 ); } } @@ -290,32 +290,32 @@ TEST_CASE( "competent_shooter_accuracy", "[ranged] [balance]" ) SECTION( "a skilled shooter with an accurate pistol" ) { arm_shooter( shooter, "sw_619", { "red_dot_sight" } ); test_shooting_scenario( shooter, 10, 12, 35 ); - test_fast_shooting( shooter, 40, 0.4 ); + test_fast_shooting( shooter, 40, 0.35 ); } SECTION( "a skilled archer with an accurate bow" ) { arm_shooter( shooter, "recurbow", { "bow_sight" } ); test_shooting_scenario( shooter, 8, 10, 35 ); - test_fast_shooting( shooter, 70, 0.4 ); + test_fast_shooting( shooter, 70, 0.35 ); } SECTION( "a skilled archer with an accurate crossbow" ) { arm_shooter( shooter, "compositecrossbow", { "tele_sight" }, "bolt_steel" ); test_shooting_scenario( shooter, 9, 10, 35 ); - test_fast_shooting( shooter, 70, 0.4 ); + test_fast_shooting( shooter, 70, 0.35 ); } SECTION( "a skilled shooter with a nice shotgun" ) { arm_shooter( shooter, "mossberg_590" ); test_shooting_scenario( shooter, 9, 12, 35 ); - test_fast_shooting( shooter, 70, 0.4 ); + test_fast_shooting( shooter, 70, 0.35 ); } SECTION( "a skilled shooter with a nice smg" ) { arm_shooter( shooter, "hk_mp5", { "red_dot_sight" } ); test_shooting_scenario( shooter, 9, 12, 35 ); - test_fast_shooting( shooter, 80, 0.4 ); + test_fast_shooting( shooter, 80, 0.3 ); } SECTION( "a skilled shooter with a carbine" ) { arm_shooter( shooter, "m4_carbine", { "red_dot_sight" }, "556_m855a1" ); test_shooting_scenario( shooter, 10, 15, 48 ); - test_fast_shooting( shooter, 80, 0.4 ); + test_fast_shooting( shooter, 80, 0.3 ); } SECTION( "a skilled shooter with an available sniper rifle" ) { arm_shooter( shooter, "M24" ); @@ -518,7 +518,7 @@ TEST_CASE( "shot_features", "[gun]" "[slow]" ) // BUCKSHOT // Unarmored target - shoot_monster( "shotgun_s", {}, "shot_00", 18, 86, "mon_wolf_mutant_huge" ); + shoot_monster( "shotgun_s", {}, "shot_00", 18, 72, "mon_wolf_mutant_huge" ); // Heavy damage at range. shoot_monster( "shotgun_s", {}, "shot_00", 12, 120, "mon_wolf_mutant_huge" ); // More damage at close range. @@ -528,15 +528,15 @@ TEST_CASE( "shot_features", "[gun]" "[slow]" ) // Lightly armored target (armor_bullet: 5) // Outcomes for lightly armored enemies are very similar. - shoot_monster( "shotgun_s", {}, "shot_00", 18, 33, "mon_zombie_brute" ); - shoot_monster( "shotgun_s", {}, "shot_00", 12, 57, "mon_zombie_brute" ); + shoot_monster( "shotgun_s", {}, "shot_00", 18, 20, "mon_zombie_brute" ); + shoot_monster( "shotgun_s", {}, "shot_00", 12, 40, "mon_zombie_brute" ); shoot_monster( "shotgun_s", {}, "shot_00", 5, 116, "mon_zombie_brute" ); shoot_monster( "shotgun_s", {}, "shot_00", 1, 73, "mon_zombie_brute" ); // Armored target (armor_bullet: 10) shoot_monster( "shotgun_s", {}, "shot_00", 18, 8, "mon_smoker_brute" ); shoot_monster( "shotgun_s", {}, "shot_00", 12, 18, "mon_smoker_brute" ); - shoot_monster( "shotgun_s", {}, "shot_00", 5, 62, "mon_smoker_brute" ); + shoot_monster( "shotgun_s", {}, "shot_00", 5, 47, "mon_smoker_brute" ); shoot_monster( "shotgun_s", {}, "shot_00", 1, 72, "mon_smoker_brute" ); } @@ -559,24 +559,24 @@ TEST_CASE( "shot_features_with_choke", "[gun]" "[slow]" ) shoot_monster( "shotgun_s", { "choke" }, "shot_bird", 1, 61, "mon_zombie_brute" ); // Unarmored target - shoot_monster( "shotgun_s", { "choke" }, "shot_00", 18, 111, "mon_wolf_mutant_huge" ); + shoot_monster( "shotgun_s", { "choke" }, "shot_00", 18, 95, "mon_wolf_mutant_huge" ); shoot_monster( "shotgun_s", { "choke" }, "shot_00", 12, 144, "mon_wolf_mutant_huge" ); shoot_monster( "shotgun_s", { "choke" }, "shot_00", 5, 165, "mon_wolf_mutant_huge" ); shoot_monster( "shotgun_s", { "choke" }, "shot_00", 1, 75, "mon_wolf_mutant_huge" ); // Triviallly armored target (armor_bullet: 1) - shoot_monster( "shotgun_s", { "choke" }, "shot_00", 18, 50, "mon_zombie_tough" ); - shoot_monster( "shotgun_s", { "choke" }, "shot_00", 12, 81, "mon_zombie_tough" ); + shoot_monster( "shotgun_s", { "choke" }, "shot_00", 18, 32, "mon_zombie_tough" ); + shoot_monster( "shotgun_s", { "choke" }, "shot_00", 12, 61, "mon_zombie_tough" ); shoot_monster( "shotgun_s", { "choke" }, "shot_00", 5, 105, "mon_zombie_tough" ); shoot_monster( "shotgun_s", { "choke" }, "shot_00", 1, 100, "mon_zombie_tough" ); // Armored target (armor_bullet: 5) - shoot_monster( "shotgun_s", { "choke" }, "shot_00", 18, 46, "mon_zombie_brute" ); - shoot_monster( "shotgun_s", { "choke" }, "shot_00", 12, 77, "mon_zombie_brute" ); + shoot_monster( "shotgun_s", { "choke" }, "shot_00", 18, 25, "mon_zombie_brute" ); + shoot_monster( "shotgun_s", { "choke" }, "shot_00", 12, 54, "mon_zombie_brute" ); shoot_monster( "shotgun_s", { "choke" }, "shot_00", 5, 124, "mon_zombie_brute" ); shoot_monster( "shotgun_s", { "choke" }, "shot_00", 1, 73, "mon_zombie_brute" ); // Armored target (armor_bullet: 10) - shoot_monster( "shotgun_s", { "choke" }, "shot_00", 18, 11, "mon_smoker_brute" ); - shoot_monster( "shotgun_s", { "choke" }, "shot_00", 12, 26, "mon_smoker_brute" ); - shoot_monster( "shotgun_s", { "choke" }, "shot_00", 5, 81, "mon_smoker_brute" ); + shoot_monster( "shotgun_s", { "choke" }, "shot_00", 18, 10, "mon_smoker_brute" ); + shoot_monster( "shotgun_s", { "choke" }, "shot_00", 12, 11, "mon_smoker_brute" ); + shoot_monster( "shotgun_s", { "choke" }, "shot_00", 5, 62, "mon_smoker_brute" ); shoot_monster( "shotgun_s", { "choke" }, "shot_00", 1, 71, "mon_smoker_brute" ); }