1 /*
2  * Researching what seem to be generally agreed on values
3  * for bow power required for guaranteed game kills, these are some breakpoints.
4  * While many source use KE values, there is a broad consensus that momentum is related to damage.
5  * FPS  ft-lbs  slug-ft-s  Game
6  * 136  19.01  0.28  Turkeys
7  * 153  24  0.315  Whitetails at close range
8  * 195  39  0.401  Whitetails
9  * 249  63.73  0.512  Elk
10  * 278  79.44  0.572  Moose
11  *
12  * The concept is to bracket these threshods with various bows using standard hunting loadouts.
13  */
14 #include <iosfwd>
15 #include <memory>
16 #include <set>
17 #include <string>
18 
19 #include "catch/catch.hpp"
20 #include "damage.h"
21 #include "game_constants.h"
22 #include "item.h"
23 #include "itype.h"
24 #include "map.h"
25 #include "mapdata.h"
26 #include "monster.h"
27 #include "point.h"
28 #include "projectile.h"
29 #include "type_id.h"
30 #include "value_ptr.h"
31 
32 // In short, a bow should never destroy a wall, pretty simple.
test_projectile_hitting_wall(const std::string & target_type,bool smashable,dealt_projectile_attack & attack,const std::string & weapon_type)33 static void test_projectile_hitting_wall( const std::string &target_type, bool smashable,
34         dealt_projectile_attack &attack, const std::string &weapon_type )
35 {
36     static const tripoint target_point{ 5, 5, 0 };
37     map &here = get_map();
38     for( int i = 0; i < 10; ++i ) {
39         projectile projectile_copy = attack.proj;
40         here.set( target_point, ter_id( target_type ), furn_id( "f_null" ) );
41         CAPTURE( projectile_copy.impact.total_damage() );
42         here.shoot( target_point, projectile_copy, false );
43         CAPTURE( target_type );
44         CAPTURE( weapon_type );
45         CAPTURE( ter_id( target_type ).obj().name() );
46         CAPTURE( here.ter( target_point ).obj().name() );
47         if( smashable ) {
48             CHECK( here.ter( target_point ) != ter_id( target_type ) );
49         } else {
50             CHECK( here.ter( target_point ) == ter_id( target_type ) );
51         }
52     }
53 }
54 
test_projectile_attack(const std::string & target_type,bool killable,dealt_projectile_attack & attack,const std::string & weapon_type)55 static void test_projectile_attack( const std::string &target_type, bool killable,
56                                     dealt_projectile_attack &attack, const std::string &weapon_type )
57 {
58     for( int i = 0; i < 10; ++i ) {
59         monster target{ mtype_id( target_type ), tripoint_zero };
60         target.deal_projectile_attack( nullptr, attack, false );
61         CAPTURE( target_type );
62         CAPTURE( target.get_hp() );
63         CAPTURE( target.get_hp_max() );
64         CAPTURE( attack.proj.impact.total_damage() );
65         CAPTURE( weapon_type );
66         CHECK( target.is_dead() == killable );
67     }
68 }
69 
test_archery_balance(const std::string & weapon_type,const std::string & ammo_type,const std::string & killable,const std::string & unkillable)70 static void test_archery_balance( const std::string &weapon_type, const std::string &ammo_type,
71                                   const std::string &killable, const std::string &unkillable )
72 {
73     item weapon( weapon_type );
74     // The standard modern hunting arrow, make this a parameter if we extend to crossbows.
75     weapon.ammo_set( itype_id( ammo_type ), 1 );
76 
77     projectile test_projectile;
78     test_projectile.speed = 1000;
79     test_projectile.impact = weapon.gun_damage();
80     test_projectile.proj_effects = weapon.ammo_effects();
81     test_projectile.critical_multiplier = weapon.ammo_data()->ammo->critical_multiplier;
82 
83     dealt_projectile_attack attack {
84         test_projectile, nullptr, dealt_damage_instance(), tripoint_zero, accuracy_critical - 0.05
85     };
86     if( !killable.empty() ) {
87         test_projectile_attack( killable, true, attack, weapon_type );
88     }
89     if( !unkillable.empty() ) {
90         test_projectile_attack( unkillable, false, attack, weapon_type );
91     }
92     test_projectile_hitting_wall( "t_wall", false, attack, weapon_type );
93     // Use "can't kill anything" as an indication that it can't break a window either.
94     if( !killable.empty() ) {
95         test_projectile_hitting_wall( "t_window", true, attack, weapon_type );
96     }
97 }
98 
99 TEST_CASE( "archery_damage_thresholds", "[balance],[archery]" )
100 {
101     // Selfbow can't kill a turkey
102     test_archery_balance( "selfbow", "arrow_metal", "", "mon_turkey" );
103     test_archery_balance( "rep_crossbow", "bolt_steel", "", "mon_turkey" );
104     // Shortbow can kill turkeys, but not deer
105     test_archery_balance( "shortbow", "arrow_metal", "mon_turkey", "mon_deer" );
106     test_archery_balance( "hand_crossbow", "bolt_steel", "mon_turkey", "mon_deer" );
107     // Fiberglass recurve can kill deer, but not bear
108     test_archery_balance( "recurbow", "arrow_metal", "mon_deer", "mon_bear" );
109     test_archery_balance( "compositecrossbow", "bolt_steel", "mon_deer", "mon_bear" );
110     // Medium setting compound bow can kill Bear
111     test_archery_balance( "compbow", "arrow_metal", "mon_bear", "" );
112     // High setting modern compund bow can kill Moose
113     test_archery_balance( "compcrossbow", "bolt_steel", "mon_moose", "" );
114     test_archery_balance( "compbow_high", "arrow_metal", "mon_moose", "" );
115 }
116