From 812d08ada9907c1407d68b2dec6f13dba53ff9b1 Mon Sep 17 00:00:00 2001 From: osuphobia Date: Sat, 4 May 2024 12:35:18 +0800 Subject: [PATCH 01/12] Adapted from solution by KheirFerrum Co-Authored-By: KheirFerrum <102964889+KheirFerrum@users.noreply.github.com> --- src/activity_actor.cpp | 59 +++++----------- src/activity_actor_definitions.h | 3 +- src/character.h | 2 +- src/npcmove.cpp | 2 +- src/ranged.cpp | 111 ++++++++++++++++++++++++++----- src/turret.cpp | 2 +- tests/eoc_test.cpp | 4 +- tests/ranged_balance_test.cpp | 2 +- tests/reload_time_test.cpp | 36 ++++++---- 9 files changed, 140 insertions(+), 81 deletions(-) diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index 284f1d1ac9a01..e2a2c3b4ce5e3 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -332,11 +332,12 @@ void aim_activity_actor::do_turn( player_activity &act, Character &who ) gun_mode gun = weapon->gun_current_mode(); // We need to make sure RAS weapon is loaded/reloaded in case the aim activity was temp. suspended // therefore the order of evaluation matters here - if( gun->has_flag( flag_RELOAD_AND_SHOOT ) && !gun->ammo_remaining() && !load_RAS_weapon() && - first_turn ) { - aborted = true; - act.moves_left = 0; - return; + if( gun->has_flag( flag_RELOAD_AND_SHOOT ) && !gun->ammo_remaining() && !reload_loc ) { + if( !load_RAS_weapon() ) { + aborted = true; + act.moves_left = 0; + return; + } } if( gun->has_flag( json_flag_ALWAYS_AIMED ) ) { @@ -354,10 +355,6 @@ void aim_activity_actor::do_turn( player_activity &act, Character &who ) fin_trajectory = trajectory; act.moves_left = 0; } - // If aborting on the first turn, keep 'first_turn' as 'true'. - // This allows refunding moves spent on unloading RELOAD_AND_SHOOT weapons - // to simulate avatar not loading them in the first place - first_turn = false; // Allow interrupting activity only during 'aim and fire'. // Prevents '.' key for 'aim for 10 turns' from conflicting with '.' key for 'interrupt activity' @@ -387,14 +384,7 @@ void aim_activity_actor::finish( player_activity &act, Character &who ) } gun_mode gun = weapon->gun_current_mode(); - who.fire_gun( fin_trajectory.back(), gun.qty, *gun ); - - if( weapon && weapon->gun_current_mode()->has_flag( flag_RELOAD_AND_SHOOT ) ) { - // RAS weapons are currently bugged, this is a workaround so bug impact - // isn't amplified, once #54997 and #50571 are fixed this can be removed. - restore_view(); - return; - } + who.fire_gun( fin_trajectory.back(), gun.qty, *gun, reload_loc ); if( !get_option( "AIM_AFTER_FIRING" ) ) { restore_view(); @@ -427,12 +417,12 @@ void aim_activity_actor::serialize( JsonOut &jsout ) const jsout.member( "fake_weapon", fake_weapon ); jsout.member( "fin_trajectory", fin_trajectory ); - jsout.member( "first_turn", first_turn ); jsout.member( "action", action ); jsout.member( "aif_duration", aif_duration ); jsout.member( "aiming_at_critter", aiming_at_critter ); jsout.member( "snap_to_target", snap_to_target ); jsout.member( "loaded_RAS_weapon", loaded_RAS_weapon ); + jsout.member( "reload_loc", reload_loc ); jsout.member( "shifting_view", shifting_view ); jsout.member( "initial_view_offset", initial_view_offset ); jsout.member( "aborted", aborted ); @@ -450,12 +440,12 @@ std::unique_ptr aim_activity_actor::deserialize( JsonValue &jsin data.read( "fake_weapon", actor.fake_weapon ); data.read( "fin_trajectory", actor.fin_trajectory ); - data.read( "first_turn", actor.first_turn ); data.read( "action", actor.action ); data.read( "aif_duration", actor.aif_duration ); data.read( "aiming_at_critter", actor.aiming_at_critter ); data.read( "snap_to_target", actor.snap_to_target ); data.read( "loaded_RAS_weapon", actor.loaded_RAS_weapon ); + data.read( "reload_loc", actor.reload_loc ); data.read( "shifting_view", actor.shifting_view ); data.read( "initial_view_offset", actor.initial_view_offset ); data.read( "aborted", actor.aborted ); @@ -518,48 +508,33 @@ bool aim_activity_actor::load_RAS_weapon() // Menu canceled return false; } - int reload_time = 0; - reload_time += opt.moves(); - if( !gun->reload( you, std::move( opt.ammo ), 1 ) ) { - // Reload not allowed - return false; - } // Burn 0.6% max base stamina without cardio/BMI factored in x the strength required to fire. - you.burn_energy_arms( gun->get_min_str() * static_cast( 0.006f * + // Stamina cost of RAS weapon is also calculated in ranged.cpp mod_stamina_archery, need to + // confirm if this formula should be removed. + you.burn_energy_arms( - gun->get_min_str() * static_cast( 0.006f * get_option( "PLAYER_MAX_STAMINA_BASE" ) ) ); - // At low stamina levels, firing starts getting slow. - int sta_percent = ( 100 * you.get_stamina() ) / you.get_stamina_max(); - reload_time += ( sta_percent < 25 ) ? ( ( 25 - sta_percent ) * 2 ) : 0; - you.mod_moves( -reload_time ); + reload_loc = opt.ammo; loaded_RAS_weapon = true; return true; } void aim_activity_actor::unload_RAS_weapon() { - // Unload reload-and-shoot weapons to avoid leaving bows pre-loaded with arrows avatar &you = get_avatar(); item_location weapon = get_weapon(); if( !weapon || !loaded_RAS_weapon ) { return; } + // Refund stamina cost. gun_mode gun = weapon->gun_current_mode(); if( gun->has_flag( flag_RELOAD_AND_SHOOT ) ) { - int moves_before_unload = you.get_moves(); - - // Note: this code works only for avatar - item_location loc = item_location( you, gun.target ); - you.unload( loc, true ); - - // Give back time for unloading as essentially nothing has been done. - if( first_turn ) { - you.set_moves( moves_before_unload ); - } + you.burn_energy_arms( gun->get_min_str() * static_cast( 0.006f * + get_option( "PLAYER_MAX_STAMINA_BASE" ) ) ); + loaded_RAS_weapon = false; } - loaded_RAS_weapon = false; } void autodrive_activity_actor::update_player_vehicle( Character &who ) diff --git a/src/activity_actor_definitions.h b/src/activity_actor_definitions.h index b0b4dcf0b7fcb..a7c933be52cdf 100644 --- a/src/activity_actor_definitions.h +++ b/src/activity_actor_definitions.h @@ -48,13 +48,14 @@ class aim_activity_actor : public activity_actor std::vector fin_trajectory; public: - bool first_turn = true; std::string action; int aif_duration = 0; // Counts aim-and-fire duration bool aiming_at_critter = false; // Whether aiming at critter or a tile bool snap_to_target = false; /** Not to try to unload RELOAD_AND_SHOOT weapon if it is not loaded */ bool loaded_RAS_weapon = false; + /* Item location for RAS weapon reload */ + item_location reload_loc = item_location(); bool shifting_view = false; tripoint initial_view_offset; /** Target UI requested to abort aiming */ diff --git a/src/character.h b/src/character.h index 648f71dda523e..304fb1a349327 100644 --- a/src/character.h +++ b/src/character.h @@ -3129,7 +3129,7 @@ class Character : public Creature, public visitable * @param gun item to fire (which does not necessary have to be in the players possession) * @return number of shots actually fired */ - int fire_gun( const tripoint &target, int shots, item &gun ); + int fire_gun( const tripoint &target, int shots, item &gun, item_location ammo = item_location() ); /** Execute a throw */ dealt_projectile_attack throw_item( const tripoint &target, const item &to_throw, const std::optional &blind_throw_from_pos = std::nullopt ); diff --git a/src/npcmove.cpp b/src/npcmove.cpp index 381f4bc999253..1de7c8cb454d9 100644 --- a/src/npcmove.cpp +++ b/src/npcmove.cpp @@ -1730,7 +1730,7 @@ void npc::execute_action( npc_action action ) if( is_hallucination() ) { pretend_fire( this, mode.qty, *mode ); } else { - fire_gun( tar, mode.qty, *mode ); + fire_gun( tar, mode.qty, *mode, item_location() ); // "discard" the fake bio weapon after shooting it if( is_using_bionic_weapon() ) { discharge_cbm_weapon(); diff --git a/src/ranged.cpp b/src/ranged.cpp index ef1c977e31e1e..4003e27133619 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -155,7 +155,8 @@ static const std::set ferric = { material_iron, material_steel, mat static constexpr int AIF_DURATION_LIMIT = 10; static projectile make_gun_projectile( const item &gun ); -static int time_to_attack( const Character &p, const itype &firing ); +static int NPC_time_to_attack( const Character &p, const itype &firing ); +static int time_to_attack( const Character &p, const item &firing, const item_location &loc ); /** * Handle spent ammo casings and linkages. * @param weap Weapon. @@ -368,6 +369,9 @@ class target_ui // Relevant for TargetMode::Fire void apply_aim_turning_penalty() const; + // Update range & ammo from current gun mode + void update_ammo_range_from_gun_mode(); + // Switch firing mode. bool action_switch_mode(); @@ -861,10 +865,10 @@ int Character::fire_gun( const tripoint &target, int shots ) debugmsg( "%s doesn't have a gun to fire", get_name() ); return 0; } - return fire_gun( target, shots, *gun ); + return fire_gun( target, shots, *gun, item_location() ); } -int Character::fire_gun( const tripoint &target, int shots, item &gun ) +int Character::fire_gun( const tripoint &target, int shots, item &gun, item_location ammo ) { if( !gun.is_gun() ) { debugmsg( "%s tried to fire non-gun (%s).", get_name(), gun.tname() ); @@ -874,6 +878,10 @@ int Character::fire_gun( const tripoint &target, int shots, item &gun ) add_msg_if_player( _( "A shotgun equipped with choke cannot fire slugs." ) ); return 0; } + if( gun.ammo_required() > 0 && !gun.ammo_remaining() && !ammo ) { + debugmsg( "%s's gun %s is empty and has no ammo for reloading.", gun.tname() ); + return 0; + } bool is_mech_weapon = false; if( is_mounted() && mounted_creature->has_flag( mon_flag_RIDEABLE_MECH ) ) { @@ -881,15 +889,17 @@ int Character::fire_gun( const tripoint &target, int shots, item &gun ) } // cap our maximum burst size by ammo and energy - if( !gun.has_flag( flag_VEHICLE ) ) { + if( !gun.has_flag( flag_VEHICLE ) && !ammo ) { shots = std::min( shots, gun.shots_remaining( this ) ); } else if( gun.ammo_required() ) { // This checks ammo only. Vehicle turret energy drain is handled elsewhere. - shots = std::min( shots, static_cast( gun.ammo_remaining() / gun.ammo_required() ) ); + const int ammo_left = ammo ? ammo.get_item()->count() : gun.ammo_remaining(); + shots = std::min( shots, ammo_left / gun.ammo_required() ); } if( shots <= 0 ) { debugmsg( "Attempted to fire zero or negative shots using %s", gun.tname() ); + return 0; } map &here = get_map(); @@ -910,6 +920,7 @@ int Character::fire_gun( const tripoint &target, int shots, item &gun ) static_cast( MAX_SKILL ) ) / static_cast( MAX_SKILL * 2 ); itype_id gun_id = gun.typeId(); + int attack_moves = time_to_attack( *this, gun, ammo ); skill_id gun_skill = gun.gun_skill(); add_msg_debug( debugmode::DF_RANGED, "Gun skill (%s) %g", gun_skill.c_str(), get_skill_level( gun_skill ) ) ; @@ -918,6 +929,9 @@ int Character::fire_gun( const tripoint &target, int shots, item &gun ) int hits = 0; // total shots on target int delay = 0; // delayed recoil that has yet to be applied while( curshot != shots ) { + if( !!ammo && !gun.ammo_remaining() ) { + gun.reload( get_avatar(), ammo, 1 ); + } if( gun.faults.count( fault_gun_chamber_spent ) && curshot == 0 ) { mod_moves( -get_speed() * 0.5 ); gun.faults.erase( fault_gun_chamber_spent ); @@ -1057,7 +1071,7 @@ int Character::fire_gun( const tripoint &target, int shots, item &gun ) } } // Use different amounts of time depending on the type of gun and our skill - mod_moves( -time_to_attack( *this, *gun_id ) ); + mod_moves( -attack_moves ); const islot_gun &firing = *gun.type->gun; for( const std::pair &hurt_part : firing.hurt_part_when_fired ) { @@ -1693,7 +1707,7 @@ static std::vector calculate_ranged_chances( const target_ui &ui, const Character &you, target_ui::TargetMode mode, const input_context &ctxt, const item &weapon, const dispersion_sources &dispersion, const std::vector &confidence_ratings, - const Target_attributes &target, const tripoint &pos ) + const Target_attributes &target, const tripoint &pos, const item_location &load_loc ) { std::vector aim_types { get_default_aim_type() }; std::vector aim_outputs; @@ -1724,7 +1738,8 @@ static std::vector calculate_ranged_chances( prediction.moves = throw_moves; } else { prediction.moves = predict_recoil( you, weapon, target, ui.get_sight_dispersion(), aim_type, - you.recoil ).moves + time_to_attack( you, *weapon.type ); + you.recoil ).moves + time_to_attack( you, weapon, + load_loc ); } // if the default method is "behind" the selected; e.g. you are in immediate @@ -1939,7 +1954,8 @@ static bool pl_sees( const Creature &cr ) } static int print_aim( const target_ui &ui, Character &you, const catacurses::window &w, - int line_number, input_context &ctxt, const item &weapon, const tripoint &pos ) + int line_number, input_context &ctxt, const item &weapon, const tripoint &pos, + item_location &load_loc ) { // This is absolute accuracy for the player. // TODO: push the calculations duplicated from Creature::deal_projectile_attack() and @@ -1959,7 +1975,7 @@ static int print_aim( const target_ui &ui, Character &you, const catacurses::win const std::vector aim_chances = calculate_ranged_chances( ui, you, target_ui::TargetMode::Fire, ctxt, weapon, dispersion, confidence_config, - Target_attributes( you.pos(), pos ), pos ); + Target_attributes( you.pos(), pos ), pos, load_loc ); return print_ranged_chance( w, line_number, aim_chances ); } @@ -1998,7 +2014,8 @@ static void draw_throw_aim( const target_ui &ui, const Character &you, const cat you.sees( target_pos ) ); const std::vector aim_chances = calculate_ranged_chances( ui, you, - throwing_target_mode, ctxt, weapon, dispersion, confidence_config, attributes, target_pos ); + throwing_target_mode, ctxt, weapon, dispersion, confidence_config, attributes, target_pos, + item_location() ); text_y = print_ranged_chance( w, text_y, aim_chances ); } @@ -2104,7 +2121,7 @@ static projectile make_gun_projectile( const item &gun ) return proj; } -int time_to_attack( const Character &p, const itype &firing ) +int NPC_time_to_attack( const Character &p, const itype &firing ) { const skill_id &skill_used = firing.gun->skill_used; const time_info_t &info = skill_used->time_to_attack(); @@ -2113,6 +2130,26 @@ int time_to_attack( const Character &p, const itype &firing ) skill_used ) ) ) ); } +int time_to_attack( const Character &p, const item &firing, const item_location &loc ) +{ + const skill_id &skill_used = firing.type->gun->skill_used; + const time_info_t &info = skill_used->time_to_attack(); + int RAS_time = 0; + if( !loc ) { + RAS_time = 0; + } else { + // At low stamina levels, firing starts getting slow. + const item_location gun = p.get_wielded_item(); + int sta_percent = ( 100 * p.get_stamina() ) / p.get_stamina_max(); + RAS_time += ( sta_percent < 25 ) ? ( ( 25 - sta_percent ) * 2 ) : 0; + item::reload_option opt = item::reload_option( &p, gun, loc ); + RAS_time += opt.moves(); + } + return std::max( info.min_time, + static_cast( round( info.base_time - info.time_reduction_per_level * p.get_skill_level( + skill_used ) ) ) + RAS_time ); +} + static void cycle_action( item &weap, const itype_id &ammo, const tripoint &pos ) { map &here = get_map(); @@ -2382,7 +2419,7 @@ double Character::gun_value( const item &weap, int ammo ) const damage_factor += 0.5f * gun_damage.damage_units.front().res_pen; } - int move_cost = time_to_attack( *this, *weap.type ); + int move_cost = NPC_time_to_attack( *this, *weap.type ); if( gun.clip != 0 && gun.clip < 10 ) { // TODO: RELOAD_ONE should get a penalty here int reload_cost = gun.reload_time + encumb( bodypart_id( "hand_l" ) ) + encumb( @@ -2466,6 +2503,7 @@ target_handler::trajectory target_ui::run() you->add_msg_if_player( m_bad, _( "You don't have enough %s to cast this spell" ), casting->energy_string() ); } else if( mode == TargetMode::Fire ) { + update_ammo_range_from_gun_mode(); sight_dispersion = you->most_accurate_aiming_method_limit( *relevant ); } @@ -2505,7 +2543,7 @@ target_handler::trajectory target_ui::run() bool attack_was_confirmed = false; bool reentered = false; bool resume_critter = false; - if( mode == TargetMode::Fire && !activity->first_turn ) { + if( mode == TargetMode::Fire && !activity->action.empty() ) { // We were in this UI during previous turn... reentered = true; std::string act_data = activity->action; @@ -2559,7 +2597,7 @@ target_handler::trajectory target_ui::run() action.clear(); attack_was_confirmed = false; } - if( !activity->first_turn && !action.empty() && !prompt_friendlies_in_lof() ) { + if( !action.empty() && !prompt_friendlies_in_lof() ) { // A friendly creature moved into line of fire during aim-and-shoot, // and player decided to stop aiming action.clear(); @@ -3301,6 +3339,34 @@ void target_ui::apply_aim_turning_penalty() const you->recoil = predicted_recoil; } +void target_ui::update_ammo_range_from_gun_mode() +{ + if( mode == TargetMode::TurretManual ) { + itype_id ammo_current = turret->ammo_current(); + if( ammo_current.is_null() ) { + ammo = nullptr; + range = 0; + } else { + ammo = item::find_type( ammo_current ); + range = turret->range(); + } + } else { + if( relevant->gun_current_mode().melee() ) { + range = relevant->current_reach_range( *you ); + } else { + ammo = activity->reload_loc ? activity->reload_loc.get_item()->type : + relevant->gun_current_mode().target->ammo_data(); + if( activity->reload_loc ) { + item temp_weapon = *relevant; + temp_weapon.ammo_set( ammo->get_id() ); + range = temp_weapon.gun_current_mode().target->gun_range( you ); + } else { + range = relevant->gun_current_mode().target->gun_range( you ); + } + } + } +} + bool target_ui::action_switch_mode() { uilist menu; @@ -3389,8 +3455,15 @@ bool target_ui::action_switch_mode() refresh = true; range = relevant->current_reach_range( *you ); } else { - range = relevant->gun_current_mode().target->gun_range( you ); - ammo = relevant->gun_current_mode().target->ammo_data(); + ammo = activity->reload_loc ? activity->reload_loc.get_item()->type : + relevant->gun_current_mode().target->ammo_data(); + if( activity->reload_loc ) { + item temp_weapon = *relevant; + temp_weapon.ammo_set( ammo->get_id() ); + range = temp_weapon.gun_current_mode().target->gun_range( you ); + } else { + range = relevant->gun_current_mode().target->gun_range( you ); + } } } @@ -3586,7 +3659,9 @@ void target_ui::draw_ui_window() } else if( status == Status::Good ) { // TODO: these are old, consider refactoring if( mode == TargetMode::Fire ) { - text_y = print_aim( *this, *you, w_target, text_y, ctxt, *relevant->gun_current_mode(), dst ); + item_location load_loc = activity->reload_loc; + text_y = print_aim( *this, *you, w_target, text_y, ctxt, *relevant->gun_current_mode(), dst, + load_loc ); } else if( mode == TargetMode::Throw || mode == TargetMode::ThrowBlind ) { bool blind = mode == TargetMode::ThrowBlind; draw_throw_aim( *this, *you, w_target, text_y, ctxt, *relevant, dst, blind ); diff --git a/src/turret.cpp b/src/turret.cpp index e7fdbdcada106..bdd9431f3d128 100644 --- a/src/turret.cpp +++ b/src/turret.cpp @@ -317,7 +317,7 @@ int turret_data::fire( Character &c, const tripoint &target ) gun_mode mode = base()->gun_current_mode(); prepare_fire( c ); - shots = c.fire_gun( target, mode.qty, *mode ); + shots = c.fire_gun( target, mode.qty, *mode, item_location() ); post_fire( c, shots ); return shots; } diff --git a/tests/eoc_test.cpp b/tests/eoc_test.cpp index fce034c121eb5..012ab86d055de 100644 --- a/tests/eoc_test.cpp +++ b/tests/eoc_test.cpp @@ -1174,7 +1174,7 @@ TEST_CASE( "EOC_combat_event_test", "[eoc]" ) get_avatar().set_body(); arm_shooter( get_avatar(), "shotgun_s" ); get_avatar().recoil = 0; - get_avatar().fire_gun( target_pos, 1, *get_avatar().get_wielded_item() ); + get_avatar().fire_gun( target_pos, 1, *get_avatar().get_wielded_item(), item_location() ); if( !npc_dst_ranged.get_value( "npctalk_var_test_event_last_event" ).empty() ) { break; } @@ -1194,7 +1194,7 @@ TEST_CASE( "EOC_combat_event_test", "[eoc]" ) get_avatar().set_body(); arm_shooter( get_avatar(), "shotgun_s" ); get_avatar().recoil = 0; - get_avatar().fire_gun( mon_dst_ranged.pos(), 1, *get_avatar().get_wielded_item() ); + get_avatar().fire_gun( mon_dst_ranged.pos(), 1, *get_avatar().get_wielded_item(), item_location() ); if( !mon_dst_ranged.get_value( "npctalk_var_test_event_last_event" ).empty() ) { break; } diff --git a/tests/ranged_balance_test.cpp b/tests/ranged_balance_test.cpp index 6afc3f84ca172..70f40f02ea6b9 100644 --- a/tests/ranged_balance_test.cpp +++ b/tests/ranged_balance_test.cpp @@ -459,7 +459,7 @@ static void shoot_monster( const std::string &gun_type, const std::vectorrecoil = 0; monster &mon = spawn_test_monster( monster_type, monster_pos, false ); const int prev_HP = mon.get_hp(); - shooter->fire_gun( monster_pos, 1, *shooter->get_wielded_item() ); + shooter->fire_gun( monster_pos, 1, *shooter->get_wielded_item(), item_location() ); damage.add( prev_HP - mon.get_hp() ); if( damage.margin_of_error() < 0.05 && damage.n() > 100 ) { break; diff --git a/tests/reload_time_test.cpp b/tests/reload_time_test.cpp index 8284a7c584ac9..c115f0b526a89 100644 --- a/tests/reload_time_test.cpp +++ b/tests/reload_time_test.cpp @@ -3,18 +3,26 @@ #include "activity_actor_definitions.h" #include "avatar.h" #include "calendar.h" +#include "creature_tracker.h" +#include #include "item.h" #include "map.h" #include "player_helpers.h" #include "point.h" #include +#include "map_helpers.h" + +static const mtype_id pseudo_debug_mon( "pseudo_debug_mon" ); static void check_reload_time( const std::string &weapon, const std::string &ammo, const std::string &container, int expected_moves ) { const tripoint test_origin( 60, 60, 0 ); + const tripoint spot( 61, 60, 0 ); + clear_map(); avatar &shooter = get_avatar(); + g->place_critter_at( pseudo_debug_mon, spot ); shooter.setpos( test_origin ); shooter.set_wielded_item( item( weapon, calendar::turn_zero, 0 ) ); if( container.empty() ) { @@ -37,7 +45,7 @@ static void check_reload_time( const std::string &weapon, const std::string &amm CAPTURE( shooter.used_weapon()->get_reload_time() ); aim_activity_actor act = aim_activity_actor::use_wielded(); int moves_before = shooter.get_moves(); - REQUIRE( act.load_RAS_weapon() ); + REQUIRE( shooter.fire_gun( spot, 1, *shooter.used_weapon(), shooter.ammo_location ) ); int moves_after = shooter.get_moves(); int spent_moves = moves_before - moves_after; int expected_upper = expected_moves * 1.05; @@ -52,47 +60,47 @@ TEST_CASE( "reload_from_inventory_times", "[reload],[inventory],[balance]" ) clear_avatar(); SECTION( "reloading a slingshot" ) { SECTION( "from a backpack" ) { - check_reload_time( "slingshot", "pebble", "backpack", 350 ); + check_reload_time( "slingshot", "pebble", "backpack", 400 ); } SECTION( "from an ammo pouch" ) { - check_reload_time( "slingshot", "pebble", "ammo_pouch", 87 ); + check_reload_time( "slingshot", "pebble", "ammo_pouch", 137 ); } SECTION( "from a tool belt" ) { - check_reload_time( "slingshot", "pebble", "tool_belt", 150 ); + check_reload_time( "slingshot", "pebble", "tool_belt", 200 ); } SECTION( "from a pocket" ) { - check_reload_time( "slingshot", "pebble", "pants", 150 ); + check_reload_time( "slingshot", "pebble", "pants", 200 ); } SECTION( "from the ground" ) { - check_reload_time( "slingshot", "pebble", "", 130 ); + check_reload_time( "slingshot", "pebble", "", 180 ); } } SECTION( "reloading a staff sling" ) { SECTION( "from a backpack" ) { - check_reload_time( "staff_sling", "rock", "backpack", 375 ); + check_reload_time( "staff_sling", "rock", "backpack", 595 ); } SECTION( "from a stone pouch" ) { - check_reload_time( "staff_sling", "rock", "stone_pouch", 95 ); + check_reload_time( "staff_sling", "rock", "stone_pouch", 315 ); } SECTION( "from a tool belt" ) { - check_reload_time( "staff_sling", "rock", "tool_belt", 160 ); + check_reload_time( "staff_sling", "rock", "tool_belt", 380 ); } SECTION( "from a pocket" ) { - check_reload_time( "staff_sling", "rock", "pants", 175 ); + check_reload_time( "staff_sling", "rock", "pants", 395 ); } SECTION( "from the ground" ) { - check_reload_time( "staff_sling", "rock", "", 150 ); + check_reload_time( "staff_sling", "rock", "", 370 ); } } SECTION( "reloading a bow" ) { SECTION( "from a duffel bag" ) { - check_reload_time( "longbow", "arrow_wood", "long_duffelbag", 360 ); + check_reload_time( "longbow", "arrow_wood", "long_duffelbag", 410 ); } SECTION( "from a quiver" ) { - check_reload_time( "longbow", "arrow_wood", "quiver", 80 ); + check_reload_time( "longbow", "arrow_wood", "quiver", 130 ); } SECTION( "from the ground" ) { - check_reload_time( "longbow", "arrow_wood", "", 140 ); + check_reload_time( "longbow", "arrow_wood", "", 190 ); } } } From 1c02c07361da2603cc87a15d9bdc7bea6bd3f14d Mon Sep 17 00:00:00 2001 From: osuphobia Date: Mon, 6 May 2024 23:32:58 +0800 Subject: [PATCH 02/12] Reload RAS weapon after aiming. --- src/activity_actor.cpp | 22 +++++++++++++--------- src/ranged.cpp | 25 +++++++++++++++++++------ 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index e2a2c3b4ce5e3..d8f98aac58c5f 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -407,8 +407,8 @@ void aim_activity_actor::finish( player_activity &act, Character &who ) void aim_activity_actor::canceled( player_activity &/*act*/, Character &/*who*/ ) { - restore_view(); unload_RAS_weapon(); + restore_view(); } void aim_activity_actor::serialize( JsonOut &jsout ) const @@ -510,13 +510,10 @@ bool aim_activity_actor::load_RAS_weapon() } // Burn 0.6% max base stamina without cardio/BMI factored in x the strength required to fire. - // Stamina cost of RAS weapon is also calculated in ranged.cpp mod_stamina_archery, need to - // confirm if this formula should be removed. you.burn_energy_arms( - gun->get_min_str() * static_cast( 0.006f * get_option( "PLAYER_MAX_STAMINA_BASE" ) ) ); reload_loc = opt.ammo; - loaded_RAS_weapon = true; return true; } @@ -524,16 +521,23 @@ void aim_activity_actor::unload_RAS_weapon() { avatar &you = get_avatar(); item_location weapon = get_weapon(); - if( !weapon || !loaded_RAS_weapon ) { + if( !weapon ) { return; } - // Refund stamina cost. gun_mode gun = weapon->gun_current_mode(); if( gun->has_flag( flag_RELOAD_AND_SHOOT ) ) { - you.burn_energy_arms( gun->get_min_str() * static_cast( 0.006f * - get_option( "PLAYER_MAX_STAMINA_BASE" ) ) ); - loaded_RAS_weapon = false; + if( !loaded_RAS_weapon ) { + // Refund stamina cost if not loaded. + you.burn_energy_arms( gun->get_min_str() * static_cast( 0.006f * + get_option( "PLAYER_MAX_STAMINA_BASE" ) ) ); + return; + } + if( gun->ammo_remaining() ){ + item_location loc = item_location( you, gun.target ); + you.unload( loc, true ); + loaded_RAS_weapon = false; + } } } diff --git a/src/ranged.cpp b/src/ranged.cpp index 4003e27133619..cbcaae18b0408 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -157,6 +157,7 @@ static constexpr int AIF_DURATION_LIMIT = 10; static projectile make_gun_projectile( const item &gun ); static int NPC_time_to_attack( const Character &p, const itype &firing ); static int time_to_attack( const Character &p, const item &firing, const item_location &loc ); +int RAS_time = 0; /** * Handle spent ammo casings and linkages. * @param weap Weapon. @@ -1923,9 +1924,11 @@ static int print_ranged_chance( const catacurses::window &w, int line_number, for( const aim_type_prediction &out : sorted ) { std::string col_hl = out.is_default ? "light_green" : "light_gray"; - std::string desc = + std::string desc = RAS_time == 0 ? string_format( "[%s] %s %s | %s: %3d", - out.hotkey, col_hl, out.name, _( "Aim" ), _( "Moves to fire" ), out.moves ); + out.hotkey, col_hl, out.name, _( "Aim" ), _( "Moves to fire" ), out.moves ) : + string_format( "[%s] %s %s | %s: %3d (%d)", + out.hotkey, col_hl, out.name, _( "Aim" ), _( "Moves to fire" ), out.moves, RAS_time ); print_colored_text( w, point( 1, line_number++ ), col, col, desc ); @@ -2134,10 +2137,8 @@ int time_to_attack( const Character &p, const item &firing, const item_location { const skill_id &skill_used = firing.type->gun->skill_used; const time_info_t &info = skill_used->time_to_attack(); - int RAS_time = 0; - if( !loc ) { - RAS_time = 0; - } else { + RAS_time = 0; + if( loc ) { // At low stamina levels, firing starts getting slow. const item_location gun = p.get_wielded_item(); int sta_percent = ( 100 * p.get_stamina() ) / p.get_stamina_max(); @@ -2688,6 +2689,15 @@ target_handler::trajectory target_ui::run() loop_exit_code = ExitCode::Timeout; break; } + + if( you->get_wielded_item()->has_flag( flag_RELOAD_AND_SHOOT ) ) { + if( !you->get_wielded_item()->ammo_remaining() && activity->reload_loc ) { + you->mod_moves( -RAS_time ); + you->get_wielded_item()->reload( get_avatar(), activity->reload_loc, 1 ); + activity->reload_loc = item_location(); + activity->loaded_RAS_weapon = true; + } + } } else if( action == "STOPAIM" ) { if( status != Status::Good ) { continue; @@ -2728,6 +2738,9 @@ target_handler::trajectory target_ui::run() bool harmful = !( mode == TargetMode::Spell && casting->damage( player_character ) <= 0 ); on_target_accepted( harmful ); } + if( you->get_wielded_item()->has_flag( flag_RELOAD_AND_SHOOT ) ) { + activity->loaded_RAS_weapon = false; + } break; } case ExitCode::Timeout: { From 44e595c3deda51ece1417f1f4d0e3fca8c38928e Mon Sep 17 00:00:00 2001 From: osuphobia Date: Tue, 7 May 2024 00:05:41 +0800 Subject: [PATCH 03/12] astyle --- src/activity_actor.cpp | 2 +- src/ranged.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index d8f98aac58c5f..54e7382bdfcb8 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -533,7 +533,7 @@ void aim_activity_actor::unload_RAS_weapon() get_option( "PLAYER_MAX_STAMINA_BASE" ) ) ); return; } - if( gun->ammo_remaining() ){ + if( gun->ammo_remaining() ) { item_location loc = item_location( you, gun.target ); you.unload( loc, true ); loaded_RAS_weapon = false; diff --git a/src/ranged.cpp b/src/ranged.cpp index cbcaae18b0408..f87bb0fdf7ba5 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -1925,10 +1925,10 @@ static int print_ranged_chance( const catacurses::window &w, int line_number, for( const aim_type_prediction &out : sorted ) { std::string col_hl = out.is_default ? "light_green" : "light_gray"; std::string desc = RAS_time == 0 ? - string_format( "[%s] %s %s | %s: %3d", - out.hotkey, col_hl, out.name, _( "Aim" ), _( "Moves to fire" ), out.moves ) : - string_format( "[%s] %s %s | %s: %3d (%d)", - out.hotkey, col_hl, out.name, _( "Aim" ), _( "Moves to fire" ), out.moves, RAS_time ); + string_format( "[%s] %s %s | %s: %3d", + out.hotkey, col_hl, out.name, _( "Aim" ), _( "Moves to fire" ), out.moves ) : + string_format( "[%s] %s %s | %s: %3d (%d)", + out.hotkey, col_hl, out.name, _( "Aim" ), _( "Moves to fire" ), out.moves, RAS_time ); print_colored_text( w, point( 1, line_number++ ), col, col, desc ); From 711dfbb267f07976676763d1556e23c56d5f9e2a Mon Sep 17 00:00:00 2001 From: osuphobia Date: Tue, 7 May 2024 09:27:19 +0800 Subject: [PATCH 04/12] clang-tidy --- src/ranged.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index f87bb0fdf7ba5..2bd9e5ef9aed1 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -157,7 +157,7 @@ static constexpr int AIF_DURATION_LIMIT = 10; static projectile make_gun_projectile( const item &gun ); static int NPC_time_to_attack( const Character &p, const itype &firing ); static int time_to_attack( const Character &p, const item &firing, const item_location &loc ); -int RAS_time = 0; +static int RAS_time = 0; /** * Handle spent ammo casings and linkages. * @param weap Weapon. From e551303b4b23003383ec911d7efb2eb31f4a3a20 Mon Sep 17 00:00:00 2001 From: osuphobia Date: Tue, 7 May 2024 23:57:57 +0800 Subject: [PATCH 05/12] Only burn stamina when needed. --- src/activity_actor.cpp | 9 --------- src/ranged.cpp | 16 +++++++++++----- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index 54e7382bdfcb8..6711873e25aeb 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -509,9 +509,6 @@ bool aim_activity_actor::load_RAS_weapon() return false; } - // Burn 0.6% max base stamina without cardio/BMI factored in x the strength required to fire. - you.burn_energy_arms( - gun->get_min_str() * static_cast( 0.006f * - get_option( "PLAYER_MAX_STAMINA_BASE" ) ) ); reload_loc = opt.ammo; return true; @@ -527,12 +524,6 @@ void aim_activity_actor::unload_RAS_weapon() gun_mode gun = weapon->gun_current_mode(); if( gun->has_flag( flag_RELOAD_AND_SHOOT ) ) { - if( !loaded_RAS_weapon ) { - // Refund stamina cost if not loaded. - you.burn_energy_arms( gun->get_min_str() * static_cast( 0.006f * - get_option( "PLAYER_MAX_STAMINA_BASE" ) ) ); - return; - } if( gun->ammo_remaining() ) { item_location loc = item_location( you, gun.target ); you.unload( loc, true ); diff --git a/src/ranged.cpp b/src/ranged.cpp index 2bd9e5ef9aed1..1b360612da058 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -880,7 +880,7 @@ int Character::fire_gun( const tripoint &target, int shots, item &gun, item_loca return 0; } if( gun.ammo_required() > 0 && !gun.ammo_remaining() && !ammo ) { - debugmsg( "%s's gun %s is empty and has no ammo for reloading.", gun.tname() ); + debugmsg( "%s is empty and has no ammo for reloading.", gun.tname() ); return 0; } bool is_mech_weapon = false; @@ -931,7 +931,10 @@ int Character::fire_gun( const tripoint &target, int shots, item &gun, item_loca int delay = 0; // delayed recoil that has yet to be applied while( curshot != shots ) { if( !!ammo && !gun.ammo_remaining() ) { - gun.reload( get_avatar(), ammo, 1 ); + Character &you = get_avatar(); + gun.reload( you, ammo, 1 ); + you.burn_energy_arms( - gun.get_min_str() * static_cast( 0.006f * + get_option( "PLAYER_MAX_STAMINA_BASE" ) ) ); } if( gun.faults.count( fault_gun_chamber_spent ) && curshot == 0 ) { mod_moves( -get_speed() * 0.5 ); @@ -2690,10 +2693,13 @@ target_handler::trajectory target_ui::run() break; } - if( you->get_wielded_item()->has_flag( flag_RELOAD_AND_SHOOT ) ) { - if( !you->get_wielded_item()->ammo_remaining() && activity->reload_loc ) { + item_location weapon = activity->get_weapon(); + if( weapon->has_flag( flag_RELOAD_AND_SHOOT ) ) { + if( !weapon->ammo_remaining() && activity->reload_loc ) { you->mod_moves( -RAS_time ); - you->get_wielded_item()->reload( get_avatar(), activity->reload_loc, 1 ); + weapon->reload( get_avatar(), activity->reload_loc, 1 ); + you->burn_energy_arms( - weapon->get_min_str() * static_cast( 0.006f * + get_option( "PLAYER_MAX_STAMINA_BASE" ) ) ); activity->reload_loc = item_location(); activity->loaded_RAS_weapon = true; } From 176243977d8b0c56c83ffb5a905b696d8b4f413d Mon Sep 17 00:00:00 2001 From: osuphobia Date: Fri, 10 May 2024 09:54:03 +0800 Subject: [PATCH 06/12] Remove unused variable. --- src/activity_actor.cpp | 3 --- src/activity_actor_definitions.h | 2 -- src/ranged.cpp | 4 ---- 3 files changed, 9 deletions(-) diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index 6711873e25aeb..1e1b4df4398c4 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -421,7 +421,6 @@ void aim_activity_actor::serialize( JsonOut &jsout ) const jsout.member( "aif_duration", aif_duration ); jsout.member( "aiming_at_critter", aiming_at_critter ); jsout.member( "snap_to_target", snap_to_target ); - jsout.member( "loaded_RAS_weapon", loaded_RAS_weapon ); jsout.member( "reload_loc", reload_loc ); jsout.member( "shifting_view", shifting_view ); jsout.member( "initial_view_offset", initial_view_offset ); @@ -444,7 +443,6 @@ std::unique_ptr aim_activity_actor::deserialize( JsonValue &jsin data.read( "aif_duration", actor.aif_duration ); data.read( "aiming_at_critter", actor.aiming_at_critter ); data.read( "snap_to_target", actor.snap_to_target ); - data.read( "loaded_RAS_weapon", actor.loaded_RAS_weapon ); data.read( "reload_loc", actor.reload_loc ); data.read( "shifting_view", actor.shifting_view ); data.read( "initial_view_offset", actor.initial_view_offset ); @@ -527,7 +525,6 @@ void aim_activity_actor::unload_RAS_weapon() if( gun->ammo_remaining() ) { item_location loc = item_location( you, gun.target ); you.unload( loc, true ); - loaded_RAS_weapon = false; } } } diff --git a/src/activity_actor_definitions.h b/src/activity_actor_definitions.h index a7c933be52cdf..707d08daedde7 100644 --- a/src/activity_actor_definitions.h +++ b/src/activity_actor_definitions.h @@ -52,8 +52,6 @@ class aim_activity_actor : public activity_actor int aif_duration = 0; // Counts aim-and-fire duration bool aiming_at_critter = false; // Whether aiming at critter or a tile bool snap_to_target = false; - /** Not to try to unload RELOAD_AND_SHOOT weapon if it is not loaded */ - bool loaded_RAS_weapon = false; /* Item location for RAS weapon reload */ item_location reload_loc = item_location(); bool shifting_view = false; diff --git a/src/ranged.cpp b/src/ranged.cpp index 1b360612da058..91b2a4bba7b09 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -2701,7 +2701,6 @@ target_handler::trajectory target_ui::run() you->burn_energy_arms( - weapon->get_min_str() * static_cast( 0.006f * get_option( "PLAYER_MAX_STAMINA_BASE" ) ) ); activity->reload_loc = item_location(); - activity->loaded_RAS_weapon = true; } } } else if( action == "STOPAIM" ) { @@ -2744,9 +2743,6 @@ target_handler::trajectory target_ui::run() bool harmful = !( mode == TargetMode::Spell && casting->damage( player_character ) <= 0 ); on_target_accepted( harmful ); } - if( you->get_wielded_item()->has_flag( flag_RELOAD_AND_SHOOT ) ) { - activity->loaded_RAS_weapon = false; - } break; } case ExitCode::Timeout: { From 2eb529e56a4ae66369c6d77c2aa1a8633e5c5d95 Mon Sep 17 00:00:00 2001 From: osuphobia Date: Sat, 18 May 2024 07:44:08 +0800 Subject: [PATCH 07/12] Remove unnecessary arguments. --- src/npcmove.cpp | 2 +- src/turret.cpp | 2 +- tests/eoc_test.cpp | 4 ++-- tests/ranged_balance_test.cpp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/npcmove.cpp b/src/npcmove.cpp index 1e2bb23d839a8..6b841d3eb3a62 100644 --- a/src/npcmove.cpp +++ b/src/npcmove.cpp @@ -1730,7 +1730,7 @@ void npc::execute_action( npc_action action ) if( is_hallucination() ) { pretend_fire( this, mode.qty, *mode ); } else { - fire_gun( tar, mode.qty, *mode, item_location() ); + fire_gun( tar, mode.qty, *mode ); // "discard" the fake bio weapon after shooting it if( is_using_bionic_weapon() ) { discharge_cbm_weapon(); diff --git a/src/turret.cpp b/src/turret.cpp index bdd9431f3d128..e7fdbdcada106 100644 --- a/src/turret.cpp +++ b/src/turret.cpp @@ -317,7 +317,7 @@ int turret_data::fire( Character &c, const tripoint &target ) gun_mode mode = base()->gun_current_mode(); prepare_fire( c ); - shots = c.fire_gun( target, mode.qty, *mode, item_location() ); + shots = c.fire_gun( target, mode.qty, *mode ); post_fire( c, shots ); return shots; } diff --git a/tests/eoc_test.cpp b/tests/eoc_test.cpp index 012ab86d055de..fce034c121eb5 100644 --- a/tests/eoc_test.cpp +++ b/tests/eoc_test.cpp @@ -1174,7 +1174,7 @@ TEST_CASE( "EOC_combat_event_test", "[eoc]" ) get_avatar().set_body(); arm_shooter( get_avatar(), "shotgun_s" ); get_avatar().recoil = 0; - get_avatar().fire_gun( target_pos, 1, *get_avatar().get_wielded_item(), item_location() ); + get_avatar().fire_gun( target_pos, 1, *get_avatar().get_wielded_item() ); if( !npc_dst_ranged.get_value( "npctalk_var_test_event_last_event" ).empty() ) { break; } @@ -1194,7 +1194,7 @@ TEST_CASE( "EOC_combat_event_test", "[eoc]" ) get_avatar().set_body(); arm_shooter( get_avatar(), "shotgun_s" ); get_avatar().recoil = 0; - get_avatar().fire_gun( mon_dst_ranged.pos(), 1, *get_avatar().get_wielded_item(), item_location() ); + get_avatar().fire_gun( mon_dst_ranged.pos(), 1, *get_avatar().get_wielded_item() ); if( !mon_dst_ranged.get_value( "npctalk_var_test_event_last_event" ).empty() ) { break; } diff --git a/tests/ranged_balance_test.cpp b/tests/ranged_balance_test.cpp index 70f40f02ea6b9..6afc3f84ca172 100644 --- a/tests/ranged_balance_test.cpp +++ b/tests/ranged_balance_test.cpp @@ -459,7 +459,7 @@ static void shoot_monster( const std::string &gun_type, const std::vectorrecoil = 0; monster &mon = spawn_test_monster( monster_type, monster_pos, false ); const int prev_HP = mon.get_hp(); - shooter->fire_gun( monster_pos, 1, *shooter->get_wielded_item(), item_location() ); + shooter->fire_gun( monster_pos, 1, *shooter->get_wielded_item() ); damage.add( prev_HP - mon.get_hp() ); if( damage.margin_of_error() < 0.05 && damage.n() > 100 ) { break; From a3eb1559a62ac8c53a7a71f09af3e05d182cfe3c Mon Sep 17 00:00:00 2001 From: osuphobia Date: Sat, 18 May 2024 09:18:39 +0800 Subject: [PATCH 08/12] Rewrite time_to_attack. --- src/ranged.cpp | 52 +++++++++++++++++++++++--------------------------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index 1dbc43281887e..c9aa2cd5e25a8 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -155,9 +155,8 @@ static const std::set ferric = { material_iron, material_steel, mat static constexpr int AIF_DURATION_LIMIT = 10; static projectile make_gun_projectile( const item &gun ); -static int NPC_time_to_attack( const Character &p, const itype &firing ); -static int time_to_attack( const Character &p, const item &firing, const item_location &loc ); -static int RAS_time = 0; +static int time_to_attack( const Character &p, const itype &firing ); +static int RAS_time( const Character &p, const item &firing, const item_location &loc ); /** * Handle spent ammo casings and linkages. * @param weap Weapon. @@ -921,7 +920,7 @@ int Character::fire_gun( const tripoint &target, int shots, item &gun, item_loca static_cast( MAX_SKILL ) ) / static_cast( MAX_SKILL * 2 ); itype_id gun_id = gun.typeId(); - int attack_moves = time_to_attack( *this, gun, ammo ); + int attack_moves = time_to_attack( *this, *gun_id ) + RAS_time( *this, gun, ammo ); skill_id gun_skill = gun.gun_skill(); add_msg_debug( debugmode::DF_RANGED, "Gun skill (%s) %g", gun_skill.c_str(), get_skill_level( gun_skill ) ) ; @@ -1742,8 +1741,8 @@ static std::vector calculate_ranged_chances( prediction.moves = throw_moves; } else { prediction.moves = predict_recoil( you, weapon, target, ui.get_sight_dispersion(), aim_type, - you.recoil ).moves + time_to_attack( you, weapon, - load_loc ); + you.recoil ).moves + time_to_attack( you, *weapon.type ) + + RAS_time( you, weapon, load_loc ); } // if the default method is "behind" the selected; e.g. you are in immediate @@ -1822,7 +1821,7 @@ static void print_confidence_rating_bar( const catacurses::window &w, } static int print_ranged_chance( const catacurses::window &w, int line_number, - const std::vector &aim_chances ) + const std::vector &aim_chances, const int time ) { std::vector sorted = aim_chances; @@ -1927,11 +1926,11 @@ static int print_ranged_chance( const catacurses::window &w, int line_number, for( const aim_type_prediction &out : sorted ) { std::string col_hl = out.is_default ? "light_green" : "light_gray"; - std::string desc = RAS_time == 0 ? + std::string desc = time == 0 ? string_format( "[%s] %s %s | %s: %3d", out.hotkey, col_hl, out.name, _( "Aim" ), _( "Moves to fire" ), out.moves ) : string_format( "[%s] %s %s | %s: %3d (%d)", - out.hotkey, col_hl, out.name, _( "Aim" ), _( "Moves to fire" ), out.moves, RAS_time ); + out.hotkey, col_hl, out.name, _( "Aim" ), _( "Moves to fire" ), out.moves, time ); print_colored_text( w, point( 1, line_number++ ), col, col, desc ); @@ -1983,7 +1982,9 @@ static int print_aim( const target_ui &ui, Character &you, const catacurses::win target_ui::TargetMode::Fire, ctxt, weapon, dispersion, confidence_config, Target_attributes( you.pos(), pos ), pos, load_loc ); - return print_ranged_chance( w, line_number, aim_chances ); + int time = RAS_time( you, weapon, load_loc ); + + return print_ranged_chance( w, line_number, aim_chances, time ); } static void draw_throw_aim( const target_ui &ui, const Character &you, const catacurses::window &w, @@ -2024,7 +2025,7 @@ static void draw_throw_aim( const target_ui &ui, const Character &you, const cat throwing_target_mode, ctxt, weapon, dispersion, confidence_config, attributes, target_pos, item_location() ); - text_y = print_ranged_chance( w, text_y, aim_chances ); + text_y = print_ranged_chance( w, text_y, aim_chances, 0 ); } std::vector Character::get_aim_types( const item &gun ) const @@ -2128,7 +2129,7 @@ static projectile make_gun_projectile( const item &gun ) return proj; } -int NPC_time_to_attack( const Character &p, const itype &firing ) +int time_to_attack( const Character &p, const itype &firing ) { const skill_id &skill_used = firing.gun->skill_used; const time_info_t &info = skill_used->time_to_attack(); @@ -2137,22 +2138,18 @@ int NPC_time_to_attack( const Character &p, const itype &firing ) skill_used ) ) ) ); } -int time_to_attack( const Character &p, const item &firing, const item_location &loc ) +int RAS_time( const Character &p, const item &firing, const item_location &loc ) { - const skill_id &skill_used = firing.type->gun->skill_used; - const time_info_t &info = skill_used->time_to_attack(); - RAS_time = 0; + int time = 0; if( loc ) { // At low stamina levels, firing starts getting slow. const item_location gun = p.get_wielded_item(); int sta_percent = ( 100 * p.get_stamina() ) / p.get_stamina_max(); - RAS_time += ( sta_percent < 25 ) ? ( ( 25 - sta_percent ) * 2 ) : 0; + time += ( sta_percent < 25 ) ? ( ( 25 - sta_percent ) * 2 ) : 0; item::reload_option opt = item::reload_option( &p, gun, loc ); - RAS_time += opt.moves(); + time += opt.moves(); } - return std::max( info.min_time, - static_cast( round( info.base_time - info.time_reduction_per_level * p.get_skill_level( - skill_used ) ) ) + RAS_time ); + return time; } static void cycle_action( item &weap, const itype_id &ammo, const tripoint &pos ) @@ -2424,7 +2421,7 @@ double Character::gun_value( const item &weap, int ammo ) const damage_factor += 0.5f * gun_damage.damage_units.front().res_pen; } - int move_cost = NPC_time_to_attack( *this, *weap.type ); + int move_cost = time_to_attack( *this, *weap.type ); if( gun.clip != 0 && gun.clip < 10 ) { // TODO: RELOAD_ONE should get a penalty here int reload_cost = gun.reload_time + encumb( bodypart_id( "hand_l" ) ) + encumb( @@ -2694,12 +2691,11 @@ target_handler::trajectory target_ui::run() break; } - item_location weapon = activity->get_weapon(); - if( weapon->has_flag( flag_RELOAD_AND_SHOOT ) ) { - if( !weapon->ammo_remaining() && activity->reload_loc ) { - you->mod_moves( -RAS_time ); - weapon->reload( get_avatar(), activity->reload_loc, 1 ); - you->burn_energy_arms( - weapon->get_min_str() * static_cast( 0.006f * + if( relevant->has_flag( flag_RELOAD_AND_SHOOT ) ) { + if( !relevant->ammo_remaining() && activity->reload_loc ) { + you->mod_moves( -RAS_time( *you, *relevant, activity->reload_loc ) ); + relevant->reload( get_avatar(), activity->reload_loc, 1 ); + you->burn_energy_arms( - relevant->get_min_str() * static_cast( 0.006f * get_option( "PLAYER_MAX_STAMINA_BASE" ) ) ); activity->reload_loc = item_location(); } From abc6e759d501a1acf56699b92a29e2f78311f747 Mon Sep 17 00:00:00 2001 From: osuphobia Date: Sat, 18 May 2024 09:19:11 +0800 Subject: [PATCH 09/12] Modified switch_ammo for RAS weapon. --- src/ranged.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ranged.cpp b/src/ranged.cpp index c9aa2cd5e25a8..76b85aa3c0eb4 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -3494,6 +3494,9 @@ bool target_ui::action_switch_ammo() ammo = item::find_type( turret->ammo_current() ); range = turret->range(); } + } else if( mode == TargetMode::Fire && relevant->has_flag( flag_RELOAD_AND_SHOOT ) ) { + activity->load_RAS_weapon(); + update_ammo_range_from_gun_mode(); } else { // Leave aiming UI and open reloading UI since // reloading annihilates our aim anyway From 6138f8fddf5db8c82d7e347d30c974396de302df Mon Sep 17 00:00:00 2001 From: osuphobia <78858975+osuphobia@users.noreply.github.com> Date: Sat, 18 May 2024 09:30:37 +0800 Subject: [PATCH 10/12] Apply suggestions from code review Co-authored-by: Kevin Granade Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/ranged.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index 76b85aa3c0eb4..2b956d0992f3f 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -929,6 +929,8 @@ int Character::fire_gun( const tripoint &target, int shots, item &gun, item_loca int hits = 0; // total shots on target int delay = 0; // delayed recoil that has yet to be applied while( curshot != shots ) { + // Special handling for weapons where we supply the ammo separately (i.e. ammo is populated) + // instead of it being loaded into the weapon, reload right before firing. if( !!ammo && !gun.ammo_remaining() ) { Character &you = get_avatar(); gun.reload( you, ammo, 1 ); @@ -1742,7 +1744,7 @@ static std::vector calculate_ranged_chances( } else { prediction.moves = predict_recoil( you, weapon, target, ui.get_sight_dispersion(), aim_type, you.recoil ).moves + time_to_attack( you, *weapon.type ) - + RAS_time( you, weapon, load_loc ); + + RAS_time( you, weapon, load_loc ); } // if the default method is "behind" the selected; e.g. you are in immediate From 39165c26713e4224354d7e7ac9f32db3e68e917b Mon Sep 17 00:00:00 2001 From: osuphobia Date: Sat, 18 May 2024 10:15:51 +0800 Subject: [PATCH 11/12] Remove unused argument. --- src/ranged.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index 2b956d0992f3f..a16c4c1bc9d5a 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -156,7 +156,7 @@ static constexpr int AIF_DURATION_LIMIT = 10; static projectile make_gun_projectile( const item &gun ); static int time_to_attack( const Character &p, const itype &firing ); -static int RAS_time( const Character &p, const item &firing, const item_location &loc ); +static int RAS_time( const Character &p, const item_location &loc ); /** * Handle spent ammo casings and linkages. * @param weap Weapon. @@ -920,7 +920,7 @@ int Character::fire_gun( const tripoint &target, int shots, item &gun, item_loca static_cast( MAX_SKILL ) ) / static_cast( MAX_SKILL * 2 ); itype_id gun_id = gun.typeId(); - int attack_moves = time_to_attack( *this, *gun_id ) + RAS_time( *this, gun, ammo ); + int attack_moves = time_to_attack( *this, *gun_id ) + RAS_time( *this, ammo ); skill_id gun_skill = gun.gun_skill(); add_msg_debug( debugmode::DF_RANGED, "Gun skill (%s) %g", gun_skill.c_str(), get_skill_level( gun_skill ) ) ; @@ -1744,7 +1744,7 @@ static std::vector calculate_ranged_chances( } else { prediction.moves = predict_recoil( you, weapon, target, ui.get_sight_dispersion(), aim_type, you.recoil ).moves + time_to_attack( you, *weapon.type ) - + RAS_time( you, weapon, load_loc ); + + RAS_time( you, load_loc ); } // if the default method is "behind" the selected; e.g. you are in immediate @@ -1984,7 +1984,7 @@ static int print_aim( const target_ui &ui, Character &you, const catacurses::win target_ui::TargetMode::Fire, ctxt, weapon, dispersion, confidence_config, Target_attributes( you.pos(), pos ), pos, load_loc ); - int time = RAS_time( you, weapon, load_loc ); + int time = RAS_time( you, load_loc ); return print_ranged_chance( w, line_number, aim_chances, time ); } @@ -2140,7 +2140,7 @@ int time_to_attack( const Character &p, const itype &firing ) skill_used ) ) ) ); } -int RAS_time( const Character &p, const item &firing, const item_location &loc ) +int RAS_time( const Character &p, const item_location &loc ) { int time = 0; if( loc ) { @@ -2695,7 +2695,7 @@ target_handler::trajectory target_ui::run() if( relevant->has_flag( flag_RELOAD_AND_SHOOT ) ) { if( !relevant->ammo_remaining() && activity->reload_loc ) { - you->mod_moves( -RAS_time( *you, *relevant, activity->reload_loc ) ); + you->mod_moves( -RAS_time( *you, activity->reload_loc ) ); relevant->reload( get_avatar(), activity->reload_loc, 1 ); you->burn_energy_arms( - relevant->get_min_str() * static_cast( 0.006f * get_option( "PLAYER_MAX_STAMINA_BASE" ) ) ); From d909186875b6e178de30e34908ad2b63c9e6ee2c Mon Sep 17 00:00:00 2001 From: osuphobia Date: Thu, 23 May 2024 20:16:35 +0800 Subject: [PATCH 12/12] Modified action_switch_ammo for RAS weapon. --- src/ranged.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index be01d1bc2b4e8..066a3afba5c73 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -3497,7 +3497,9 @@ bool target_ui::action_switch_ammo() range = turret->range(); } } else if( mode == TargetMode::Fire && relevant->has_flag( flag_RELOAD_AND_SHOOT ) ) { - activity->load_RAS_weapon(); + item_location gun = you->get_wielded_item(); + item::reload_option opt = you->select_ammo( gun ); + activity->reload_loc = opt.ammo; update_ammo_range_from_gun_mode(); } else { // Leave aiming UI and open reloading UI since