From 4f94f5fc26ec83abddf91cec258cca70cfa94c1d Mon Sep 17 00:00:00 2001 From: olanti-p Date: Sun, 17 May 2020 17:45:32 +0300 Subject: [PATCH 1/8] Implement activity_actor for ACT_AIM; don't restart activity on each turn. --- src/activity_actor.cpp | 60 +++++++++++++++++++++++++++++++ src/activity_actor.h | 39 ++++++++++++++++++++ src/activity_handlers.cpp | 18 ---------- src/activity_handlers.h | 1 - src/avatar_action.cpp | 53 +++++++++++++-------------- src/avatar_action.h | 3 +- src/ranged.cpp | 76 +++++++++++++++++---------------------- src/ranged.h | 4 +-- 8 files changed, 163 insertions(+), 91 deletions(-) diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index c2611680bcd6a..ccb5893bbf385 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -44,6 +44,65 @@ static const mtype_id mon_zombie_rot( "mon_zombie_rot" ); static const mtype_id mon_skeleton( "mon_skeleton" ); static const mtype_id mon_zombie_crawler( "mon_zombie_crawler" ); +void aim_activity_actor::start( player_activity &act, Character &/*who*/ ) +{ + // Time spent on aiming is determined on the go by the player + act.moves_total = 1; + act.moves_left = 1; +} + +void aim_activity_actor::do_turn( player_activity &act, Character &who ) +{ + if( avatar *you = dynamic_cast( &who ) ) { + avatar_action::aim_do_turn( *you, g->m, *this ); + } else { + debugmsg( "ACT_AIM not implemented for NPCs" ); + aborted = true; + } + first_turn = false; + if( aborted || finished ) { + act.moves_left = 0; + } +} + +void aim_activity_actor::finish( player_activity &act, Character &who ) +{ + act.set_to_null(); + if( reload_requested ) { + // Reload the gun / select different arrows + // May assign ACT_RELOAD + g->reload_wielded( true ); + } +} + +void aim_activity_actor::serialize( JsonOut &jsout ) const +{ + jsout.start_object(); + + jsout.member( "first_turn", first_turn ); + jsout.member( "action", action ); + jsout.member( "snap_to_target", snap_to_target ); + jsout.member( "shifting_view", shifting_view ); + jsout.member( "view_offset", view_offset ); + + jsout.end_object(); +} + +std::unique_ptr aim_activity_actor::deserialize( JsonIn &jsin ) +{ + aim_activity_actor actor = aim_activity_actor(); + + JsonObject data = jsin.get_object(); + + data.read( "first_turn", actor.first_turn ); + data.read( "action", actor.action ); + data.read( "snap_to_target", actor.snap_to_target ); + data.read( "shifting_view", actor.shifting_view ); + data.read( "view_offset", actor.view_offset ); + + return actor.clone(); +} + void dig_activity_actor::start( player_activity &act, Character & ) { act.moves_total = moves_total; @@ -646,6 +705,7 @@ namespace activity_actors // Please keep this alphabetically sorted const std::unordered_map( * )( JsonIn & )> deserialize_functions = { + { activity_id( "ACT_AIM" ), &aim_activity_actor::deserialize }, { activity_id( "ACT_CONSUME" ), &consume_activity_actor::deserialize }, { activity_id( "ACT_DIG" ), &dig_activity_actor::deserialize }, { activity_id( "ACT_DIG_CHANNEL" ), &dig_channel_activity_actor::deserialize }, diff --git a/src/activity_actor.h b/src/activity_actor.h index d487f75366be9..8efbe5a6959ab 100644 --- a/src/activity_actor.h +++ b/src/activity_actor.h @@ -102,6 +102,45 @@ class activity_actor virtual void serialize( JsonOut &jsout ) const = 0; }; +class aim_activity_actor : public activity_actor +{ + public: + bool first_turn = true; + std::string action = ""; + bool snap_to_target = false; + bool shifting_view = false; + tripoint view_offset = tripoint_zero; + + /** Target UI requested to abort aiming */ + bool aborted = false; + + /** Target UI requested to fire */ + bool finished = false; + + /** + * Target UI requested to abort aiming and reload weapon + * Implies aborted = true + */ + bool reload_requested = false; + + aim_activity_actor() = default; + + activity_id get_type() const override { + return activity_id( "ACT_AIM" ); + } + + void start( player_activity &act, Character &who ) override; + void do_turn( player_activity &act, Character &who ) override; + void finish( player_activity &act, Character &who ) override; + + std::unique_ptr clone() const override { + return std::make_unique( *this ); + } + + void serialize( JsonOut &jsout ) const override; + static std::unique_ptr deserialize( JsonIn &jsin ); +}; + class dig_activity_actor : public activity_actor { private: diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index 1bbc5791bd6e3..ce3b009e3557b 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -107,7 +107,6 @@ static const efftype_id effect_sheared( "sheared" ); #define dbg(x) DebugLog((x),D_GAME) << __FILE__ << ":" << __LINE__ << ": " static const activity_id ACT_ADV_INVENTORY( "ACT_ADV_INVENTORY" ); -static const activity_id ACT_AIM( "ACT_AIM" ); static const activity_id ACT_ARMOR_LAYERS( "ACT_ARMOR_LAYERS" ); static const activity_id ACT_ATM( "ACT_ATM" ); static const activity_id ACT_AUTODRIVE( "ACT_AUTODRIVE" ); @@ -290,7 +289,6 @@ activity_handlers::do_turn_functions = { { ACT_VIBE, vibe_do_turn }, { ACT_HAND_CRANK, hand_crank_do_turn }, { ACT_OXYTORCH, oxytorch_do_turn }, - { ACT_AIM, aim_do_turn }, { ACT_WEAR, wear_do_turn }, { ACT_MULTIPLE_FISH, multiple_fish_do_turn }, { ACT_MULTIPLE_CONSTRUCTION, multiple_construction_do_turn }, @@ -388,7 +386,6 @@ activity_handlers::finish_functions = { { ACT_DISASSEMBLE, disassemble_finish }, { ACT_VIBE, vibe_finish }, { ACT_ATM, atm_finish }, - { ACT_AIM, aim_finish }, { ACT_EAT_MENU, eat_menu_finish }, { ACT_CONSUME_FOOD_MENU, eat_menu_finish }, { ACT_CONSUME_DRINK_MENU, eat_menu_finish }, @@ -2962,15 +2959,6 @@ void activity_handlers::meditate_finish( player_activity *act, player *p ) act->set_to_null(); } -void activity_handlers::aim_do_turn( player_activity *act, player * ) -{ - if( act->index == 0 ) { - g->m.invalidate_map_cache( g->get_levz() ); - g->m.build_map_cache( g->get_levz() ); - avatar_action::aim_do_turn( g->u, g->m ); - } -} - void activity_handlers::wear_do_turn( player_activity *act, player *p ) { activity_on_turn_wear( *act, *p ); @@ -3848,12 +3836,6 @@ void activity_handlers::atm_finish( player_activity *act, player * ) } } -void activity_handlers::aim_finish( player_activity *, player * ) -{ - // Aim bails itself by resetting itself every turn, - // you only re-enter if it gets set again. - return; -} void activity_handlers::eat_menu_finish( player_activity *, player * ) { // Only exists to keep the eat activity alive between turns diff --git a/src/activity_handlers.h b/src/activity_handlers.h index 5455d821f0b26..d44ecd04a48d6 100644 --- a/src/activity_handlers.h +++ b/src/activity_handlers.h @@ -139,7 +139,6 @@ void vibe_do_turn( player_activity *act, player *p ); void hand_crank_do_turn( player_activity *act, player *p ); void multiple_chop_planks_do_turn( player_activity *act, player *p ); void oxytorch_do_turn( player_activity *act, player *p ); -void aim_do_turn( player_activity *act, player *p ); void wear_do_turn( player_activity *act, player *p ); void eat_menu_do_turn( player_activity *act, player *p ); void consume_food_menu_do_turn( player_activity *act, player *p ); diff --git a/src/avatar_action.cpp b/src/avatar_action.cpp index 88d98a088f762..39c64d6395340 100644 --- a/src/avatar_action.cpp +++ b/src/avatar_action.cpp @@ -823,7 +823,7 @@ static bool can_fire_turret( avatar &you, const map &m, const turret_data &turre return false; } -void avatar_action::aim_do_turn( avatar &you, map &m ) +void avatar_action::aim_do_turn( avatar &you, map &m, aim_activity_actor &activity ) { targeting_data &args = you.get_targeting_data(); @@ -853,14 +853,10 @@ void avatar_action::aim_do_turn( avatar &you, map &m ) you.cancel_activity(); return; } - - int reload_time = 0; gun_mode gun = weapon->gun_current_mode(); - // TODO: use MODERATE_EXERCISE if firing a bow - you.increase_activity_level( LIGHT_EXERCISE ); - // TODO: move handling "RELOAD_AND_SHOOT" flagged guns to a separate function. + int moves_before_reload = you.moves; if( gun->has_flag( flag_RELOAD_AND_SHOOT ) ) { if( !gun->ammo_remaining() ) { const auto ammo_location_is_valid = [&]() -> bool { @@ -884,6 +880,7 @@ void avatar_action::aim_do_turn( avatar &you, map &m ) // Menu canceled return; } + int reload_time = 0; reload_time += opt.moves(); if( !gun->reload( you, std::move( opt.ammo ), 1 ) ) { // Reload not allowed @@ -897,43 +894,47 @@ void avatar_action::aim_do_turn( avatar &you, map &m ) int sta_percent = ( 100 * you.get_stamina() ) / you.get_stamina_max(); reload_time += ( sta_percent < 25 ) ? ( ( 25 - sta_percent ) * 2 ) : 0; + you.moves -= reload_time; g->refresh_all(); } } + int moves_before_ui = you.moves; g->temp_exit_fullscreen(); m.draw( g->w_terrain, you.pos() ); - bool reload_requested; - target_handler::trajectory trajectory = target_handler::mode_fire( you, *weapon, reload_requested ); + target_handler::trajectory trajectory = target_handler::mode_fire( you, *weapon, activity ); - //may be changed in target_ui - gun = weapon->gun_current_mode(); + if( activity.aborted ) { + if( gun->has_flag( flag_RELOAD_AND_SHOOT ) ) { + bool refund = activity.first_turn && you.moves == moves_before_ui; + int moves_before_unload = you.moves; - if( trajectory.empty() ) { - bool not_aiming = you.activity.id() != ACT_AIM; - if( not_aiming && gun->has_flag( flag_RELOAD_AND_SHOOT ) ) { - const auto previous_moves = you.moves; item_location loc = item_location( you, gun.target ); g->unload( loc ); + // Give back time for unloading as essentially nothing has been done. - // Note that reload_time has not been applied either. - you.moves = previous_moves; + if( refund ) { + you.moves = moves_before_unload; + } } g->reenter_fullscreen(); - - if( reload_requested ) { - // Reload the gun / select different arrows - g->reload_wielded( true ); - } return; } + if( trajectory.empty() ) { + // Still aiming + return; + } + activity.finished = true; + + // TODO: move everything below to aim_activity_actor::finish() + // Recenter our view g->draw_ter(); wrefresh( g->w_terrain ); g->draw_panels(); - you.moves -= reload_time; - + // Update gun mode since target UI may change it + gun = weapon->gun_current_mode(); int shots_fired = you.fire_gun( trajectory.back(), gun.qty, *gun ); // TODO: bionic power cost of firing should be derived from a value of the relevant weapon. @@ -961,14 +962,14 @@ void avatar_action::fire_wielded_weapon( avatar &you, map &m ) targeting_data args = targeting_data::use_wielded(); you.set_targeting_data( args ); - avatar_action::aim_do_turn( you, m ); + you.assign_activity( aim_activity_actor(), false ); } void avatar_action::fire_ranged_mutation( avatar &you, map &m, const item &fake_gun ) { targeting_data args = targeting_data::use_mutation( fake_gun ); you.set_targeting_data( args ); - avatar_action::aim_do_turn( you, m ); + you.assign_activity( aim_activity_actor(), false ); } void avatar_action::fire_ranged_bionic( avatar &you, map &m, const item &fake_gun, @@ -976,7 +977,7 @@ void avatar_action::fire_ranged_bionic( avatar &you, map &m, const item &fake_gu { targeting_data args = targeting_data::use_bionic( fake_gun, cost_per_shot ); you.set_targeting_data( args ); - avatar_action::aim_do_turn( you, m ); + you.assign_activity( aim_activity_actor(), false ); } void avatar_action::fire_turret_manual( avatar &you, map &m, turret_data &turret ) diff --git a/src/avatar_action.h b/src/avatar_action.h index 4d9741e2ea7c4..ce8b773b0ee2e 100644 --- a/src/avatar_action.h +++ b/src/avatar_action.h @@ -11,6 +11,7 @@ class item; class item_location; class map; class turret_data; +class aim_activity_actor; namespace avatar_action { @@ -44,7 +45,7 @@ void mend( avatar &you, item_location loc ); * Validates avatar's targeting_data, then handles interactive parts of gun firing * (target selection, aiming, etc.) */ -void aim_do_turn( avatar &you, map &m ); +void aim_do_turn( avatar &you, map &m, aim_activity_actor &activity ); /** Checks if the wielded weapon is a gun and can be fired then starts interactive aiming */ void fire_wielded_weapon( avatar &you, map &m ); diff --git a/src/ranged.cpp b/src/ranged.cpp index 96630861b1659..4788c3583d843 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -139,6 +139,8 @@ class target_ui bool no_fail = false; // Spell does not require mana bool no_mana = false; + // Relevant activity + aim_activity_actor *activity = nullptr; enum class ExitCode { Abort, @@ -147,10 +149,8 @@ class target_ui Reload }; - // Initialize UI and run the event loop. - // Uses public members to determine UI contents - // If exit_code != nullptr, exit code will be written into provided address - target_handler::trajectory run( player &pc, ExitCode *exit_code = nullptr ); + // Initialize UI and run the event loop + target_handler::trajectory run( player &pc ); private: enum class Status { @@ -339,19 +339,17 @@ class target_ui }; target_handler::trajectory target_handler::mode_fire( player &pc, item &weapon, - bool &reload_requested ) + aim_activity_actor &activity ) { target_ui ui = target_ui(); ui.mode = target_ui::TargetMode::Fire; + ui.activity = &activity; ui.relevant = &weapon; gun_mode gun = weapon.gun_current_mode(); ui.range = gun.target->gun_range( &pc ); ui.ammo = gun->ammo_data(); - target_ui::ExitCode exit_code; - trajectory result = ui.run( pc, &exit_code ); - reload_requested = exit_code == target_ui::ExitCode::Reload; - return result; + return ui.run( pc ); } target_handler::trajectory target_handler::mode_throw( player &pc, item &relevant, @@ -1892,7 +1890,7 @@ double player::gun_value( const item &weap, int ammo ) const return std::max( 0.0, gun_value ); } -target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) +target_handler::trajectory target_ui::run( player &pc ) { if( mode == TargetMode::Spell && !no_mana && !casting->can_cast( pc ) ) { pc.add_msg_if_player( m_bad, _( "You don't have enough %s to cast this spell" ), @@ -1920,26 +1918,22 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) bool attack_was_confirmed = false; bool reentered = false; tripoint manual_view_offset = pc.view_offset; - if( mode == TargetMode::Fire ) { - if( pc.activity.id() == ACT_AIM ) { - // We were in this UI during previous turn... - reentered = true; - std::string act_data = pc.activity.str_values[0]; - if( act_data == "AIM" ) { - // ...and ran out of moves while aiming. - } else { - // ...and selected 'aim and shoot', but ran out of moves. - // So, skip retrieving input and go straight to the action. - action = act_data; - attack_was_confirmed = true; - } - // Load state to keep the ui consistent across turns - snap_to_target = pc.activity.str_values[1] == "snap"; - shifting_view = pc.activity.values[0] == 1; - manual_view_offset = pc.activity.coords[0]; - // Clear the activity, we'll re-set it later if we need to. - pc.cancel_activity(); + if( mode == TargetMode::Fire && !activity->first_turn ) { + // We were in this UI during previous turn... + reentered = true; + std::string act_data = activity->action; + if( act_data == "AIM" ) { + // ...and ran out of moves while aiming. + } else { + // ...and selected 'aim and shoot', but ran out of moves. + // So, skip retrieving input and go straight to the action. + action = act_data; + attack_was_confirmed = true; } + // Load state to keep the ui consistent across turns + snap_to_target = activity->snap_to_target; + shifting_view = activity->shifting_view; + manual_view_offset = activity->view_offset; } // Restore view to how it was during previos turn in this ui @@ -2072,6 +2066,9 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) switch( loop_exit_code ) { case ExitCode::Abort: { traj.clear(); + if( mode == TargetMode::Fire ) { + activity->aborted = true; + } break; } case ExitCode::Fire: { @@ -2080,29 +2077,22 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) break; } case ExitCode::Timeout: { - // We've ran out of moves. Set activity to ACT_AIM, so we'll - // automatically re-enter the aiming UI on the next turn. - // pc.activity.str_values[0] remembers which action, AIM or *_SHOT, - // we didn't have the time to finish. + // We've ran out of moves, save UI state traj.clear(); - pc.assign_activity( ACT_AIM, 0, 0 ); - pc.activity.str_values.push_back( timed_out_action ); - // Save UI state - pc.activity.str_values.push_back( snap_to_target ? "snap" : "nosnap" ); - pc.activity.values.push_back( shifting_view ? 1 : 0 ); - pc.activity.coords.push_back( manual_view_offset ); + activity->action = timed_out_action; + activity->snap_to_target = snap_to_target; + activity->shifting_view = shifting_view; + activity->view_offset = manual_view_offset; break; } case ExitCode::Reload: { - // Calling function will handle reloading for us traj.clear(); + activity->aborted = true; + activity->reload_requested = true; break; } } - if( exit_code ) { - *exit_code = loop_exit_code; - } return traj; } diff --git a/src/ranged.h b/src/ranged.h index eae11bfc44485..2d6669ece6c26 100644 --- a/src/ranged.h +++ b/src/ranged.h @@ -9,6 +9,7 @@ class JsonIn; class JsonOut; +class aim_activity_actor; class item; class player; class spell; @@ -75,9 +76,8 @@ using trajectory = std::vector; /** * Firing ranged weapon. This mode allows spending moves on aiming. - * 'reload_requested' is set to 'true' if user aborted aiming to reload the gun/change ammo */ -trajectory mode_fire( player &pc, item &weapon, bool &reload_requested ); +trajectory mode_fire( player &pc, item &weapon, aim_activity_actor &activity ); /** Throwing item */ trajectory mode_throw( player &pc, item &relevant, bool blind_throwing ); From c50d5a1c15fcb74d43427fbf3ab746b2c192a409 Mon Sep 17 00:00:00 2001 From: olanti-p Date: Sun, 17 May 2020 17:45:40 +0300 Subject: [PATCH 2/8] Fix clang build --- src/activity_actor.cpp | 2 +- src/avatar_action.cpp | 12 +++++++----- src/avatar_action.h | 6 +++--- src/bionics.cpp | 5 ++--- src/game.cpp | 2 +- src/handle_action.cpp | 4 ++-- src/mutation.cpp | 2 +- 7 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index ccb5893bbf385..ab3c045f7d91c 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -65,7 +65,7 @@ void aim_activity_actor::do_turn( player_activity &act, Character &who ) } } -void aim_activity_actor::finish( player_activity &act, Character &who ) +void aim_activity_actor::finish( player_activity &act, Character &/*who*/ ) { act.set_to_null(); if( reload_requested ) { diff --git a/src/avatar_action.cpp b/src/avatar_action.cpp index 39c64d6395340..607e0d4a11b82 100644 --- a/src/avatar_action.cpp +++ b/src/avatar_action.cpp @@ -850,13 +850,13 @@ void avatar_action::aim_do_turn( avatar &you, map &m, aim_activity_actor &activi } if( !weapon || !can_fire_weapon( you, m, *weapon ) ) { - you.cancel_activity(); + activity.aborted = true; return; } gun_mode gun = weapon->gun_current_mode(); // TODO: move handling "RELOAD_AND_SHOOT" flagged guns to a separate function. - int moves_before_reload = you.moves; + // TODO: handle cases when reloading takes a long time if( gun->has_flag( flag_RELOAD_AND_SHOOT ) ) { if( !gun->ammo_remaining() ) { const auto ammo_location_is_valid = [&]() -> bool { @@ -878,12 +878,14 @@ void avatar_action::aim_do_turn( avatar &you, map &m, aim_activity_actor &activi weapon, you.ammo_location ) : you.select_ammo( *gun ); if( !opt ) { // Menu canceled + activity.aborted = true; return; } int reload_time = 0; reload_time += opt.moves(); if( !gun->reload( you, std::move( opt.ammo ), 1 ) ) { // Reload not allowed + activity.aborted = true; return; } @@ -944,7 +946,7 @@ void avatar_action::aim_do_turn( avatar &you, map &m, aim_activity_actor &activi g->reenter_fullscreen(); } -void avatar_action::fire_wielded_weapon( avatar &you, map &m ) +void avatar_action::fire_wielded_weapon( avatar &you ) { item &weapon = you.weapon; if( weapon.is_gunmod() ) { @@ -965,14 +967,14 @@ void avatar_action::fire_wielded_weapon( avatar &you, map &m ) you.assign_activity( aim_activity_actor(), false ); } -void avatar_action::fire_ranged_mutation( avatar &you, map &m, const item &fake_gun ) +void avatar_action::fire_ranged_mutation( avatar &you, const item &fake_gun ) { targeting_data args = targeting_data::use_mutation( fake_gun ); you.set_targeting_data( args ); you.assign_activity( aim_activity_actor(), false ); } -void avatar_action::fire_ranged_bionic( avatar &you, map &m, const item &fake_gun, +void avatar_action::fire_ranged_bionic( avatar &you, const item &fake_gun, units::energy cost_per_shot ) { targeting_data args = targeting_data::use_bionic( fake_gun, cost_per_shot ); diff --git a/src/avatar_action.h b/src/avatar_action.h index ce8b773b0ee2e..ea3e028cc0aa1 100644 --- a/src/avatar_action.h +++ b/src/avatar_action.h @@ -48,13 +48,13 @@ void mend( avatar &you, item_location loc ); void aim_do_turn( avatar &you, map &m, aim_activity_actor &activity ); /** Checks if the wielded weapon is a gun and can be fired then starts interactive aiming */ -void fire_wielded_weapon( avatar &you, map &m ); +void fire_wielded_weapon( avatar &you ); /** Stores fake gun specified by the mutation and starts interactive aiming */ -void fire_ranged_mutation( avatar &you, map &m, const item &fake_gun ); +void fire_ranged_mutation( avatar &you, const item &fake_gun ); /** Stores fake gun specified by the bionic and starts interactive aiming */ -void fire_ranged_bionic( avatar &you, map &m, const item &fake_gun, units::energy cost_per_shot ); +void fire_ranged_bionic( avatar &you, const item &fake_gun, units::energy cost_per_shot ); /** * Checks if the player can manually (with their 2 hands, not via vehicle controls) diff --git a/src/bionics.cpp b/src/bionics.cpp index 263637a8d77ec..b5ad7027f5190 100644 --- a/src/bionics.cpp +++ b/src/bionics.cpp @@ -571,8 +571,7 @@ bool Character::activate_bionic( int b, bool eff_only ) add_msg_activate(); refund_power(); // Power usage calculated later, in avatar_action::fire g->refresh_all(); - avatar_action::fire_ranged_bionic( g->u, g->m, item( bio.info().fake_item ), - bio.info().power_activate ); + avatar_action::fire_ranged_bionic( g->u, item( bio.info().fake_item ), bio.info().power_activate ); } else if( bio.info().has_flag( flag_BIO_WEAPON ) ) { if( weapon.has_flag( flag_NO_UNWIELD ) ) { add_msg_if_player( m_info, _( "Deactivate your %s first!" ), weapon.tname() ); @@ -595,8 +594,8 @@ bool Character::activate_bionic( int b, bool eff_only ) weapon.invlet = '#'; if( bio.ammo_count > 0 ) { weapon.ammo_set( bio.ammo_loaded, bio.ammo_count ); - avatar_action::fire_wielded_weapon( g->u, g->m ); g->refresh_all(); + avatar_action::fire_wielded_weapon( g->u ); } } else if( bio.id == bio_ears && has_active_bionic( bio_earplugs ) ) { add_msg_activate(); diff --git a/src/game.cpp b/src/game.cpp index ac6fbd667e33e..160edfc09c6ae 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -7251,7 +7251,7 @@ void game::list_items_monsters() } if( ret == game::vmenu_ret::FIRE ) { - avatar_action::fire_wielded_weapon( u, m ); + avatar_action::fire_wielded_weapon( u ); } reenter_fullscreen(); } diff --git a/src/handle_action.cpp b/src/handle_action.cpp index a953ba7e71d49..d1726a179d355 100644 --- a/src/handle_action.cpp +++ b/src/handle_action.cpp @@ -1358,7 +1358,7 @@ static void fire() } if( u.weapon.is_gun() && !u.weapon.gun_current_mode().melee() ) { - avatar_action::fire_wielded_weapon( g->u, g->m ); + avatar_action::fire_wielded_weapon( g->u ); } else if( u.weapon.current_reach_range( u ) > 1 ) { if( u.has_effect( effect_relax_gas ) ) { if( one_in( 8 ) ) { @@ -2050,7 +2050,7 @@ bool game::handle_action() case ACTION_FIRE_BURST: { gun_mode_id original_mode = u.weapon.gun_get_mode_id(); if( u.weapon.gun_set_mode( gun_mode_id( "AUTO" ) ) ) { - avatar_action::fire_wielded_weapon( u, m ); + avatar_action::fire_wielded_weapon( u ); u.weapon.gun_set_mode( original_mode ); } break; diff --git a/src/mutation.cpp b/src/mutation.cpp index 3e0013593feb2..163cbd358d516 100644 --- a/src/mutation.cpp +++ b/src/mutation.cpp @@ -626,7 +626,7 @@ void Character::activate_mutation( const trait_id &mut ) } else if( !mdata.ranged_mutation.empty() ) { add_msg_if_player( mdata.ranged_mutation_message() ); g->refresh_all(); - avatar_action::fire_ranged_mutation( g->u, g->m, item( mdata.ranged_mutation ) ); + avatar_action::fire_ranged_mutation( g->u, item( mdata.ranged_mutation ) ); tdata.powered = false; return; } From 60bf40e818e9bee955b34cee698fe4f676bbf62b Mon Sep 17 00:00:00 2001 From: olanti-p Date: Sun, 17 May 2020 17:45:47 +0300 Subject: [PATCH 3/8] Integrate targeting_data into aim_activity_actor --- src/activity_actor.cpp | 78 ++++++++++++++++++++++++++++++++++++++++++ src/activity_actor.h | 27 +++++++++++++-- src/avatar.cpp | 14 -------- src/avatar.h | 11 ------ src/avatar_action.cpp | 43 ++++------------------- src/ranged.cpp | 60 ++------------------------------ src/ranged.h | 56 +----------------------------- src/savegame_json.cpp | 39 --------------------- 8 files changed, 113 insertions(+), 215 deletions(-) diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index ab3c045f7d91c..78d15bba44105 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -44,6 +44,56 @@ static const mtype_id mon_zombie_rot( "mon_zombie_rot" ); static const mtype_id mon_skeleton( "mon_skeleton" ); static const mtype_id mon_zombie_crawler( "mon_zombie_crawler" ); +template<> +struct enum_traits { + static constexpr aim_activity_actor::WeaponSource last = + aim_activity_actor::WeaponSource::NumWeaponSources; +}; + +namespace io +{ +using WS = aim_activity_actor::WeaponSource; + +template<> +std::string enum_to_string( WS data ) +{ + switch( data ) { + // *INDENT-OFF* + case WS::Wielded: return "Wielded"; + case WS::Bionic: return "Bionic"; + case WS::Mutation: return "Mutation"; + // *INDENT-ON* + case WS::NumWeaponSources: + break; + } + debugmsg( "Invalid weapon source" ); + abort(); +} +} // namespace io + +aim_activity_actor aim_activity_actor::use_wielded() +{ + return aim_activity_actor(); +} + +aim_activity_actor aim_activity_actor::use_bionic( const item &fake_gun, + const units::energy &cost_per_shot ) +{ + aim_activity_actor act = aim_activity_actor(); + act.weapon_source = WeaponSource::Bionic; + act.bp_cost_per_shot = cost_per_shot; + act.fake_weapon = shared_ptr_fast( new item( fake_gun ) ); + return act; +} + +aim_activity_actor aim_activity_actor::use_mutation( const item &fake_gun ) +{ + aim_activity_actor act = aim_activity_actor(); + act.weapon_source = WeaponSource::Mutation; + act.fake_weapon = shared_ptr_fast( new item( fake_gun ) ); + return act; +} + void aim_activity_actor::start( player_activity &act, Character &/*who*/ ) { // Time spent on aiming is determined on the go by the player @@ -79,6 +129,11 @@ void aim_activity_actor::serialize( JsonOut &jsout ) const { jsout.start_object(); + jsout.member( "weapon_source", weapon_source ); + if( weapon_source == WeaponSource::Bionic || weapon_source == WeaponSource::Mutation ) { + jsout.member( "fake_weapon", *fake_weapon ); + } + jsout.member( "bp_cost_per_shot", bp_cost_per_shot ); jsout.member( "first_turn", first_turn ); jsout.member( "action", action ); jsout.member( "snap_to_target", snap_to_target ); @@ -94,6 +149,12 @@ std::unique_ptr aim_activity_actor::deserialize( JsonIn &jsin ) JsonObject data = jsin.get_object(); + data.read( "weapon_source", actor.weapon_source ); + if( actor.weapon_source == WeaponSource::Bionic || actor.weapon_source == WeaponSource::Mutation ) { + actor.fake_weapon = shared_ptr_fast( new item() ); + data.read( "fake_weapon", *actor.fake_weapon ); + } + data.read( "bp_cost_per_shot", actor.bp_cost_per_shot ); data.read( "first_turn", actor.first_turn ); data.read( "action", actor.action ); data.read( "snap_to_target", actor.snap_to_target ); @@ -103,6 +164,23 @@ std::unique_ptr aim_activity_actor::deserialize( JsonIn &jsin ) return actor.clone(); } +item *aim_activity_actor::get_weapon() +{ + switch( weapon_source ) { + case WeaponSource::Wielded: + // Check for lost gun (e.g. yanked by zombie technician) + // TODO: check that this is the same gun that was used to start aiming + return g->u.weapon.is_null() ? nullptr : &g->u.weapon; + case WeaponSource::Bionic: + case WeaponSource::Mutation: + // TODO: check if the player lost relevant bionic/mutation + return fake_weapon.get(); + default: + debugmsg( "Invalid weapon source value" ); + return nullptr; + } +} + void dig_activity_actor::start( player_activity &act, Character & ) { act.moves_total = moves_total; diff --git a/src/activity_actor.h b/src/activity_actor.h index 8efbe5a6959ab..e18dd4f3659d0 100644 --- a/src/activity_actor.h +++ b/src/activity_actor.h @@ -9,8 +9,10 @@ #include "clone_ptr.h" #include "item_location.h" +#include "memory_fast.h" #include "point.h" #include "type_id.h" +#include "units.h" class Character; class JsonIn; @@ -105,26 +107,43 @@ class activity_actor class aim_activity_actor : public activity_actor { public: + enum class WeaponSource { + Wielded, + Bionic, + Mutation, + NumWeaponSources + }; + + WeaponSource weapon_source = WeaponSource::Wielded; + shared_ptr_fast fake_weapon = nullptr; + units::energy bp_cost_per_shot = 0_J; bool first_turn = true; std::string action = ""; bool snap_to_target = false; bool shifting_view = false; tripoint view_offset = tripoint_zero; - /** Target UI requested to abort aiming */ bool aborted = false; - /** Target UI requested to fire */ bool finished = false; - /** * Target UI requested to abort aiming and reload weapon * Implies aborted = true */ bool reload_requested = false; + aim_activity_actor() = default; + /** Aiming wielded gun */ + static aim_activity_actor use_wielded(); + + /** Aiming fake gun provided by a bionic */ + static aim_activity_actor use_bionic( const item &fake_gun, const units::energy &cost_per_shot ); + + /** Aiming fake gun provided by a mutation */ + static aim_activity_actor use_mutation( const item &fake_gun ); + activity_id get_type() const override { return activity_id( "ACT_AIM" ); } @@ -139,6 +158,8 @@ class aim_activity_actor : public activity_actor void serialize( JsonOut &jsout ) const override; static std::unique_ptr deserialize( JsonIn &jsin ); + + item *get_weapon(); }; class dig_activity_actor : public activity_actor diff --git a/src/avatar.cpp b/src/avatar.cpp index ca3832f1e006a..f73fe95532d5b 100644 --- a/src/avatar.cpp +++ b/src/avatar.cpp @@ -1635,20 +1635,6 @@ bool avatar::invoke_item( item *used, const std::string &method ) return Character::invoke_item( used, method ); } -targeting_data &avatar::get_targeting_data() -{ - if( tdata == nullptr ) { - debugmsg( "Tried to get targeting data before setting it" ); - tdata.reset( new targeting_data() ); - } - return *tdata; -} - -void avatar::set_targeting_data( const targeting_data &td ) -{ - tdata.reset( new targeting_data( td ) ); -} - points_left::points_left() { limit = MULTI_POOL; diff --git a/src/avatar.h b/src/avatar.h index 2b21b143a7d49..17fcb5e771c2f 100644 --- a/src/avatar.h +++ b/src/avatar.h @@ -36,7 +36,6 @@ class mission_debug; } // namespace debug_menu struct mtype; struct points_left; -struct targeting_data; // Monster visible in different directions (safe mode & compass) struct monster_visible_info { @@ -263,16 +262,6 @@ class avatar : public player int per_upgrade = 0; monster_visible_info mon_visible; - - /** Targeting data used for aiming the player's weapon across turns. */ - shared_ptr_fast tdata; - - public: - /** Accessor method for weapon targeting data. */ - targeting_data &get_targeting_data(); - - /** Mutator method for weapon targeting data. */ - void set_targeting_data( const targeting_data &td ); }; struct points_left { diff --git a/src/avatar_action.cpp b/src/avatar_action.cpp index 607e0d4a11b82..4d79d64810a7c 100644 --- a/src/avatar_action.cpp +++ b/src/avatar_action.cpp @@ -825,30 +825,7 @@ static bool can_fire_turret( avatar &you, const map &m, const turret_data &turre void avatar_action::aim_do_turn( avatar &you, map &m, aim_activity_actor &activity ) { - targeting_data &args = you.get_targeting_data(); - - item *weapon = nullptr; - switch( args.weapon_source ) { - case WEAPON_SOURCE_WIELDED: - // TODO: if wielding a gun, check that this is the same gun that was used to start aiming - if( !you.weapon.is_null() ) { - // Gun wasn't lost (e.g. yanked by zombie technician) - weapon = &you.weapon; - } - break; - - case WEAPON_SOURCE_BIONIC: - case WEAPON_SOURCE_MUTATION: - // TODO: this should check if the player lost relevant bionic/mutation - weapon = args.cached_fake_weapon.get(); - break; - - case WEAPON_SOURCE_INVALID: - case NUM_WEAPON_SOURCES: - debugmsg( "Expected valid targeting data" ); - break; - } - + item *weapon = activity.get_weapon(); if( !weapon || !can_fire_weapon( you, m, *weapon ) ) { activity.aborted = true; return; @@ -904,7 +881,7 @@ void avatar_action::aim_do_turn( avatar &you, map &m, aim_activity_actor &activi g->temp_exit_fullscreen(); m.draw( g->w_terrain, you.pos() ); - target_handler::trajectory trajectory = target_handler::mode_fire( you, *weapon, activity ); + target_handler::trajectory trajectory = target_handler::mode_fire( you, activity ); if( activity.aborted ) { if( gun->has_flag( flag_RELOAD_AND_SHOOT ) ) { @@ -940,8 +917,8 @@ void avatar_action::aim_do_turn( avatar &you, map &m, aim_activity_actor &activi int shots_fired = you.fire_gun( trajectory.back(), gun.qty, *gun ); // TODO: bionic power cost of firing should be derived from a value of the relevant weapon. - if( shots_fired && ( args.bp_cost_per_shot > 0_J ) ) { - you.mod_power_level( -args.bp_cost_per_shot * shots_fired ); + if( shots_fired && ( activity.bp_cost_per_shot > 0_J ) ) { + you.mod_power_level( -activity.bp_cost_per_shot * shots_fired ); } g->reenter_fullscreen(); } @@ -962,24 +939,18 @@ void avatar_action::fire_wielded_weapon( avatar &you ) return; } - targeting_data args = targeting_data::use_wielded(); - you.set_targeting_data( args ); - you.assign_activity( aim_activity_actor(), false ); + you.assign_activity( aim_activity_actor::use_wielded(), false ); } void avatar_action::fire_ranged_mutation( avatar &you, const item &fake_gun ) { - targeting_data args = targeting_data::use_mutation( fake_gun ); - you.set_targeting_data( args ); - you.assign_activity( aim_activity_actor(), false ); + you.assign_activity( aim_activity_actor::use_mutation( fake_gun ), false ); } void avatar_action::fire_ranged_bionic( avatar &you, const item &fake_gun, units::energy cost_per_shot ) { - targeting_data args = targeting_data::use_bionic( fake_gun, cost_per_shot ); - you.set_targeting_data( args ); - you.assign_activity( aim_activity_actor(), false ); + you.assign_activity( aim_activity_actor::use_bionic( fake_gun, cost_per_shot ), false ); } void avatar_action::fire_turret_manual( avatar &you, map &m, turret_data &turret ) diff --git a/src/ranged.cpp b/src/ranged.cpp index 4788c3583d843..75631d456b67e 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -338,14 +338,13 @@ class target_ui void on_target_accepted( player &pc, bool harmful ); }; -target_handler::trajectory target_handler::mode_fire( player &pc, item &weapon, - aim_activity_actor &activity ) +target_handler::trajectory target_handler::mode_fire( player &pc, aim_activity_actor &activity ) { target_ui ui = target_ui(); ui.mode = target_ui::TargetMode::Fire; ui.activity = &activity; - ui.relevant = &weapon; - gun_mode gun = weapon.gun_current_mode(); + ui.relevant = activity.get_weapon(); + gun_mode gun = ui.relevant->gun_current_mode(); ui.range = gun.target->gun_range( &pc ); ui.ammo = gun->ammo_data(); @@ -432,59 +431,6 @@ target_handler::trajectory target_handler::mode_spell( player &pc, spell_id sp, return mode_spell( pc, g->u.magic.get_spell( sp ), no_fail, no_mana ); } -bool targeting_data::is_valid() const -{ - return weapon_source != WEAPON_SOURCE_INVALID; -} - -targeting_data targeting_data::use_wielded() -{ - return targeting_data{ - WEAPON_SOURCE_WIELDED, - nullptr, - 0_J, - }; -} - -targeting_data targeting_data::use_bionic( const item &fake_gun, - const units::energy &cost_per_shot ) -{ - return targeting_data{ - WEAPON_SOURCE_BIONIC, - shared_ptr_fast( new item( fake_gun ) ), - cost_per_shot - }; -} - -targeting_data targeting_data::use_mutation( const item &fake_gun ) -{ - return targeting_data{ - WEAPON_SOURCE_MUTATION, - shared_ptr_fast( new item( fake_gun ) ), - 0_J - }; -} - -namespace io -{ -template<> -std::string enum_to_string( weapon_source_enum data ) -{ - switch( data ) { - // *INDENT-OFF* - case WEAPON_SOURCE_INVALID: return "WS_INVALID"; - case WEAPON_SOURCE_WIELDED: return "WS_WIELDED"; - case WEAPON_SOURCE_BIONIC: return "WS_BIONIC"; - case WEAPON_SOURCE_MUTATION: return "WS_MUTATION"; - // *INDENT-ON* - case NUM_WEAPON_SOURCES: - break; - } - debugmsg( "Invalid weapon source" ); - abort(); -} -} // namespace io - static double occupied_tile_fraction( m_size target_size ) { switch( target_size ) { diff --git a/src/ranged.h b/src/ranged.h index 2d6669ece6c26..95f4148554b4e 100644 --- a/src/ranged.h +++ b/src/ranged.h @@ -3,12 +3,8 @@ #include -#include "memory_fast.h" #include "type_id.h" -#include "units.h" -class JsonIn; -class JsonOut; class aim_activity_actor; class item; class player; @@ -18,56 +14,6 @@ class vehicle; struct itype; struct tripoint; struct vehicle_part; -template struct enum_traits; - -/** - * Specifies weapon source for aiming across turns and - * (de-)serialization of targeting_data - */ -enum weapon_source_enum { - /** Invalid weapon source */ - WEAPON_SOURCE_INVALID, - /** Firing wielded weapon */ - WEAPON_SOURCE_WIELDED, - /** Firing fake gun provided by a bionic */ - WEAPON_SOURCE_BIONIC, - /** Firing fake gun provided by a mutation */ - WEAPON_SOURCE_MUTATION, - NUM_WEAPON_SOURCES -}; - -template <> -struct enum_traits { - static constexpr weapon_source_enum last = NUM_WEAPON_SOURCES; -}; - -/** Stores data for aiming the player's weapon across turns */ -struct targeting_data { - weapon_source_enum weapon_source; - - /** Cached fake weapon provided by bionic/mutation */ - shared_ptr_fast cached_fake_weapon; - - /** Bionic power cost per shot */ - units::energy bp_cost_per_shot; - - bool is_valid() const; - - /** Use wielded gun */ - static targeting_data use_wielded(); - - /** Use fake gun provided by a bionic */ - static targeting_data use_bionic( const item &fake_gun, const units::energy &cost_per_shot ); - - /** Use fake gun provided by a mutation */ - static targeting_data use_mutation( const item &fake_gun ); - - // Since only avatar uses targeting_data, - // (de-)serialization is implemented in savegame_json.cpp - // near avatar (de-)serialization - void serialize( JsonOut &json ) const; - void deserialize( JsonIn &jsin ); -}; namespace target_handler { @@ -77,7 +23,7 @@ using trajectory = std::vector; /** * Firing ranged weapon. This mode allows spending moves on aiming. */ -trajectory mode_fire( player &pc, item &weapon, aim_activity_actor &activity ); +trajectory mode_fire( player &pc, aim_activity_actor &activity ); /** Throwing item */ trajectory mode_throw( player &pc, item &relevant, bool blind_throwing ); diff --git a/src/savegame_json.cpp b/src/savegame_json.cpp index 63d5baf90f162..245f61440981c 100644 --- a/src/savegame_json.cpp +++ b/src/savegame_json.cpp @@ -90,7 +90,6 @@ #include "player_activity.h" #include "point.h" #include "profession.h" -#include "ranged.h" #include "recipe.h" #include "recipe_dictionary.h" #include "requirements.h" @@ -1025,11 +1024,6 @@ void avatar::store( JsonOut &json ) const json.member( "int_upgrade", std::abs( int_upgrade ) ); json.member( "per_upgrade", std::abs( per_upgrade ) ); - // targeting - if( activity.id() == ACT_AIM ) { - json.member( "targeting_data", *tdata ); - } - // npc: unimplemented, potentially useful json.member( "learned_recipes", *learned_recipes ); @@ -1099,13 +1093,6 @@ void avatar::load( const JsonObject &data ) data.read( "int_upgrade", int_upgrade ); data.read( "per_upgrade", per_upgrade ); - // targeting - targeting_data tdata = targeting_data(); - data.read( "targeting_data", tdata ); - if( tdata.is_valid() ) { - set_targeting_data( tdata ); - } - // this is so we don't need to call get_option in a draw function if( !get_option( "STATS_THROUGH_KILLS" ) ) { str_upgrade = -str_upgrade; @@ -1215,32 +1202,6 @@ void avatar::load( const JsonObject &data ) } } -//////////////////////////////////////////////////////////////////////////////////////////////////// -///// ranged.h - -void targeting_data::serialize( JsonOut &json ) const -{ - json.start_object(); - json.member( "weapon_source", io::enum_to_string( weapon_source ) ); - if( cached_fake_weapon ) { - json.member( "cached_fake_weapon", *cached_fake_weapon ); - } - json.member( "bp_cost", bp_cost_per_shot ); - json.end_object(); -} - -void targeting_data::deserialize( JsonIn &jsin ) -{ - JsonObject data = jsin.get_object(); - data.read( "weapon_source", weapon_source ); - if( weapon_source == WEAPON_SOURCE_BIONIC || weapon_source == WEAPON_SOURCE_MUTATION ) { - cached_fake_weapon = shared_ptr_fast( new item() ); - data.read( "cached_fake_weapon", *cached_fake_weapon ); - } - - data.read( "bp_cost", bp_cost_per_shot ); -} - //////////////////////////////////////////////////////////////////////////////////////////////////// ///// npc.h From c1fc6e9e0cb8d2191ef8b4c7943c4434b9f94deb Mon Sep 17 00:00:00 2001 From: olanti-p Date: Mon, 18 May 2020 18:40:02 +0300 Subject: [PATCH 4/8] Move do_turn code into activity actor --- src/activity_actor.cpp | 151 ++++++++++++++++++++++++++++++++++++++--- src/activity_actor.h | 6 +- src/avatar_action.cpp | 106 +---------------------------- src/avatar_action.h | 7 +- 4 files changed, 150 insertions(+), 120 deletions(-) diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index 78d15bba44105..ec34468a6e21f 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -22,11 +22,13 @@ #include "map.h" #include "map_iterator.h" #include "npc.h" +#include "options.h" #include "output.h" #include "pickup.h" #include "player.h" #include "player_activity.h" #include "point.h" +#include "ranged.h" #include "timed_event.h" #include "uistate.h" @@ -36,6 +38,7 @@ static const skill_id skill_computer( "computer" ); static const trait_id trait_ILLITERATE( "ILLITERATE" ); +static const std::string flag_RELOAD_AND_SHOOT( "RELOAD_AND_SHOOT" ); static const std::string flag_USE_EAT_VERB( "USE_EAT_VERB" ); static const mtype_id mon_zombie( "mon_zombie" ); @@ -103,26 +106,81 @@ void aim_activity_actor::start( player_activity &act, Character &/*who*/ ) void aim_activity_actor::do_turn( player_activity &act, Character &who ) { - if( avatar *you = dynamic_cast( &who ) ) { - avatar_action::aim_do_turn( *you, g->m, *this ); - } else { + if( aborted || finished ) { + // A shortcut that allows terminating this activity by setting 'aborted' or 'finished' + act.moves_left = 0; + return; + } + if( !who.is_avatar() ) { debugmsg( "ACT_AIM not implemented for NPCs" ); aborted = true; + return; } - first_turn = false; - if( aborted || finished ) { - act.moves_left = 0; + avatar &you = g->u; + + item *weapon = get_weapon(); + if( !weapon || !avatar_action::can_fire_weapon( you, g->m, *weapon ) ) { + aborted = true; + return; + } + + gun_mode gun = weapon->gun_current_mode(); + if( first_turn && gun->has_flag( flag_RELOAD_AND_SHOOT ) && !gun->ammo_remaining() ) { + if( !load_RAS_weapon() ) { + aborted = true; + return; + } + } + + g->temp_exit_fullscreen(); + g->m.draw( g->w_terrain, you.pos() ); + target_handler::trajectory trajectory = target_handler::mode_fire( you, *this ); + g->reenter_fullscreen(); + + if( !aborted ) { + if( !trajectory.empty() ) { + finished = true; + fin_trajectory = trajectory; + } + // 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; } } -void aim_activity_actor::finish( player_activity &act, Character &/*who*/ ) +void aim_activity_actor::finish( player_activity &act, Character &who ) { act.set_to_null(); - if( reload_requested ) { - // Reload the gun / select different arrows - // May assign ACT_RELOAD - g->reload_wielded( true ); + if( aborted ) { + unload_RAS_weapon(); + if( reload_requested ) { + // Reload the gun / select different arrows + // May assign ACT_RELOAD + g->reload_wielded( true ); + } + return; } + + // Recenter our view + g->draw_ter(); + wrefresh( g->w_terrain ); + g->draw_panels(); + + // Fire! + item *weapon = get_weapon(); + gun_mode gun = weapon->gun_current_mode(); + int shots_fired = static_cast( &who )->fire_gun( fin_trajectory.back(), gun.qty, *gun ); + + // TODO: bionic power cost of firing should be derived from a value of the relevant weapon. + if( shots_fired && ( bp_cost_per_shot > 0_J ) ) { + who.mod_power_level( -bp_cost_per_shot * shots_fired ); + } +} + +void aim_activity_actor::canceled( player_activity &/*act*/, Character &/*who*/ ) +{ + unload_RAS_weapon(); } void aim_activity_actor::serialize( JsonOut &jsout ) const @@ -181,6 +239,77 @@ item *aim_activity_actor::get_weapon() } } +bool aim_activity_actor::load_RAS_weapon() +{ + // TODO: use activity for fetching ammo and loading weapon + player &you = g->u; + item *weapon = get_weapon(); + gun_mode gun = weapon->gun_current_mode(); + const auto ammo_location_is_valid = [&]() -> bool { + if( !you.ammo_location ) + { + return false; + } + if( !gun->can_reload_with( you.ammo_location->typeId() ) ) + { + return false; + } + if( square_dist( you.pos(), you.ammo_location.position() ) > 1 ) + { + return false; + } + return true; + }; + item::reload_option opt = ammo_location_is_valid() ? item::reload_option( &you, weapon, + weapon, you.ammo_location ) : you.select_ammo( *gun ); + if( !opt ) { + // 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.2% max base stamina x the strength required to fire. + you.mod_stamina( gun->get_min_str() * static_cast( 0.002f * + get_option( "PLAYER_MAX_STAMINA" ) ) ); + // 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.moves -= reload_time; + g->refresh_all(); + return true; +} + +void aim_activity_actor::unload_RAS_weapon() +{ + // Unload reload-and-shoot weapons to avoid leaving bows pre-loaded with arrows + avatar &you = g->u; + item *weapon = get_weapon(); + if( !weapon ) { + return; + } + + gun_mode gun = weapon->gun_current_mode(); + if( gun->has_flag( flag_RELOAD_AND_SHOOT ) ) { + int moves_before_unload = you.moves; + + // Note: this code works only for avatar + item_location loc = item_location( you, gun.target ); + g->unload( loc ); + + // Give back time for unloading as essentially nothing has been done. + if( first_turn ) { + you.moves = moves_before_unload; + } + g->refresh_all(); + } +} + void dig_activity_actor::start( player_activity &act, Character & ) { act.moves_total = moves_total; diff --git a/src/activity_actor.h b/src/activity_actor.h index e18dd4f3659d0..0ec2b438219b2 100644 --- a/src/activity_actor.h +++ b/src/activity_actor.h @@ -131,7 +131,7 @@ class aim_activity_actor : public activity_actor * Implies aborted = true */ bool reload_requested = false; - + std::vector fin_trajectory; aim_activity_actor() = default; @@ -151,6 +151,7 @@ class aim_activity_actor : public activity_actor void start( player_activity &act, Character &who ) override; void do_turn( player_activity &act, Character &who ) override; void finish( player_activity &act, Character &who ) override; + void canceled( player_activity &act, Character &who ) override; std::unique_ptr clone() const override { return std::make_unique( *this ); @@ -160,6 +161,9 @@ class aim_activity_actor : public activity_actor static std::unique_ptr deserialize( JsonIn &jsin ); item *get_weapon(); + // Load/unload a RELOAD_AND_SHOOT weapon + bool load_RAS_weapon(); + void unload_RAS_weapon(); }; class dig_activity_actor : public activity_actor diff --git a/src/avatar_action.cpp b/src/avatar_action.cpp index 4d79d64810a7c..d5847c887012a 100644 --- a/src/avatar_action.cpp +++ b/src/avatar_action.cpp @@ -730,11 +730,7 @@ static bool gunmode_checks_weapon( avatar &you, const map &m, std::vectorgun_current_mode(); - - // TODO: move handling "RELOAD_AND_SHOOT" flagged guns to a separate function. - // TODO: handle cases when reloading takes a long time - if( gun->has_flag( flag_RELOAD_AND_SHOOT ) ) { - if( !gun->ammo_remaining() ) { - const auto ammo_location_is_valid = [&]() -> bool { - if( !you.ammo_location ) - { - return false; - } - if( !gun->can_reload_with( you.ammo_location->typeId() ) ) - { - return false; - } - if( square_dist( you.pos(), you.ammo_location.position() ) > 1 ) - { - return false; - } - return true; - }; - item::reload_option opt = ammo_location_is_valid() ? item::reload_option( &you, weapon, - weapon, you.ammo_location ) : you.select_ammo( *gun ); - if( !opt ) { - // Menu canceled - activity.aborted = true; - return; - } - int reload_time = 0; - reload_time += opt.moves(); - if( !gun->reload( you, std::move( opt.ammo ), 1 ) ) { - // Reload not allowed - activity.aborted = true; - return; - } - - // Burn 0.2% max base stamina x the strength required to fire. - you.mod_stamina( gun->get_min_str() * static_cast( 0.002f * - get_option( "PLAYER_MAX_STAMINA" ) ) ); - // 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.moves -= reload_time; - g->refresh_all(); - } - } - int moves_before_ui = you.moves; - - g->temp_exit_fullscreen(); - m.draw( g->w_terrain, you.pos() ); - target_handler::trajectory trajectory = target_handler::mode_fire( you, activity ); - - if( activity.aborted ) { - if( gun->has_flag( flag_RELOAD_AND_SHOOT ) ) { - bool refund = activity.first_turn && you.moves == moves_before_ui; - int moves_before_unload = you.moves; - - item_location loc = item_location( you, gun.target ); - g->unload( loc ); - - // Give back time for unloading as essentially nothing has been done. - if( refund ) { - you.moves = moves_before_unload; - } - } - g->reenter_fullscreen(); - return; - } - if( trajectory.empty() ) { - // Still aiming - return; - } - activity.finished = true; - - // TODO: move everything below to aim_activity_actor::finish() - - // Recenter our view - g->draw_ter(); - wrefresh( g->w_terrain ); - g->draw_panels(); - - // Update gun mode since target UI may change it - gun = weapon->gun_current_mode(); - int shots_fired = you.fire_gun( trajectory.back(), gun.qty, *gun ); - - // TODO: bionic power cost of firing should be derived from a value of the relevant weapon. - if( shots_fired && ( activity.bp_cost_per_shot > 0_J ) ) { - you.mod_power_level( -activity.bp_cost_per_shot * shots_fired ); - } - g->reenter_fullscreen(); -} - void avatar_action::fire_wielded_weapon( avatar &you ) { item &weapon = you.weapon; diff --git a/src/avatar_action.h b/src/avatar_action.h index ea3e028cc0aa1..1aac0cac95958 100644 --- a/src/avatar_action.h +++ b/src/avatar_action.h @@ -42,10 +42,11 @@ void autoattack( avatar &you, map &m ); void mend( avatar &you, item_location loc ); /** - * Validates avatar's targeting_data, then handles interactive parts of gun firing - * (target selection, aiming, etc.) + * Checks if the weapon is valid and if the player meets certain conditions for firing it. + * Used for validating ACT_AIM and turret weapon + * @return True if all conditions are true, otherwise false. */ -void aim_do_turn( avatar &you, map &m, aim_activity_actor &activity ); +bool can_fire_weapon( avatar &you, const map &m, const item &weapon ); /** Checks if the wielded weapon is a gun and can be fired then starts interactive aiming */ void fire_wielded_weapon( avatar &you ); From d1fbe6f1c1c41de23d1cf802c24b1abb42deb763 Mon Sep 17 00:00:00 2001 From: olanti-p Date: Mon, 18 May 2020 21:32:26 +0300 Subject: [PATCH 5/8] Keep g->u.view_offset while aiming Stops terrain from flickering on CURSES while aiming with shifted view --- src/activity_actor.cpp | 21 +++++++++++++++++++-- src/activity_actor.h | 5 +++-- src/ranged.cpp | 12 ------------ 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index ec34468a6e21f..279015356a1c0 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -74,6 +74,11 @@ std::string enum_to_string( WS data ) } } // namespace io +aim_activity_actor::aim_activity_actor() +{ + initial_view_offset = g->u.view_offset; +} + aim_activity_actor aim_activity_actor::use_wielded() { return aim_activity_actor(); @@ -152,6 +157,7 @@ void aim_activity_actor::do_turn( player_activity &act, Character &who ) void aim_activity_actor::finish( player_activity &act, Character &who ) { act.set_to_null(); + restore_view(); if( aborted ) { unload_RAS_weapon(); if( reload_requested ) { @@ -180,6 +186,7 @@ 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(); } @@ -196,7 +203,7 @@ void aim_activity_actor::serialize( JsonOut &jsout ) const jsout.member( "action", action ); jsout.member( "snap_to_target", snap_to_target ); jsout.member( "shifting_view", shifting_view ); - jsout.member( "view_offset", view_offset ); + jsout.member( "initial_view_offset", initial_view_offset ); jsout.end_object(); } @@ -217,7 +224,7 @@ std::unique_ptr aim_activity_actor::deserialize( JsonIn &jsin ) data.read( "action", actor.action ); data.read( "snap_to_target", actor.snap_to_target ); data.read( "shifting_view", actor.shifting_view ); - data.read( "view_offset", actor.view_offset ); + data.read( "initial_view_offset", actor.initial_view_offset ); return actor.clone(); } @@ -239,6 +246,16 @@ item *aim_activity_actor::get_weapon() } } +void aim_activity_actor::restore_view() +{ + bool changed_z = g->u.view_offset.z != initial_view_offset.z; + g->u.view_offset = initial_view_offset; + if( changed_z ) { + g->m.invalidate_map_cache( g->u.view_offset.z ); + g->refresh_all(); + } +} + bool aim_activity_actor::load_RAS_weapon() { // TODO: use activity for fetching ammo and loading weapon diff --git a/src/activity_actor.h b/src/activity_actor.h index 0ec2b438219b2..0e934e9948241 100644 --- a/src/activity_actor.h +++ b/src/activity_actor.h @@ -121,7 +121,7 @@ class aim_activity_actor : public activity_actor std::string action = ""; bool snap_to_target = false; bool shifting_view = false; - tripoint view_offset = tripoint_zero; + tripoint initial_view_offset; /** Target UI requested to abort aiming */ bool aborted = false; /** Target UI requested to fire */ @@ -133,7 +133,7 @@ class aim_activity_actor : public activity_actor bool reload_requested = false; std::vector fin_trajectory; - aim_activity_actor() = default; + aim_activity_actor(); /** Aiming wielded gun */ static aim_activity_actor use_wielded(); @@ -161,6 +161,7 @@ class aim_activity_actor : public activity_actor static std::unique_ptr deserialize( JsonIn &jsin ); item *get_weapon(); + void restore_view(); // Load/unload a RELOAD_AND_SHOOT weapon bool load_RAS_weapon(); void unload_RAS_weapon(); diff --git a/src/ranged.cpp b/src/ranged.cpp index 75631d456b67e..38697189b4998 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -1863,7 +1863,6 @@ target_handler::trajectory target_ui::run( player &pc ) std::string action; bool attack_was_confirmed = false; bool reentered = false; - tripoint manual_view_offset = pc.view_offset; if( mode == TargetMode::Fire && !activity->first_turn ) { // We were in this UI during previous turn... reentered = true; @@ -1879,13 +1878,8 @@ target_handler::trajectory target_ui::run( player &pc ) // Load state to keep the ui consistent across turns snap_to_target = activity->snap_to_target; shifting_view = activity->shifting_view; - manual_view_offset = activity->view_offset; } - // Restore view to how it was during previos turn in this ui - const tripoint saved_view_offset = pc.view_offset; - set_view_offset( pc, manual_view_offset ); - // Initialize cursor position src = pc.pos(); tripoint initial_dst = src; @@ -2004,11 +1998,6 @@ target_handler::trajectory target_ui::run( player &pc ) } } // for(;;) - // Since the activity can be cancelled at any time, we restore the view now - // but save it if we need it in this ui on the next turn - manual_view_offset = pc.view_offset; - set_view_offset( pc, saved_view_offset ); - switch( loop_exit_code ) { case ExitCode::Abort: { traj.clear(); @@ -2028,7 +2017,6 @@ target_handler::trajectory target_ui::run( player &pc ) activity->action = timed_out_action; activity->snap_to_target = snap_to_target; activity->shifting_view = shifting_view; - activity->view_offset = manual_view_offset; break; } case ExitCode::Reload: { From 7e17b613c9072d7dfe92bf0b7e7f00b0e64faea2 Mon Sep 17 00:00:00 2001 From: olanti-p Date: Mon, 18 May 2020 22:16:05 +0300 Subject: [PATCH 6/8] Add a switch to stop '.' key from creating "Cancel activity?" query Stops this query from popping up when holding '.' for "Aim" in target ui on CURSES or TILES with low fps --- src/activity_actor.cpp | 1 + src/game.cpp | 4 +++- src/player_activity.h | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index 279015356a1c0..412c4d39d0e16 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -107,6 +107,7 @@ void aim_activity_actor::start( player_activity &act, Character &/*who*/ ) // Time spent on aiming is determined on the go by the player act.moves_total = 1; act.moves_left = 1; + act.interruptable_with_kb = false; } void aim_activity_actor::do_turn( player_activity &act, Character &who ) diff --git a/src/game.cpp b/src/game.cpp index 160edfc09c6ae..579f4415ff13f 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -2014,7 +2014,9 @@ void game::handle_key_blocking_activity() const std::string action = ctxt.handle_input( 0 ); bool refresh = true; if( action == "pause" ) { - cancel_activity_query( _( "Confirm:" ) ); + if( u.activity.interruptable_with_kb ) { + cancel_activity_query( _( "Confirm:" ) ); + } } else if( action == "player_data" ) { u.disp_info(); } else if( action == "messages" ) { diff --git a/src/player_activity.h b/src/player_activity.h index df3877b8699e8..c60e6113abecb 100644 --- a/src/player_activity.h +++ b/src/player_activity.h @@ -39,6 +39,8 @@ class player_activity int moves_total = 0; /** The number of moves remaining in this activity before it is complete. */ int moves_left = 0; + /** Controls whether this activity can be cancelled with 'pause' action */ + bool interruptable_with_kb = true; // The members in the following block are deprecated, prefer creating a new // activity_actor. From 06608a0390ae37c42c7f97233b7e5353eecb965c Mon Sep 17 00:00:00 2001 From: olanti-p Date: Mon, 18 May 2020 23:52:22 +0300 Subject: [PATCH 7/8] Remove dead code --- src/activity_handlers.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/activity_handlers.h b/src/activity_handlers.h index d44ecd04a48d6..1b8633d07cd76 100644 --- a/src/activity_handlers.h +++ b/src/activity_handlers.h @@ -227,7 +227,6 @@ void disassemble_finish( player_activity *act, player *p ); void vibe_finish( player_activity *act, player *p ); void hand_crank_finish( player_activity *act, player *p ); void atm_finish( player_activity *act, player *p ); -void aim_finish( player_activity *act, player *p ); void eat_menu_finish( player_activity *act, player *p ); void washing_finish( player_activity *act, player *p ); void hacksaw_finish( player_activity *act, player *p ); From 453ef58380dd06742bf842ce68e7e88a4affe9aa Mon Sep 17 00:00:00 2001 From: olanti-p Date: Tue, 19 May 2020 03:56:55 +0300 Subject: [PATCH 8/8] Fix units::energy (de-)serialization The old thing doesn't work on clang 7 --- src/savegame_json.cpp | 16 ---------------- src/units.cpp | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/savegame_json.cpp b/src/savegame_json.cpp index 245f61440981c..1030d7082ff46 100644 --- a/src/savegame_json.cpp +++ b/src/savegame_json.cpp @@ -2091,22 +2091,6 @@ void time_duration::deserialize( JsonIn &jsin ) } } -template -void units::quantity::serialize( JsonOut &jsout ) const -{ - jsout.write( value_ ); -} - -// TODO: BATTERIES this template specialization should be in the global namespace - see GCC bug 56480 -namespace units -{ -template<> -void units::energy::deserialize( JsonIn &jsin ) -{ - *this = from_millijoule( jsin.get_int() ); -} -} // namespace units - //////////////////////////////////////////////////////////////////////////////////////////////////// ///// item.h diff --git a/src/units.cpp b/src/units.cpp index e116592b1c345..d907a7298821b 100644 --- a/src/units.cpp +++ b/src/units.cpp @@ -45,4 +45,22 @@ void length::deserialize( JsonIn &jsin ) { *this = read_from_json_string( jsin, units::length_units ); } + +template<> +void energy::serialize( JsonOut &jsout ) const +{ + if( value_ % 1000000 == 0 ) { + jsout.write( string_format( "%d kJ", value_ / 1000000 ) ); + } else if( value_ % 1000 == 0 ) { + jsout.write( string_format( "%d J", value_ / 1000 ) ) ; + } else { + jsout.write( string_format( "%d mJ", value_ ) ); + } +} + +template<> +void energy::deserialize( JsonIn &jsin ) +{ + *this = read_from_json_string( jsin, units::energy_units ); +} } // namespace units