projectile_attack_aim projectile_attack_roll( dispersion_sources dispersion, double range, double target_size ) { projectile_attack_aim aim; // dispersion is a measure of the dispersion of shots due to the gun + shooter characteristics // i.e. it is independent of any particular shot // shot_dispersion is the actual dispersion for this particular shot, i.e. // the error angle between where the shot was aimed and where this one actually went // NB: some cases pass dispersion == 0 for a "never misses" shot e.g. bio_magnet, aim.dispersion = dispersion.roll(); // an isosceles triangle is formed by the intended and actual target tiles aim.missed_by_tiles = iso_tangent( range, aim.dispersion ); // fraction we missed a monster target by (0.0 = perfect hit, 1.0 = miss) if( target_size > 0.0 ) { aim.missed_by = std::min( 1.0, aim.missed_by_tiles / target_size ); } else { // Special case 0 size targets, just to be safe from 0.0/0.0 NaNs aim.missed_by = 1.0; } return aim; }
#include "catch/catch.hpp" #include "game.h" #include "npc.h" #include "item_factory.h" static void test_internal( const npc& who, const item &gun ) { THEN( "the effective range is correctly calcuated" ) { // calculate range for 50% chance of critical hit at arbitrary recoil double recoil = rng_float( 0, 1000 ); double range = who.gun_current_range( gun, recoil, 50, accuracy_critical ); // calculate actual accuracy at the given range double dispersion = ( who.get_weapon_dispersion( gun ) + recoil ) / 2; double missed_by = iso_tangent( range, dispersion ); INFO( "Recoil: " << recoil ); INFO( "Range: " << range ); INFO( "Dispersion: " << dispersion ); // require inverse calculation to agree with tolerance of 0.1% REQUIRE( std::abs( missed_by - accuracy_critical ) < accuracy_critical / 1000 ); } THEN( "the snapshot range is less than the effective range" ) { REQUIRE( who.gun_engagement_range( gun, player::engagement::snapshot ) <= who.gun_engagement_range( gun, player::engagement::effective ) ); } THEN( "the effective range is less than maximum range" ) {