diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index 7c62943061812..39b248f609512 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" @@ -39,6 +41,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" ); @@ -47,6 +50,287 @@ 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() +{ + initial_view_offset = g->u.view_offset; +} + +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 + act.moves_total = 1; + act.moves_left = 1; + act.interruptable_with_kb = false; +} + +void aim_activity_actor::do_turn( player_activity &act, Character &who ) +{ + 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; + } + 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 ) +{ + act.set_to_null(); + restore_view(); + 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*/ ) +{ + restore_view(); + unload_RAS_weapon(); +} + +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 ); + jsout.member( "shifting_view", shifting_view ); + jsout.member( "initial_view_offset", initial_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( "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 ); + data.read( "shifting_view", actor.shifting_view ); + data.read( "initial_view_offset", actor.initial_view_offset ); + + 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 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 + 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; @@ -674,6 +958,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 ce6bc8f69555b..1f09714937aa2 100644 --- a/src/activity_actor.h +++ b/src/activity_actor.h @@ -10,8 +10,10 @@ #include "clone_ptr.h" #include "item_location.h" #include "item.h" +#include "memory_fast.h" #include "point.h" #include "type_id.h" +#include "units.h" class Character; class JsonIn; @@ -103,6 +105,69 @@ class activity_actor virtual void serialize( JsonOut &jsout ) const = 0; }; +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 initial_view_offset; + /** 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; + std::vector fin_trajectory; + + aim_activity_actor(); + + /** 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" ); + } + + 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 ); + } + + void serialize( JsonOut &jsout ) const override; + 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(); +}; + class dig_activity_actor : public activity_actor { private: diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index 7a6dd3e0f0fb3..751941bd15db5 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" ); @@ -317,7 +316,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 }, @@ -415,7 +413,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 }, @@ -2996,15 +2993,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 ); @@ -3882,12 +3870,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..1b8633d07cd76 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 ); @@ -228,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 ); diff --git a/src/avatar.cpp b/src/avatar.cpp index 45cc8204610b9..017f56c9673cf 100644 --- a/src/avatar.cpp +++ b/src/avatar.cpp @@ -1678,20 +1678,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 387595e80135a..f800ed9eaea54 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 { @@ -266,16 +265,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 6275eee00e247..c6509e4260656 100644 --- a/src/avatar_action.cpp +++ b/src/avatar_action.cpp @@ -738,11 +738,7 @@ static bool gunmode_checks_weapon( avatar &you, const map &m, std::vectorgun_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. - 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 - return; - } - reload_time += opt.moves(); - if( !gun->reload( you, std::move( opt.ammo ), 1 ) ) { - // Reload not allowed - 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; - - g->refresh_all(); - } - } - - 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 ); - - //may be changed in target_ui - gun = weapon->gun_current_mode(); - - 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; - } - g->reenter_fullscreen(); - - if( reload_requested ) { - // Reload the gun / select different arrows - g->reload_wielded( true ); - } - return; - } - // Recenter our view - g->draw_ter(); - wrefresh( g->w_terrain ); - g->draw_panels(); - - you.moves -= reload_time; - - 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 ); - } - 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() ) { @@ -967,24 +843,18 @@ void avatar_action::fire_wielded_weapon( avatar &you, map &m ) return; } - 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::use_wielded(), 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 ); - avatar_action::aim_do_turn( you, m ); + you.assign_activity( aim_activity_actor::use_mutation( fake_gun ), 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 ); - you.set_targeting_data( args ); - avatar_action::aim_do_turn( you, m ); + 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/avatar_action.h b/src/avatar_action.h index 4d9741e2ea7c4..1aac0cac95958 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 { @@ -41,19 +42,20 @@ 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 ); +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, 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 f566a7ac705e7..659024d53d275 100644 --- a/src/bionics.cpp +++ b/src/bionics.cpp @@ -588,8 +588,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() ); @@ -612,8 +611,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 4ee7359556b8b..41983f7d626ec 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -2027,7 +2027,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" ) { @@ -7255,7 +7257,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 c514fa357923d..8fcb646ef9d18 100644 --- a/src/handle_action.cpp +++ b/src/handle_action.cpp @@ -1362,7 +1362,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 ) ) { @@ -2055,7 +2055,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 042c2d4b41f58..d273964fbd72a 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.is_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; } 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. diff --git a/src/ranged.cpp b/src/ranged.cpp index 331fa248b19dd..41a65c80eccba 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -152,6 +152,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, @@ -160,10 +162,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 { @@ -351,20 +351,17 @@ class target_ui void on_target_accepted( player &pc, bool harmful ); }; -target_handler::trajectory target_handler::mode_fire( player &pc, item &weapon, - bool &reload_requested ) +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.relevant = &weapon; - gun_mode gun = weapon.gun_current_mode(); + ui.activity = &activity; + 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(); - 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, @@ -447,59 +444,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 ) { @@ -1905,7 +1849,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" ), @@ -1932,33 +1876,23 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) std::string action; 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; } - // 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; @@ -2077,14 +2011,12 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) } } // 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(); + if( mode == TargetMode::Fire ) { + activity->aborted = true; + } break; } case ExitCode::Fire: { @@ -2093,29 +2025,21 @@ 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; 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..95f4148554b4e 100644 --- a/src/ranged.h +++ b/src/ranged.h @@ -3,12 +3,9 @@ #include -#include "memory_fast.h" #include "type_id.h" -#include "units.h" -class JsonIn; -class JsonOut; +class aim_activity_actor; class item; class player; class spell; @@ -17,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 { @@ -75,9 +22,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, 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 a26ef7fe9b437..05bc9dba960a9 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" @@ -1031,11 +1030,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 ); @@ -1105,13 +1099,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; @@ -1222,32 +1209,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 @@ -2137,22 +2098,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