From dd72c80584bc4d4aafb5c65a119f76f26be474bb Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Fri, 17 Apr 2020 20:26:36 +0300 Subject: [PATCH 01/43] Use separate functions for modes & derive relevant info from arguments --- src/activity_handlers.cpp | 5 +- src/avatar_action.cpp | 25 ++-------- src/game.h | 1 - src/handle_action.cpp | 35 ++++---------- src/item.cpp | 18 ++++++++ src/item.h | 13 +++++- src/ranged.cpp | 96 ++++++++++++++++++++++++++++++++++++++- src/ranged.h | 62 ++++++++++--------------- src/turret.cpp | 19 +------- 9 files changed, 165 insertions(+), 109 deletions(-) diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index c85fc8b0f45ef..57190525bf474 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -4775,14 +4775,13 @@ void activity_handlers::spellcasting_finish( player_activity *act, player *p ) const bool no_mana = act->get_value( 2 ) == 0; // choose target for spell (if the spell has a range > 0) - - target_handler th; tripoint target = p->pos(); bool target_is_valid = false; if( spell_being_cast.range() > 0 && !spell_being_cast.is_valid_target( target_none ) && !spell_being_cast.has_flag( RANDOM_TARGET ) ) { do { - std::vector trajectory = th.target_ui( spell_being_cast, no_fail, no_mana ); + std::vector trajectory = target_handler::mode_spell( &spell_being_cast, no_fail, + no_mana ); if( !trajectory.empty() ) { target = trajectory.back(); target_is_valid = spell_being_cast.is_valid_target( *p, target ); diff --git a/src/avatar_action.cpp b/src/avatar_action.cpp index b58f2bd2bd4f7..907dcd810a34e 100644 --- a/src/avatar_action.cpp +++ b/src/avatar_action.cpp @@ -898,13 +898,9 @@ void avatar_action::aim_do_turn( avatar &you, map &m ) } } - int range = gun.target->gun_range( &you ); - const itype *ammo = gun->ammo_data(); - g->temp_exit_fullscreen(); m.draw( g->w_terrain, you.pos() ); - std::vector trajectory = target_handler().target_ui( you, TARGET_MODE_FIRE, weapon, range, - ammo ); + target_handler::trajectory trajectory = target_handler::mode_fire( you, weapon ); //may be changed in target_ui gun = weapon->gun_current_mode(); @@ -979,18 +975,9 @@ void avatar_action::fire_turret_manual( avatar &you, map &m, turret_data &turret return; } - item *turret_base = &*turret.base(); - g->temp_exit_fullscreen(); g->m.draw( g->w_terrain, you.pos() ); - std::vector trajectory = target_handler().target_ui( - you, - TARGET_MODE_TURRET_MANUAL, - turret_base, - turret.range(), - turret.ammo_data(), - &turret - ); + target_handler::trajectory trajectory = target_handler::mode_turret_manual( you, &turret ); if( !trajectory.empty() ) { // Recenter our view @@ -1184,12 +1171,8 @@ void avatar_action::plthrow( avatar &you, item_location loc, g->temp_exit_fullscreen(); g->m.draw( g->w_terrain, you.pos() ); - const target_mode throwing_target_mode = blind_throw_from_pos ? TARGET_MODE_THROW_BLIND : - TARGET_MODE_THROW; - // target_ui() sets x and y, or returns empty vector if we canceled (by pressing Esc) - std::vector trajectory = target_handler().target_ui( you, throwing_target_mode, - &you.weapon, - range ); + target_handler::trajectory trajectory = target_handler::mode_throw( you, &you.weapon, + blind_throw_from_pos.has_value() ); // If we previously shifted our position, put ourselves back now that we've picked our target. if( blind_throw_from_pos ) { diff --git a/src/game.h b/src/game.h index c0cb96164a5c1..26d0d33ef47e3 100644 --- a/src/game.h +++ b/src/game.h @@ -153,7 +153,6 @@ class game friend class editmap; friend class advanced_inventory; friend class main_menu; - friend class target_handler; public: game(); ~game(); diff --git a/src/handle_action.cpp b/src/handle_action.cpp index 36ad9b1b5afac..ae725acfea84a 100644 --- a/src/handle_action.cpp +++ b/src/handle_action.cpp @@ -1283,17 +1283,16 @@ static void read() } } -// Perform a reach attach -// range - the range of the current weapon. -// u - player -static void reach_attack( int range, player &u ) +// Perform a reach attach using wielded weapon +static void reach_attack( player &u ) { g->temp_exit_fullscreen(); g->m.draw( g->w_terrain, u.pos() ); - std::vector trajectory = target_handler().target_ui( u, TARGET_MODE_REACH, &u.weapon, - range ); - if( !trajectory.empty() ) { - u.reach_attack( trajectory.back() ); + + target_handler::trajectory traj = target_handler::mode_reach( u, &u.weapon ); + + if( !traj.empty() ) { + u.reach_attack( traj.back() ); } g->draw_ter(); wrefresh( g->w_terrain ); @@ -1353,31 +1352,17 @@ static void fire() if( u.weapon.is_gun() && !u.weapon.gun_current_mode().melee() ) { avatar_action::fire_wielded_weapon( g->u, g->m ); - } else if( u.weapon.has_flag( flag_REACH_ATTACK ) ) { - int range = u.weapon.has_flag( flag_REACH3 ) ? 3 : 2; - if( u.has_effect( effect_relax_gas ) ) { - if( one_in( 8 ) ) { - add_msg( m_good, _( "Your willpower asserts itself, and so do you!" ) ); - reach_attack( range, u ); - } else { - u.moves -= rng( 2, 8 ) * 10; - add_msg( m_bad, _( "You're too pacified to strike anything…" ) ); - } - } else { - reach_attack( range, u ); - } - } else if( u.weapon.is_gun() && u.weapon.gun_current_mode().flags.count( flag_REACH_ATTACK ) ) { - int range = u.weapon.gun_current_mode().qty; + } else if( u.weapon.current_reach_range( u ) > 1 ) { if( u.has_effect( effect_relax_gas ) ) { if( one_in( 8 ) ) { add_msg( m_good, _( "Your willpower asserts itself, and so do you!" ) ); - reach_attack( range, u ); + reach_attack( u ); } else { u.moves -= rng( 2, 8 ) * 10; add_msg( m_bad, _( "You're too pacified to strike anything…" ) ); } } else { - reach_attack( range, u ); + reach_attack( u ); } } } diff --git a/src/item.cpp b/src/item.cpp index 65639e8495d4f..5b39b99f8448a 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -4861,6 +4861,24 @@ int item::reach_range( const player &p ) const return res; } +int item::current_reach_range( const player &p ) const +{ + int res = 1; + + if( has_flag( flag_REACH_ATTACK ) ) { + res = has_flag( flag_REACH3 ) ? 3 : 2; + } + + if( is_gun() && !is_gunmod() ) { + gun_mode gun = gun_current_mode(); + if( !( gun.flags.count( "NPC_AVOID" ) && p.is_npc() ) && gun.melee() ) { + res = std::max( res, gun.qty ); + } + } + + return res; +} + void item::unset_flags() { item_tags.clear(); diff --git a/src/item.h b/src/item.h index f11d800e1e04c..235567d5d32fa 100644 --- a/src/item.h +++ b/src/item.h @@ -593,9 +593,20 @@ class item : public visitable skill_id melee_skill() const; /*@}*/ - /** Max range weapon usable for melee attack accounting for player/NPC abilities */ + /* + * Max range of melee attack this weapon can be used for. + * Accounts for player/NPC abilities and installed gun mods. + * Guaranteed to be at least 1 + */ int reach_range( const player &p ) const; + /* + * Max range of melee attack this weapon can be used for in its current state. + * Accounts for player/NPC abilities and installed gun mods. + * Guaranteed to be at least 1 + */ + int current_reach_range( const player &p ) const; + /** * Sets time until activation for an item that will self-activate in the future. **/ diff --git a/src/ranged.cpp b/src/ranged.cpp index a3ca204437490..e424f439fe180 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -103,6 +103,99 @@ int time_to_attack( const Character &p, const itype &firing ); static void cycle_action( item &weap, const tripoint &pos ); void make_gun_sound_effect( const player &p, bool burst, item *weapon ); +enum target_mode : int { + TARGET_MODE_FIRE, + TARGET_MODE_THROW, + TARGET_MODE_TURRET, + TARGET_MODE_TURRET_MANUAL, + TARGET_MODE_REACH, + TARGET_MODE_THROW_BLIND, + TARGET_MODE_SPELL +}; + +// TODO: Remove these forward declarations when done refactoring +namespace target_handler +{ +std::vector target_ui( player &pc, target_mode mode, + item *relevant, int range, + const itype *ammo = nullptr, + turret_data *turret = nullptr, + vehicle *veh = nullptr, + const std::vector &vturrets = std::vector() + ); +std::vector target_ui( spell_id sp, bool no_fail = false, + bool no_mana = false ); +std::vector target_ui( spell &casting, bool no_fail = false, + bool no_mana = false ); +} + +target_handler::trajectory target_handler::mode_fire( player &pc, item *weapon ) +{ + gun_mode gun = weapon->gun_current_mode(); + int range = gun.target->gun_range( &pc ); + const itype *ammo = gun->ammo_data(); + + return target_ui( pc, TARGET_MODE_FIRE, weapon, range, ammo ); +} + +target_handler::trajectory target_handler::mode_throw( player &pc, item *relevant, + bool blind_throwing ) +{ + target_mode mode = blind_throwing ? TARGET_MODE_THROW_BLIND : TARGET_MODE_THROW; + int range = pc.throw_range( *relevant ); + + return target_ui( pc, mode, relevant, range ); +} + +target_handler::trajectory target_handler::mode_reach( player &pc, item *weapon ) +{ + int range = weapon->current_reach_range( pc ); + return target_ui( pc, TARGET_MODE_REACH, weapon, range ); +} + +target_handler::trajectory target_handler::mode_turret_manual( player &pc, turret_data *turret ) +{ + item *turret_base = &*turret->base(); + int range = turret->range(); + const itype *ammo = turret->ammo_data(); + + return target_ui( pc, TARGET_MODE_TURRET_MANUAL, turret_base, range, ammo, turret ); +} + +target_handler::trajectory target_handler::mode_turrets( player &pc, vehicle *veh, + const std::vector *turrets ) +{ + // Find radius of a circle centered at u encompassing all points turrets can aim at + // FIXME: this calculation is fine for square distances, but results in an underestimation + // when used with real circles + int range_total = 0; + for( vehicle_part *t : *turrets ) { + int range = veh->turret_query( *t ).range(); + tripoint pos = veh->global_part_pos3( *t ); + + int res = 0; + res = std::max( res, rl_dist( g->u.pos(), pos + point( range, 0 ) ) ); + res = std::max( res, rl_dist( g->u.pos(), pos + point( -range, 0 ) ) ); + res = std::max( res, rl_dist( g->u.pos(), pos + point( 0, range ) ) ); + res = std::max( res, rl_dist( g->u.pos(), pos + point( 0, -range ) ) ); + range_total = std::max( range_total, res ); + } + + return target_ui( pc, TARGET_MODE_TURRET, nullptr, range_total, nullptr, nullptr, veh, *turrets ); +} + +target_handler::trajectory target_handler::mode_spell( spell *casting, bool no_fail, + bool no_mana ) +{ + return target_ui( *casting, no_fail, no_mana ); +} + +target_handler::trajectory target_handler::mode_spell( spell_id sp, bool no_fail, + bool no_mana ) +{ + return target_ui( sp, no_fail, no_mana ); +} + bool targeting_data::is_valid() const { return weapon_source != WEAPON_SOURCE_INVALID; @@ -1782,7 +1875,8 @@ std::vector target_handler::target_ui( player &pc, target_mode mode, add_msg( m_info, _( "You can't reload a %s!" ), relevant->tname() ); } else { item_location loc( pc, relevant ); - g->reload( loc, true ); + // TODO: make this compile. + // g->reload( loc, true ); ret.clear(); break; } diff --git a/src/ranged.h b/src/ranged.h index ceef5552ca208..4bd75f4c61e72 100644 --- a/src/ranged.h +++ b/src/ranged.h @@ -19,16 +19,6 @@ struct tripoint; struct vehicle_part; template struct enum_traits; -enum target_mode : int { - TARGET_MODE_FIRE, - TARGET_MODE_THROW, - TARGET_MODE_TURRET, - TARGET_MODE_TURRET_MANUAL, - TARGET_MODE_REACH, - TARGET_MODE_THROW_BLIND, - TARGET_MODE_SPELL -}; - /** * Specifies weapon source for aiming across turns and * (de-)serialization of targeting_data @@ -78,36 +68,30 @@ struct targeting_data { void deserialize( JsonIn &jsin ); }; -class target_handler +namespace target_handler { - // TODO: alias return type of target_ui - public: - /** - * Prompts for target and returns trajectory to it. - * TODO: pass arguments via constructor(s) and add methods for getting names and button labels, - * switching ammo & firing modes, drawing - stuff like that - * @param pc The player doing the targeting - * @param mode targeting mode, which affects UI display among other things. - * @param relevant active item, if any (for instance, a weapon to be aimed). - * @param range the maximum distance to which we're allowed to draw a target. - * @param ammo effective ammo data (derived from @param relevant if unspecified). - * @param turret turret being fired (relevant for TARGET_MODE_TURRET_MANUAL) - * @param veh vehicle that turrets belong to (relevant for TARGET_MODE_TURRET) - * @param vturrets vehicle turrets being aimed (relevant for TARGET_MODE_TURRET) - */ - std::vector target_ui( player &pc, target_mode mode, - item *relevant, int range, - const itype *ammo = nullptr, - turret_data *turret = nullptr, - vehicle *veh = nullptr, - const std::vector &vturrets = std::vector() - ); - // magic version of target_ui - std::vector target_ui( spell_id sp, bool no_fail = false, - bool no_mana = false ); - std::vector target_ui( spell &casting, bool no_fail = false, - bool no_mana = false ); -}; +// Trajectory to target. Empty if selection was aborted or player ran out of moves +using trajectory = std::vector; + +/** Firing ranged weapon. This mode allows spending moves on aiming. */ +trajectory mode_fire( player &pc, item *weapon ); + +/** Throwing item */ +trajectory mode_throw( player &pc, item *relevant, bool blind_throwing ); + +/** Reach attacking */ +trajectory mode_reach( player &pc, item *weapon ); + +/** Manually firing vehicle turret */ +trajectory mode_turret_manual( player &pc, turret_data *turret ); + +/** Selecting target for turrets (when using vehicle controls) */ +trajectory mode_turrets( player &pc, vehicle *veh, const std::vector *turrets ); + +/** Casting a spell */ +trajectory mode_spell( spell *casting, bool no_fail, bool no_mana ); +trajectory mode_spell( spell_id sp, bool no_fail, bool no_mana ); +} int range_with_even_chance_of_good_hit( int dispersion ); diff --git a/src/turret.cpp b/src/turret.cpp index 0993bf044fee9..2084d5d107128 100644 --- a/src/turret.cpp +++ b/src/turret.cpp @@ -388,25 +388,8 @@ bool vehicle::turrets_aim( std::vector &turrets ) t->reset_target( global_part_pos3( *t ) ); } - // Find radius of a circle centered at u encompassing all points turrets can aim at - // FIXME: this calculation is fine for square distances, but results in an underestimation - // when used with real circles - int range_total = 0; - for( vehicle_part *t : turrets ) { - int range = turret_query( *t ).range(); - tripoint pos = global_part_pos3( *t ); - - int res = 0; - res = std::max( res, rl_dist( g->u.pos(), pos + point( range, 0 ) ) ); - res = std::max( res, rl_dist( g->u.pos(), pos + point( -range, 0 ) ) ); - res = std::max( res, rl_dist( g->u.pos(), pos + point( 0, range ) ) ); - res = std::max( res, rl_dist( g->u.pos(), pos + point( 0, -range ) ) ); - range_total = std::max( range_total, res ); - } - // Get target - std::vector trajectory = target_handler().target_ui( g->u, TARGET_MODE_TURRET, nullptr, - range_total, nullptr, nullptr, this, turrets ); + target_handler::trajectory trajectory = target_handler::mode_turrets( g->u, this, &turrets ); bool got_target = !trajectory.empty(); if( got_target ) { From d8bfb43bd36c9a9bf77c3d12272a55f5bb8982a7 Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Sat, 18 Apr 2020 00:29:24 +0300 Subject: [PATCH 02/43] Pass arguments as class members --- src/activity_handlers.cpp | 2 +- src/ranged.cpp | 131 +++++++++++++++++++++++++------------- src/ranged.h | 4 +- 3 files changed, 88 insertions(+), 49 deletions(-) diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index 57190525bf474..7c8812dbe56ca 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -4780,7 +4780,7 @@ void activity_handlers::spellcasting_finish( player_activity *act, player *p ) if( spell_being_cast.range() > 0 && !spell_being_cast.is_valid_target( target_none ) && !spell_being_cast.has_flag( RANDOM_TARGET ) ) { do { - std::vector trajectory = target_handler::mode_spell( &spell_being_cast, no_fail, + std::vector trajectory = target_handler::mode_spell( *p, &spell_being_cast, no_fail, no_mana ); if( !trajectory.empty() ) { target = trajectory.back(); diff --git a/src/ranged.cpp b/src/ranged.cpp index e424f439fe180..ace6ca86ca254 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -113,53 +113,81 @@ enum target_mode : int { TARGET_MODE_SPELL }; -// TODO: Remove these forward declarations when done refactoring -namespace target_handler +class target_ui { -std::vector target_ui( player &pc, target_mode mode, - item *relevant, int range, - const itype *ammo = nullptr, - turret_data *turret = nullptr, - vehicle *veh = nullptr, - const std::vector &vturrets = std::vector() - ); -std::vector target_ui( spell_id sp, bool no_fail = false, - bool no_mana = false ); -std::vector target_ui( spell &casting, bool no_fail = false, - bool no_mana = false ); -} + public: + // Interface mode + target_mode mode = TARGET_MODE_FIRE; + // Weapon being fired/thrown + item *relevant = nullptr; + // Cached selection range from player's position + int range = 0; + // Cached current ammo to display + const itype *ammo = nullptr; + // Turret being manually fired + turret_data *turret = nullptr; + // Turrets being fired (via vehicle controls) + const std::vector *vturrets = nullptr; + // Vehicle that turrets belong to + vehicle *veh = nullptr; + // Spell being cast + spell *casting = nullptr; + // Spell cannot fail + bool no_fail = false; + // Spell does not require mana + bool no_mana = false; + + target_handler::trajectory run( player &pc ); + + private: + // TODO: break down these functions into methods + std::vector run_normal_ui_old( player &pc ); + std::vector run_spell_ui_old( player &pc ); +}; target_handler::trajectory target_handler::mode_fire( player &pc, item *weapon ) { + target_ui ui = target_ui(); + ui.mode = TARGET_MODE_FIRE; + ui.relevant = weapon; gun_mode gun = weapon->gun_current_mode(); - int range = gun.target->gun_range( &pc ); - const itype *ammo = gun->ammo_data(); + ui.range = gun.target->gun_range( &pc ); + ui.ammo = gun->ammo_data(); - return target_ui( pc, TARGET_MODE_FIRE, weapon, range, ammo ); + return ui.run( pc ); } target_handler::trajectory target_handler::mode_throw( player &pc, item *relevant, bool blind_throwing ) { - target_mode mode = blind_throwing ? TARGET_MODE_THROW_BLIND : TARGET_MODE_THROW; - int range = pc.throw_range( *relevant ); + target_ui ui = target_ui(); + ui.mode = blind_throwing ? TARGET_MODE_THROW_BLIND : TARGET_MODE_THROW; + ui.relevant = relevant; + ui.range = pc.throw_range( *relevant ); - return target_ui( pc, mode, relevant, range ); + return ui.run( pc ); } target_handler::trajectory target_handler::mode_reach( player &pc, item *weapon ) { - int range = weapon->current_reach_range( pc ); - return target_ui( pc, TARGET_MODE_REACH, weapon, range ); + target_ui ui = target_ui(); + ui.mode = TARGET_MODE_REACH; + ui.relevant = weapon; + ui.range = weapon->current_reach_range( pc ); + + return ui.run( pc ); } target_handler::trajectory target_handler::mode_turret_manual( player &pc, turret_data *turret ) { - item *turret_base = &*turret->base(); - int range = turret->range(); - const itype *ammo = turret->ammo_data(); - - return target_ui( pc, TARGET_MODE_TURRET_MANUAL, turret_base, range, ammo, turret ); + target_ui ui = target_ui(); + ui.mode = TARGET_MODE_TURRET_MANUAL; + ui.turret = turret; + ui.relevant = &*turret->base(); + ui.range = turret->range(); + ui.ammo = turret->ammo_data(); + + return ui.run( pc ); } target_handler::trajectory target_handler::mode_turrets( player &pc, vehicle *veh, @@ -181,19 +209,31 @@ target_handler::trajectory target_handler::mode_turrets( player &pc, vehicle *ve range_total = std::max( range_total, res ); } - return target_ui( pc, TARGET_MODE_TURRET, nullptr, range_total, nullptr, nullptr, veh, *turrets ); + target_ui ui = target_ui(); + ui.mode = TARGET_MODE_TURRET; + ui.veh = veh; + ui.vturrets = turrets; + ui.range = range_total; + + return ui.run( pc ); } -target_handler::trajectory target_handler::mode_spell( spell *casting, bool no_fail, +target_handler::trajectory target_handler::mode_spell( player &pc, spell *casting, bool no_fail, bool no_mana ) { - return target_ui( *casting, no_fail, no_mana ); + target_ui ui = target_ui(); + ui.mode = TARGET_MODE_SPELL; + ui.casting = casting; + ui.no_fail = no_fail; + ui.no_mana = no_mana; + + return ui.run( pc ); } -target_handler::trajectory target_handler::mode_spell( spell_id sp, bool no_fail, +target_handler::trajectory target_handler::mode_spell( player &pc, spell_id sp, bool no_fail, bool no_mana ) { - return target_ui( sp, no_fail, no_mana ); + return mode_spell( pc, &g->u.magic.get_spell( sp ), no_fail, no_mana ); } bool targeting_data::is_valid() const @@ -1437,9 +1477,7 @@ static void update_targets( player &pc, int range, std::vector &targ } // TODO: Shunt redundant drawing code elsewhere -std::vector target_handler::target_ui( player &pc, target_mode mode, - item *relevant, int range, const itype *ammo, turret_data *turret, vehicle *veh, - const std::vector &vturrets ) +std::vector target_ui::run_normal_ui_old( player &pc ) { // TODO: this should return a reference to a static vector which is cleared on each call. static const std::vector empty_result{}; @@ -1710,7 +1748,7 @@ std::vector target_handler::target_ui( player &pc, target_mode mode, predicted_delay ); } } else if( mode == TARGET_MODE_TURRET ) { - list_turrets_in_range( veh, vturrets, w_target, line_number, dst ); + list_turrets_in_range( veh, *vturrets, w_target, line_number, dst ); } else if( mode == TARGET_MODE_THROW && relevant ) { draw_throw_aim( pc, w_target, line_number, ctxt, *relevant, dst, false ); } else if( mode == TARGET_MODE_THROW_BLIND && relevant ) { @@ -2027,17 +2065,9 @@ std::vector target_handler::target_ui( player &pc, target_mode mode, return ret; } -// magic mod -std::vector target_handler::target_ui( spell_id sp, const bool no_fail, - const bool no_mana ) +std::vector target_ui::run_spell_ui_old( player &pc ) { - return target_ui( g->u.magic.get_spell( sp ), no_fail, no_mana ); -} -// does not have a targeting mode because we know this is the spellcasting version of this function -std::vector target_handler::target_ui( spell &casting, const bool no_fail, - const bool no_mana ) -{ - player &pc = g->u; + spell &casting = *this->casting; // TODO: make code use pointer if( !no_mana && !casting.can_cast( pc ) ) { pc.add_msg_if_player( m_bad, _( "You don't have enough %s to cast this spell" ), casting.energy_string() ); @@ -2757,3 +2787,12 @@ double player::gun_value( const item &weap, int ammo ) const capacity_factor ); return std::max( 0.0, gun_value ); } + +target_handler::trajectory target_ui::run( player &pc ) +{ + if( mode == TARGET_MODE_SPELL ) { + return run_spell_ui_old( pc ); + } else { + return run_normal_ui_old( pc ); + } +} diff --git a/src/ranged.h b/src/ranged.h index 4bd75f4c61e72..8f8473905db44 100644 --- a/src/ranged.h +++ b/src/ranged.h @@ -89,8 +89,8 @@ trajectory mode_turret_manual( player &pc, turret_data *turret ); trajectory mode_turrets( player &pc, vehicle *veh, const std::vector *turrets ); /** Casting a spell */ -trajectory mode_spell( spell *casting, bool no_fail, bool no_mana ); -trajectory mode_spell( spell_id sp, bool no_fail, bool no_mana ); +trajectory mode_spell( player &pc, spell *casting, bool no_fail, bool no_mana ); +trajectory mode_spell( player &pc, spell_id sp, bool no_fail, bool no_mana ); } int range_with_even_chance_of_good_hit( int dispersion ); From 949a0f57f5784af3dc7235fcd54a3e7a1d704df7 Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Sat, 18 Apr 2020 01:20:14 +0300 Subject: [PATCH 03/43] Remove duplicate code for window creation, input context setup, settings loading, trajectory buffer and cursor position initialization --- src/ranged.cpp | 204 +++++++++++++++++++++++-------------------------- 1 file changed, 94 insertions(+), 110 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index ace6ca86ca254..d21d17768b410 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -140,6 +140,36 @@ class target_ui target_handler::trajectory run( player &pc ); private: + // Current trajectory (TODO: better name) + std::vector ret; + // Aiming source (player's position) + tripoint src; + // Aiming destination (cursor position) + tripoint dst; + // List of visible hostile targets (TODO: better name) + std::vector t; + // Currently selected target (TODO: get rid of this) + int target = 0; + + // 'true' if map has z levels and 3D fov is on + bool allow_zlevel_shift; + // Snap camera to cursor. Can be permanently toggled in settings + // or temporarily in this window + bool snap_to_target; + + // Compact layout - slightly smaller then normal + bool compact; + // Tiny layout - smallest possible + bool tiny; + // Window width + int height; + // Window height + int width; + // Window + catacurses::window w_target; + // Input context + input_context ctxt; + // TODO: break down these functions into methods std::vector run_normal_ui_old( player &pc ); std::vector run_spell_ui_old( player &pc ); @@ -224,6 +254,7 @@ target_handler::trajectory target_handler::mode_spell( player &pc, spell *castin target_ui ui = target_ui(); ui.mode = TARGET_MODE_SPELL; ui.casting = casting; + ui.range = casting->range(); ui.no_fail = no_fail; ui.no_mana = no_mana; @@ -1479,74 +1510,14 @@ static void update_targets( player &pc, int range, std::vector &targ // TODO: Shunt redundant drawing code elsewhere std::vector target_ui::run_normal_ui_old( player &pc ) { - // TODO: this should return a reference to a static vector which is cleared on each call. - static const std::vector empty_result{}; - std::vector ret; - int sight_dispersion = 0; - if( relevant ) { + if( mode == TARGET_MODE_FIRE ) { sight_dispersion = pc.effective_dispersion( relevant->sight_dispersion() ); } - const bool allow_zlevel_shift = g->m.has_zlevels() && get_option( "FOV_3D" ); - - const tripoint src = pc.pos(); - tripoint dst = pc.pos(); - - std::vector t; - int target = 0; - - update_targets( pc, range, t, target, src, dst ); - double recoil_pc = pc.recoil; tripoint recoil_pos = dst; - bool compact = TERMY < 41; - bool tiny = TERMY < 31; - - // Default to the maximum window size we can use. - int height = 31; - int width = 55; - int top = 0; - if( tiny ) { - // If we're extremely short on space, use the whole sidebar. - height = TERMY; - } else if( compact ) { - // Cover up more low-value ui elements if we're tight on space. - height = 25; - } - catacurses::window w_target = catacurses::newwin( height, width, point( TERMX - width, top ) ); - - input_context ctxt( "TARGET" ); - ctxt.set_iso( true ); - // "ANY_INPUT" should be added before any real help strings - // Or strings will be written on window border. - ctxt.register_action( "ANY_INPUT" ); - ctxt.register_directions(); - ctxt.register_action( "COORDINATE" ); - ctxt.register_action( "SELECT" ); - ctxt.register_action( "FIRE" ); - ctxt.register_action( "NEXT_TARGET" ); - ctxt.register_action( "PREV_TARGET" ); - ctxt.register_action( "LEVEL_UP" ); - ctxt.register_action( "LEVEL_DOWN" ); - ctxt.register_action( "CENTER" ); - ctxt.register_action( "TOGGLE_SNAP_TO_TARGET" ); - ctxt.register_action( "HELP_KEYBINDINGS" ); - ctxt.register_action( "QUIT" ); - ctxt.register_action( "MOUSE_MOVE" ); - ctxt.register_action( "zoom_out" ); - ctxt.register_action( "zoom_in" ); - - if( mode == TARGET_MODE_FIRE || mode == TARGET_MODE_TURRET_MANUAL ) { - ctxt.register_action( "SWITCH_MODE" ); - ctxt.register_action( "SWITCH_AMMO" ); - } - if( mode == TARGET_MODE_FIRE ) { - ctxt.register_action( "AIM" ); - ctxt.register_action( "SWITCH_AIM" ); - } - std::vector aim_types; std::vector::iterator aim_mode; @@ -1566,8 +1537,6 @@ std::vector target_ui::run_normal_ui_old( player &pc ) int num_instruction_lines = draw_targeting_window( w_target, relevant ? relevant->tname() : _( "turrets" ), mode, ctxt, aim_types, tiny, src == dst ); - bool snap_to_target = get_option( "SNAP_TO_TARGET" ); - const auto set_last_target = [&pc]( const tripoint & dst ) { pc.last_target_pos = g->m.getabs( dst ); if( const Creature *const critter_ptr = g->critter_at( dst, true ) ) { @@ -1607,9 +1576,6 @@ std::vector target_ui::run_normal_ui_old( player &pc ) return std::min( recoil_pc + angle * recoil_per_deg, MAX_RECOIL ); }; - // FIXME: temporarily disable redrawing of lower UIs before this UI is migrated to `ui_adaptor` - ui_adaptor ui( ui_adaptor::disable_uis_below {} ); - bool redraw = true; const tripoint old_offset = pc.view_offset; do { @@ -1875,7 +1841,8 @@ std::vector target_ui::run_normal_ui_old( player &pc ) pc.assign_activity( ACT_AIM, 0, 0 ); pc.activity.str_values.push_back( "AIM" ); pc.view_offset = old_offset; - return empty_result; + ret.clear(); + return ret; } } else if( action == "SWITCH_MODE" ) { if( relevant && relevant->is_gun() ) { @@ -1973,7 +1940,8 @@ std::vector target_ui::run_normal_ui_old( player &pc ) pc.assign_activity( ACT_AIM, 0, 0 ); pc.activity.str_values.push_back( action ); pc.view_offset = old_offset; - return empty_result; + ret.clear(); + return ret; } } else if( action == "FIRE" ) { if( src == dst ) { @@ -2072,53 +2040,14 @@ std::vector target_ui::run_spell_ui_old( player &pc ) pc.add_msg_if_player( m_bad, _( "You don't have enough %s to cast this spell" ), casting.energy_string() ); } - bool compact = TERMY < 41; - bool tiny = TERMY < 31; - - // Default to the maximum window size we can use. - int height = 31; - int width = 55; - catacurses::window w_target = catacurses::newwin( height, width, point( TERMX - width, 0 ) ); - // TODO: this should return a reference to a static vector which is cleared on each call. - static const std::vector empty_result{}; - std::vector ret; std::set spell_aoe; - tripoint src = pc.pos(); - tripoint dst = pc.pos(); - - std::vector t; - int target = 0; - int range = static_cast( casting.range() ); - - update_targets( pc, range, t, target, src, dst ); - - input_context ctxt( "TARGET" ); - ctxt.set_iso( true ); - // "ANY_INPUT" should be added before any real help strings - // Or strings will be written on window border. - ctxt.register_action( "ANY_INPUT" ); - ctxt.register_directions(); - ctxt.register_action( "COORDINATE" ); - ctxt.register_action( "SELECT" ); - ctxt.register_action( "FIRE" ); - ctxt.register_action( "NEXT_TARGET" ); - ctxt.register_action( "PREV_TARGET" ); - ctxt.register_action( "LEVEL_UP" ); - ctxt.register_action( "LEVEL_DOWN" ); - ctxt.register_action( "CENTER" ); - ctxt.register_action( "TOGGLE_SNAP_TO_TARGET" ); - ctxt.register_action( "HELP_KEYBINDINGS" ); - ctxt.register_action( "QUIT" ); - std::vector aim_types; int num_instruction_lines = draw_targeting_window( w_target, casting.name(), TARGET_MODE_SPELL, ctxt, aim_types, tiny ); - bool snap_to_target = get_option( "SNAP_TO_TARGET" ); - const auto set_last_target = [&pc]( const tripoint & dst ) { pc.last_target_pos = g->m.getabs( dst ); if( const Creature *const critter_ptr = g->critter_at( dst, true ) ) { @@ -2144,9 +2073,6 @@ std::vector target_ui::run_spell_ui_old( player &pc ) const std::string fx = casting.effect(); const tripoint old_offset = pc.view_offset; - // FIXME: temporarily disable redrawing of lower UIs before this UI is migrated to `ui_adaptor` - ui_adaptor ui( ui_adaptor::disable_uis_below {} ); - do { ret = g->m.find_clear_path( src, dst ); @@ -2790,6 +2716,64 @@ double player::gun_value( const item &weap, int ammo ) const target_handler::trajectory target_ui::run( player &pc ) { + // Load settings + allow_zlevel_shift = g->m.has_zlevels() && get_option( "FOV_3D" ); + snap_to_target = get_option( "SNAP_TO_TARGET" ); + + // Initialize cursor position + src = pc.pos(); + dst = pc.pos(); + update_targets( pc, range, t, target, src, dst ); + + // Create window + compact = TERMY < 41; + tiny = TERMY < 31; + int top = 0; + width = 55; + if( tiny ) { + // If we're extremely short on space, use the whole sidebar. + height = TERMY; + } else if( compact ) { + // Cover up more low-value ui elements if we're tight on space. + height = 25; + } else { + // Go all out + height = 31; + } + w_target = catacurses::newwin( height, width, point( TERMX - width, top ) ); + + ctxt = input_context( "TARGET" ); + ctxt.set_iso( true ); + // "ANY_INPUT" should be added before any real help strings + // Or strings will be written on window border. + ctxt.register_action( "ANY_INPUT" ); + ctxt.register_directions(); + ctxt.register_action( "COORDINATE" ); + ctxt.register_action( "SELECT" ); + ctxt.register_action( "FIRE" ); + ctxt.register_action( "NEXT_TARGET" ); + ctxt.register_action( "PREV_TARGET" ); + ctxt.register_action( "LEVEL_UP" ); + ctxt.register_action( "LEVEL_DOWN" ); + ctxt.register_action( "CENTER" ); + ctxt.register_action( "TOGGLE_SNAP_TO_TARGET" ); + ctxt.register_action( "HELP_KEYBINDINGS" ); + ctxt.register_action( "QUIT" ); + ctxt.register_action( "MOUSE_MOVE" ); + ctxt.register_action( "zoom_out" ); + ctxt.register_action( "zoom_in" ); + if( mode == TARGET_MODE_FIRE || mode == TARGET_MODE_TURRET_MANUAL ) { + ctxt.register_action( "SWITCH_MODE" ); + ctxt.register_action( "SWITCH_AMMO" ); + } + if( mode == TARGET_MODE_FIRE ) { + ctxt.register_action( "AIM" ); + ctxt.register_action( "SWITCH_AIM" ); + } + + // FIXME: temporarily disable redrawing of lower UIs before this UI is migrated to `ui_adaptor` + ui_adaptor ui( ui_adaptor::disable_uis_below {} ); + if( mode == TARGET_MODE_SPELL ) { return run_spell_ui_old( pc ); } else { From 6a0b0302f57522917e98f7622f0b2f2d5b05fc27 Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Sat, 18 Apr 2020 01:43:22 +0300 Subject: [PATCH 04/43] Deduplicate checks fired when npc/monster has been selected as target --- src/ranged.cpp | 52 ++++++++++++++++++++++---------------------------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index d21d17768b410..a1042accb70e6 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -173,6 +173,10 @@ class target_ui // TODO: break down these functions into methods std::vector run_normal_ui_old( player &pc ); std::vector run_spell_ui_old( player &pc ); + + // On-selected-as-target checks that act as if they are on-hit checks. + // `harmful` is `false` if using a non-damaging spell + void on_target_accepted( player &pc, bool harmful ); }; target_handler::trajectory target_handler::mode_fire( player &pc, item *weapon ) @@ -2015,20 +2019,7 @@ std::vector target_ui::run_normal_ui_old( player &pc ) } set_last_target( ret.back() ); - - const auto lt_ptr = pc.last_target.lock(); - if( npc *const guy = dynamic_cast( lt_ptr.get() ) ) { - if( !guy->guaranteed_hostile() ) { - // TODO: get rid of this. Or combine it with effect_hit_by_player - guy->hit_by_player = true; // used for morale penalty - } - // TODO: should probably go into the on-hit code? - guy->make_angry(); - - } else if( monster *const mon = dynamic_cast( lt_ptr.get() ) ) { - // TODO: get rid of this. Or move into the on-hit code? - mon->add_effect( effect_hit_by_player, 10_minutes ); - } + on_target_accepted( pc, true ); wrefresh( w_target ); return ret; } @@ -2322,21 +2313,7 @@ std::vector target_ui::run_spell_ui_old( player &pc ) } set_last_target( ret.back() ); - - const auto lt_ptr = pc.last_target.lock(); - if( npc *const guy = dynamic_cast( lt_ptr.get() ) ) { - if( casting.damage() > 0 ) { - if( !guy->guaranteed_hostile() ) { - // TODO: get rid of this. Or combine it with effect_hit_by_player - guy->hit_by_player = true; // used for morale penalty - } - // TODO: should probably go into the on-hit code? - guy->make_angry(); - } - } else if( monster *const mon = dynamic_cast( lt_ptr.get() ) ) { - // TODO: get rid of this. Or move into the on-hit code? - mon->add_effect( effect_hit_by_player, 10_minutes ); - } + on_target_accepted( pc, casting.damage() > 0 ); wrefresh( w_target ); return ret; } @@ -2780,3 +2757,20 @@ target_handler::trajectory target_ui::run( player &pc ) return run_normal_ui_old( pc ); } } + +void target_ui::on_target_accepted( player &pc, bool harmful ) +{ + // TODO: all of this should be moved into on-hit code + const auto lt_ptr = pc.last_target.lock(); + if( npc *const guy = dynamic_cast( lt_ptr.get() ) ) { + if( harmful ) { + if( !guy->guaranteed_hostile() ) { + // TODO: get rid of this. Or combine it with effect_hit_by_player + guy->hit_by_player = true; // used for morale penalty + } + guy->make_angry(); + } + } else if( monster *const mon = dynamic_cast( lt_ptr.get() ) ) { + mon->add_effect( effect_hit_by_player, 10_minutes ); + } +} From 3529ad7b811af8202dc4e7f208b5f3b2a2f1fb6d Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Sat, 18 Apr 2020 20:07:00 +0300 Subject: [PATCH 05/43] Consolidate movement of aim point and center of view --- src/ranged.cpp | 381 +++++++++++++++++++++++-------------------------- 1 file changed, 181 insertions(+), 200 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index a1042accb70e6..00b03da769356 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -145,6 +145,7 @@ class target_ui // Aiming source (player's position) tripoint src; // Aiming destination (cursor position) + // Use set_cursor_pos() to modify tripoint dst; // List of visible hostile targets (TODO: better name) std::vector t; @@ -174,6 +175,22 @@ class target_ui std::vector run_normal_ui_old( player &pc ); std::vector run_spell_ui_old( player &pc ); + // Returns new cursor position or current cursor position + // based on user input + // TODO: betters docs/name? + tripoint get_desired_cursor_pos( const std::string &action ); + + // Set cursor position. If new position is out of range, + // selects closest position in range. + // Returns 'false' if cursor position did not change + bool set_cursor_pos( player &pc, const tripoint &new_pos ); + + // Toggle snap-to-target + void toggle_snap_to_target( player &pc ); + + // Set new view offset. Updates map cache if necessary + void set_view_offset( player &pc, const tripoint &new_offset ); + // On-selected-as-target checks that act as if they are on-hit checks. // `harmful` is `false` if using a non-damaging spell void on_target_accepted( player &pc, bool harmful ); @@ -1583,30 +1600,7 @@ std::vector target_ui::run_normal_ui_old( player &pc ) bool redraw = true; const tripoint old_offset = pc.view_offset; do { - ret = g->m.find_clear_path( src, dst ); - - // This chunk of code handles shifting the aim point around - // at maximum range when using circular distance. - // The range > 1 check ensures that you can always at least hit adjacent squares. - if( trigdist && range > 1 && std::round( trig_dist( src, dst ) ) > range ) { - bool cont = true; - tripoint cp = dst; - for( size_t i = 0; i < ret.size() && cont; i++ ) { - if( std::round( trig_dist( src, ret[i] ) ) > range ) { - ret.resize( i ); - cont = false; - } else { - cp = ret[i]; - } - } - dst = cp; - } - tripoint center; - if( snap_to_target ) { - center = dst; - } else { - center = pc.pos() + pc.view_offset; - } + tripoint center = pc.pos() + pc.view_offset; if( redraw ) { // Clear the target window. for( int i = 1; i <= getmaxy( w_target ) - num_instruction_lines - 2; i++ ) { @@ -1745,55 +1739,15 @@ std::vector target_ui::run_normal_ui_old( player &pc ) // Clear the activity if any, we'll re-set it later if we need to. pc.cancel_activity(); - tripoint targ; - cata::optional mouse_pos; - // Our coordinates will either be determined by coordinate input(mouse), - // by a direction key, or by the previous value. - if( action == "SELECT" && ( mouse_pos = ctxt.get_coordinates( g->w_terrain ) ) ) { - targ = *mouse_pos; - targ.x -= dst.x; - targ.y -= dst.y; - targ.z -= dst.z; - } else if( const cata::optional vec = ctxt.get_direction( action ) ) { - targ.x = vec->x; - targ.y = vec->y; - } else { - targ.x = 0; - targ.y = 0; - } if( action == "FIRE" && mode == TARGET_MODE_FIRE && aim_mode->has_threshold ) { action = aim_mode->action; } - if( allow_zlevel_shift && ( action == "LEVEL_UP" || action == "LEVEL_DOWN" ) ) { - // Just determine our delta-z. - const int dz = action == "LEVEL_UP" ? 1 : -1; - - // Shift the view up or down accordingly. - // We need to clamp the offset, but it needs to be clamped such that - // the player position plus the offset is still in range, since the player - // might be at Z+10 and looking down to Z-10, which is an offset greater than - // OVERMAP_DEPTH or OVERMAP_HEIGHT - const int potential_result = pc.pos().z + pc.view_offset.z + dz; - if( potential_result <= OVERMAP_HEIGHT && potential_result >= -OVERMAP_DEPTH ) { - pc.view_offset.z += dz; - } - - // Set our cursor z to our view z. This accounts for cases where - // our view and our target are on different z-levels (e.g. when - // we cycle targets on different z-levels but do not have SNAP_TO_TARGET - // enabled). This will ensure that we don't just chase the cursor up or - // down, never catching up. - dst.z = clamp( pc.pos().z + pc.view_offset.z, -OVERMAP_DEPTH, OVERMAP_HEIGHT ); - - // We need to do a bunch of redrawing and cache updates since we're - // looking at a different z-level. - g->m.invalidate_map_cache( dst.z ); - g->refresh_all(); - } + // Move cursor based on user input + tripoint new_dst = get_desired_cursor_pos( action ); /* More drawing to terrain */ - if( targ != tripoint_zero ) { + if( set_cursor_pos( pc, new_dst ) ) { const Creature *critter = g->critter_at( dst, true ); if( critter != nullptr ) { g->draw_critter( *critter, center ); @@ -1803,26 +1757,22 @@ std::vector target_ui::run_normal_ui_old( player &pc ) mvwputch( g->w_terrain, point( POSX, POSY ), c_black, 'X' ); } - // constrain by range - dst.x = std::min( std::max( dst.x + targ.x, src.x - range ), src.x + range ); - dst.y = std::min( std::max( dst.y + targ.y, src.y - range ), src.y + range ); - dst.z = std::min( std::max( dst.z + targ.z, src.z - range ), src.z + range ); - pc.recoil = recalc_recoil( dst ); - } else if( ( action == "PREV_TARGET" ) && ( target != -1 ) ) { int newtarget = find_target( t, dst ) - 1; if( newtarget < 0 ) { newtarget = t.size() - 1; } - dst = t[newtarget]->pos(); + tripoint new_dst = t[newtarget]->pos(); + set_cursor_pos( pc, new_dst ); pc.recoil = recalc_recoil( dst ); } else if( ( action == "NEXT_TARGET" ) && ( target != -1 ) ) { int newtarget = find_target( t, dst ) + 1; if( newtarget == static_cast( t.size() ) ) { newtarget = 0; } - dst = t[newtarget]->pos(); + tripoint new_dst = t[newtarget]->pos(); + set_cursor_pos( pc, new_dst ); pc.recoil = recalc_recoil( dst ); } else if( action == "AIM" ) { if( src == dst ) { @@ -1844,7 +1794,7 @@ std::vector target_ui::run_normal_ui_old( player &pc ) // We've run out of moves, clear target vector, but leave target selected. pc.assign_activity( ACT_AIM, 0, 0 ); pc.activity.str_values.push_back( "AIM" ); - pc.view_offset = old_offset; + set_view_offset( pc, old_offset ); ret.clear(); return ret; } @@ -1932,7 +1882,7 @@ std::vector target_ui::run_normal_ui_old( player &pc ) ( !g->critter_at( dst ) && pc.recoil == min_recoil ) ) { // If we made it under the aim threshold, go ahead and fire. // Also fire if we're at our best aim level already. - pc.view_offset = old_offset; + set_view_offset( pc, old_offset ); return ret; } else { @@ -1943,7 +1893,7 @@ std::vector target_ui::run_normal_ui_old( player &pc ) // Also clear target vector, but leave target selected. pc.assign_activity( ACT_AIM, 0, 0 ); pc.activity.str_values.push_back( action ); - pc.view_offset = old_offset; + set_view_offset( pc, old_offset ); ret.clear(); return ret; } @@ -1958,17 +1908,10 @@ std::vector target_ui::run_normal_ui_old( player &pc ) } break; } else if( action == "CENTER" ) { - pc.view_offset = tripoint_zero; - dst = src; - set_last_target( dst ); - ret.clear(); + set_cursor_pos( pc, src ); + set_last_target( src ); } else if( action == "TOGGLE_SNAP_TO_TARGET" ) { - if( snap_to_target ) { - snap_to_target = false; - pc.view_offset = dst - pc.pos(); - } else { - snap_to_target = true; - } + toggle_snap_to_target( pc ); } else if( action == "QUIT" ) { // return empty vector (cancel) ret.clear(); pc.last_target_pos = cata::nullopt; @@ -1985,34 +1928,17 @@ std::vector target_ui::run_normal_ui_old( player &pc ) if( action == "MOUSE_MOVE" ) { edge_scroll *= 2; } - pc.view_offset += edge_scroll; if( snap_to_target ) { - dst += edge_scroll; + tripoint new_dst = dst + edge_scroll; + set_cursor_pos( pc, new_dst ); + } else { + set_view_offset( pc, pc.view_offset + edge_scroll ); } } } - - int new_dx = dst.x - src.x; - int new_dy = dst.y - src.y; - - // Make player's sprite flip to face the current target - if( !tile_iso ) { - if( new_dx > 0 ) { - g->u.facing = FD_RIGHT; - } else if( new_dx < 0 ) { - g->u.facing = FD_LEFT; - } - } else { - if( new_dx >= 0 && new_dy >= 0 ) { - g->u.facing = FD_RIGHT; - } - if( new_dy <= 0 && new_dx <= 0 ) { - g->u.facing = FD_LEFT; - } - } } while( true ); - pc.view_offset = old_offset; + set_view_offset( pc, old_offset ); if( ret.empty() ) { return ret; @@ -2065,8 +1991,6 @@ std::vector target_ui::run_spell_ui_old( player &pc ) const tripoint old_offset = pc.view_offset; do { - ret = g->m.find_clear_path( src, dst ); - if( fx == "target_attack" || fx == "projectile_attack" || fx == "ter_transform" ) { spell_aoe = spell_effect::spell_effect_blast( casting, src, ret.back(), casting.aoe(), true ); } else if( fx == "cone_attack" ) { @@ -2077,28 +2001,7 @@ std::vector target_ui::run_spell_ui_old( player &pc ) spell_aoe.clear(); } - // This chunk of code handles shifting the aim point around - // at maximum range when using circular distance. - // The range > 1 check ensures that you can always at least hit adjacent squares. - if( trigdist && range > 1 && std::round( trig_dist( src, dst ) ) > range ) { - bool cont = true; - tripoint cp = dst; - for( size_t i = 0; i < ret.size() && cont; i++ ) { - if( std::round( trig_dist( src, ret[i] ) ) > range ) { - ret.resize( i ); - cont = false; - } else { - cp = ret[i]; - } - } - dst = cp; - } - tripoint center; - if( snap_to_target ) { - center = dst; - } else { - center = pc.pos() + pc.view_offset; - } + tripoint center = pc.pos() + pc.view_offset; // Clear the target window. for( int i = 1; i <= getmaxy( w_target ) - num_instruction_lines - 2; i++ ) { // Clear width excluding borders. @@ -2210,75 +2113,31 @@ std::vector target_ui::run_spell_ui_old( player &pc ) // Clear the activity if any, we'll re-set it later if we need to. pc.cancel_activity(); - tripoint targ; - cata::optional mouse_pos; - // Our coordinates will either be determined by coordinate input(mouse), - // by a direction key, or by the previous value. - if( action == "SELECT" && ( mouse_pos = ctxt.get_coordinates( g->w_terrain ) ) ) { - targ = *mouse_pos; - targ.x -= dst.x; - targ.y -= dst.y; - targ.z -= dst.z; - } else if( const cata::optional vec = ctxt.get_direction( action ) ) { - targ.x = vec->x; - targ.y = vec->y; - } else { - targ.x = 0; - targ.y = 0; - } - - if( g->m.has_zlevels() && ( action == "LEVEL_UP" || action == "LEVEL_DOWN" ) ) { - // Just determine our delta-z. - const int dz = action == "LEVEL_UP" ? 1 : -1; - - // Shift the view up or down accordingly. - // We need to clamp the offset, but it needs to be clamped such that - // the player position plus the offset is still in range, since the player - // might be at Z+10 and looking down to Z-10, which is an offset greater than - // OVERMAP_DEPTH or OVERMAP_HEIGHT - const int potential_result = pc.pos().z + pc.view_offset.z + dz; - if( potential_result <= OVERMAP_HEIGHT && potential_result >= -OVERMAP_DEPTH ) { - pc.view_offset.z += dz; - } - - // Set our cursor z to our view z. This accounts for cases where - // our view and our target are on different z-levels (e.g. when - // we cycle targets on different z-levels but do not have SNAP_TO_TARGET - // enabled). This will ensure that we don't just chase the cursor up or - // down, never catching up. - dst.z = clamp( pc.pos().z + pc.view_offset.z, -OVERMAP_DEPTH, OVERMAP_HEIGHT ); - - // We need to do a bunch of redrawing and cache updates since we're - // looking at a different z-level. - g->refresh_all(); - } + // Move cursor based on user input + tripoint new_dst = get_desired_cursor_pos( action ); /* More drawing to terrain */ - if( targ != tripoint_zero ) { + if( set_cursor_pos( pc, new_dst ) ) { const Creature *critter = g->critter_at( dst, true ); if( critter != nullptr ) { g->draw_critter( *critter, center ); } else if( g->m.pl_sees( dst, -1 ) ) { g->m.drawsq( g->w_terrain, pc, dst, false, true, center ); } - - // constrain by range - dst.x = std::min( std::max( dst.x + targ.x, src.x - range ), src.x + range ); - dst.y = std::min( std::max( dst.y + targ.y, src.y - range ), src.y + range ); - dst.z = std::min( std::max( dst.z + targ.z, src.z - range ), src.z + range ); - } else if( ( action == "PREV_TARGET" ) && ( target != -1 ) ) { int newtarget = find_target( t, dst ) - 1; if( newtarget < 0 ) { newtarget = t.size() - 1; } - dst = t[newtarget]->pos(); + tripoint new_dst = t[newtarget]->pos(); + set_cursor_pos( pc, new_dst ); } else if( ( action == "NEXT_TARGET" ) && ( target != -1 ) ) { int newtarget = find_target( t, dst ) + 1; if( newtarget == static_cast( t.size() ) ) { newtarget = 0; } - dst = t[newtarget]->pos(); + tripoint new_dst = t[newtarget]->pos(); + set_cursor_pos( pc, new_dst ); } else if( action == "FIRE" ) { if( casting.damage() > 0 && !confirm_non_enemy_target( dst ) ) { continue; @@ -2286,27 +2145,18 @@ std::vector target_ui::run_spell_ui_old( player &pc ) find_target( t, dst ); break; } else if( action == "CENTER" ) { - dst = src; - set_last_target( dst ); - ret.clear(); + set_cursor_pos( pc, src ); + set_last_target( src ); } else if( action == "TOGGLE_SNAP_TO_TARGET" ) { - snap_to_target = !snap_to_target; + toggle_snap_to_target( pc ); } else if( action == "QUIT" ) { // return empty vector (cancel) ret.clear(); pc.last_target_pos = cata::nullopt; break; } - - // Make player's sprite flip to face the current target - if( dst.x > src.x ) { - g->u.facing = FD_RIGHT; - } else if( dst.x < src.x ) { - g->u.facing = FD_LEFT; - } - } while( true ); - pc.view_offset = old_offset; + set_view_offset( pc, old_offset ); if( ret.empty() || ret.back() == pc.pos() ) { return ret; @@ -2699,8 +2549,9 @@ target_handler::trajectory target_ui::run( player &pc ) // Initialize cursor position src = pc.pos(); - dst = pc.pos(); - update_targets( pc, range, t, target, src, dst ); + tripoint initial_dst = pc.pos(); + update_targets( pc, range, t, target, src, initial_dst ); + set_cursor_pos( pc, initial_dst ); // Create window compact = TERMY < 41; @@ -2758,6 +2609,136 @@ target_handler::trajectory target_ui::run( player &pc ) } } +tripoint target_ui::get_desired_cursor_pos( const std::string &action ) +{ + // Our coordinates will either be determined by coordinate input(mouse), + // by a direction key, or by the previous value. + tripoint new_dst = dst; + cata::optional mouse_pos; + if( action == "SELECT" && ( mouse_pos = ctxt.get_coordinates( g->w_terrain ) ) ) { + new_dst = *mouse_pos; + } else if( const cata::optional vec = ctxt.get_direction( action ) ) { + new_dst.x += vec->x; + new_dst.y += vec->y; + } + if( allow_zlevel_shift && ( action == "LEVEL_UP" || action == "LEVEL_DOWN" ) ) { + const int delta_z = action == "LEVEL_UP" ? 1 : -1; + new_dst.z += delta_z; + } + return new_dst; +} + +bool target_ui::set_cursor_pos( player &pc, const tripoint &new_pos ) +{ + if( dst == new_pos ) { + return false; + } + + // Make sure new position is valid or find a closest valid position + std::vector new_traj; + tripoint valid_pos = new_pos; + if( new_pos != src ) { + // On Z axis, make sure we do not exceed map boundaries + valid_pos.z = clamp( valid_pos.z, -OVERMAP_DEPTH, OVERMAP_HEIGHT ); + + new_traj = g->m.find_clear_path( src, valid_pos ); + if( range == 1 ) { + // We should always be able to hit adjacent squares + if( square_dist( src, valid_pos ) > 1 ) { + valid_pos = new_traj[0]; + } + } else if( trigdist ) { + const auto dist_fn = [this]( const tripoint & p ) { + return static_cast( std::round( trig_dist( this->src, p ) ) ); + }; + + if( dist_fn( valid_pos ) > range ) { + // Find the farthest point that is still in range + for( size_t i = new_traj.size(); i > 0; i-- ) { + if( dist_fn( new_traj[i - 1] ) <= range ) { + valid_pos = new_traj[i - 1]; + break; + } + } + + // FIXME: due to a bug in map::find_clear_path (#39693), + // returned trajectory is invalid in some cases. + // This bandaid stops us from exceeding range, + // but does not fix the issue. + if( dist_fn( valid_pos ) > range ) { + debugmsg( "Exceeded allowed range!" ); + valid_pos = src; + } + } + } else { + tripoint delta = valid_pos - src; + valid_pos = src + tripoint( + clamp( delta.x, -range, range ), + clamp( delta.y, -range, range ), + clamp( delta.z, -range, range ) + ); + } + } + + if( valid_pos == dst ) { + // We don't need to move the cursor after all + return false; + } else if( new_pos == valid_pos ) { + // We can reuse new_traj + dst = valid_pos; + ret = new_traj; + } else { + dst = valid_pos; + ret = g->m.find_clear_path( src, dst ); + } + + if( snap_to_target ) { + set_view_offset( pc, dst - src ); + } + + // Make player's sprite flip to face the current target + int dx = dst.x - src.x; + int dy = dst.y - src.y; + if( !tile_iso ) { + if( dx > 0 ) { + g->u.facing = FD_RIGHT; + } else if( dx < 0 ) { + g->u.facing = FD_LEFT; + } + } else { + if( dx >= 0 && dy >= 0 ) { + g->u.facing = FD_RIGHT; + } + if( dy <= 0 && dx <= 0 ) { + g->u.facing = FD_LEFT; + } + } + + return true; +} + +void target_ui::toggle_snap_to_target( player &pc ) +{ + if( snap_to_target ) { + // Keep current view offset + } else { + set_view_offset( pc, dst - src ); + } + snap_to_target = !snap_to_target; +} + +void target_ui::set_view_offset( player &pc, const tripoint &new_offset ) +{ + // TODO: player's sprite disappears when shifting view back into player's z level + if( pc.view_offset.z != new_offset.z ) { + // We need to do a bunch of redrawing and cache updates since we're + // looking at a different z-level. + g->m.invalidate_map_cache( new_offset.z ); + g->refresh_all(); + } + pc.view_offset = new_offset; +} + void target_ui::on_target_accepted( player &pc, bool harmful ) { // TODO: all of this should be moved into on-hit code From 7965e6d50d16bf561be2bfee39315639c72848fa Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Sat, 18 Apr 2020 23:53:35 +0300 Subject: [PATCH 06/43] Cache critter under cursor; deduplicate 'set_as_target'; change player's last target only when necessary --- src/ranged.cpp | 52 +++++++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index 00b03da769356..77897240cf75f 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -147,6 +147,9 @@ class target_ui // Aiming destination (cursor position) // Use set_cursor_pos() to modify tripoint dst; + // Creature currently under cursor + // nullptr if aiming at empty tile or yourself + Creature *dst_critter = nullptr; // List of visible hostile targets (TODO: better name) std::vector t; // Currently selected target (TODO: get rid of this) @@ -185,6 +188,9 @@ class target_ui // Returns 'false' if cursor position did not change bool set_cursor_pos( player &pc, const tripoint &new_pos ); + // Set creature (or tile) under cursor as player's last target + void set_last_target( player &pc ); + // Toggle snap-to-target void toggle_snap_to_target( player &pc ); @@ -1558,15 +1564,6 @@ std::vector target_ui::run_normal_ui_old( player &pc ) int num_instruction_lines = draw_targeting_window( w_target, relevant ? relevant->tname() : _( "turrets" ), mode, ctxt, aim_types, tiny, src == dst ); - const auto set_last_target = [&pc]( const tripoint & dst ) { - pc.last_target_pos = g->m.getabs( dst ); - if( const Creature *const critter_ptr = g->critter_at( dst, true ) ) { - pc.last_target = g->shared_from( *critter_ptr ); - } else { - pc.last_target.reset(); - } - }; - const auto confirm_non_enemy_target = [&pc]( const tripoint & dst ) { if( dst == pc.pos() ) { return true; @@ -1785,7 +1782,7 @@ std::vector target_ui::run_normal_ui_old( player &pc ) recoil_pc = pc.recoil; recoil_pos = dst; - set_last_target( dst ); + set_last_target( pc ); const double min_recoil = calculate_aim_cap( pc, dst ); for( int i = 0; i < 10; ++i ) { do_aim( pc, *relevant, min_recoil ); @@ -1869,7 +1866,7 @@ std::vector target_ui::run_normal_ui_old( player &pc ) aim_mode = aim_types.begin(); } int aim_threshold = it->threshold; - set_last_target( dst ); + set_last_target( pc ); const double min_recoil = calculate_aim_cap( pc, dst ); do { do_aim( pc, relevant ? *relevant : null_item_reference(), min_recoil ); @@ -1909,12 +1906,10 @@ std::vector target_ui::run_normal_ui_old( player &pc ) break; } else if( action == "CENTER" ) { set_cursor_pos( pc, src ); - set_last_target( src ); } else if( action == "TOGGLE_SNAP_TO_TARGET" ) { toggle_snap_to_target( pc ); } else if( action == "QUIT" ) { // return empty vector (cancel) ret.clear(); - pc.last_target_pos = cata::nullopt; break; } else if( action == "zoom_in" ) { g->zoom_in(); @@ -1944,7 +1939,6 @@ std::vector target_ui::run_normal_ui_old( player &pc ) return ret; } - set_last_target( ret.back() ); on_target_accepted( pc, true ); wrefresh( w_target ); return ret; @@ -1965,15 +1959,6 @@ std::vector target_ui::run_spell_ui_old( player &pc ) int num_instruction_lines = draw_targeting_window( w_target, casting.name(), TARGET_MODE_SPELL, ctxt, aim_types, tiny ); - const auto set_last_target = [&pc]( const tripoint & dst ) { - pc.last_target_pos = g->m.getabs( dst ); - if( const Creature *const critter_ptr = g->critter_at( dst, true ) ) { - pc.last_target = g->shared_from( *critter_ptr ); - } else { - pc.last_target.reset(); - } - }; - const auto confirm_non_enemy_target = [&pc]( const tripoint & dst ) { if( dst == pc.pos() ) { return true; @@ -2143,15 +2128,14 @@ std::vector target_ui::run_spell_ui_old( player &pc ) continue; } find_target( t, dst ); + set_last_target( pc ); break; } else if( action == "CENTER" ) { set_cursor_pos( pc, src ); - set_last_target( src ); } else if( action == "TOGGLE_SNAP_TO_TARGET" ) { toggle_snap_to_target( pc ); } else if( action == "QUIT" ) { // return empty vector (cancel) ret.clear(); - pc.last_target_pos = cata::nullopt; break; } } while( true ); @@ -2162,7 +2146,6 @@ std::vector target_ui::run_spell_ui_old( player &pc ) return ret; } - set_last_target( ret.back() ); on_target_accepted( pc, casting.damage() > 0 ); wrefresh( w_target ); return ret; @@ -2714,9 +2697,26 @@ bool target_ui::set_cursor_pos( player &pc, const tripoint &new_pos ) } } + // Cache creature under cursor + if( src != dst ) { + dst_critter = g->critter_at( dst, true ); + } else { + dst_critter = nullptr; + } + return true; } +void target_ui::set_last_target( player &pc ) +{ + pc.last_target_pos = g->m.getabs( dst ); + if( dst_critter ) { + pc.last_target = g->shared_from( *dst_critter ); + } else { + pc.last_target.reset(); + } +} + void target_ui::toggle_snap_to_target( player &pc ) { if( snap_to_target ) { From 5764f04a42af273e2059b3f8579db4156a55c38c Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Sat, 18 Apr 2020 23:54:33 +0300 Subject: [PATCH 07/43] Deduplicate and refine 'confirm_non_enemy_target' --- src/ranged.cpp | 47 +++++++++++++++-------------------------------- 1 file changed, 15 insertions(+), 32 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index 77897240cf75f..66c1662ed3f67 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -191,6 +191,9 @@ class target_ui // Set creature (or tile) under cursor as player's last target void set_last_target( player &pc ); + // Prompts player to confirm attack on neutral NPC + bool confirm_non_enemy_target(); + // Toggle snap-to-target void toggle_snap_to_target( player &pc ); @@ -1564,22 +1567,6 @@ std::vector target_ui::run_normal_ui_old( player &pc ) int num_instruction_lines = draw_targeting_window( w_target, relevant ? relevant->tname() : _( "turrets" ), mode, ctxt, aim_types, tiny, src == dst ); - const auto confirm_non_enemy_target = [&pc]( const tripoint & dst ) { - if( dst == pc.pos() ) { - return true; - } - if( npc *const who_ = g->critter_at( dst, false ) ) { - const npc &who = *who_; - if( who.guaranteed_hostile() ) { - return true; - } - if( g->u.sees( who ) ) { - return query_yn( _( "Really attack %s?" ), who.name ); - } - } - return true; - }; - auto recalc_recoil = [&recoil_pc, &recoil_pos, &pc]( tripoint & dst ) { if( pc.pos() == dst || pc.pos() == recoil_pos ) { return MAX_RECOIL; @@ -1849,7 +1836,7 @@ std::vector target_ui::run_normal_ui_old( player &pc ) // This action basically means "FIRE" as well, the actual firing may be delayed // through aiming, but there is usually no means to stop it. Therefore we query here. - if( !confirm_non_enemy_target( dst ) ) { + if( !confirm_non_enemy_target() ) { continue; } @@ -1900,7 +1887,7 @@ std::vector target_ui::run_normal_ui_old( player &pc ) // TODO: Consider allowing firing vehicle turret at yourself continue; } - if( !confirm_non_enemy_target( dst ) ) { + if( !confirm_non_enemy_target() ) { continue; } break; @@ -1959,19 +1946,6 @@ std::vector target_ui::run_spell_ui_old( player &pc ) int num_instruction_lines = draw_targeting_window( w_target, casting.name(), TARGET_MODE_SPELL, ctxt, aim_types, tiny ); - const auto confirm_non_enemy_target = [&pc]( const tripoint & dst ) { - if( dst == pc.pos() ) { - return true; - } - if( npc *const who_ = g->critter_at( dst, false ) ) { - const npc &who = *who_; - if( who.guaranteed_hostile() ) { - return true; - } - return query_yn( _( "Really attack %s?" ), who.name.c_str() ); - } - return true; - }; const std::string fx = casting.effect(); const tripoint old_offset = pc.view_offset; @@ -2124,7 +2098,7 @@ std::vector target_ui::run_spell_ui_old( player &pc ) tripoint new_dst = t[newtarget]->pos(); set_cursor_pos( pc, new_dst ); } else if( action == "FIRE" ) { - if( casting.damage() > 0 && !confirm_non_enemy_target( dst ) ) { + if( casting.damage() > 0 && !confirm_non_enemy_target() ) { continue; } find_target( t, dst ); @@ -2717,6 +2691,15 @@ void target_ui::set_last_target( player &pc ) } } +bool target_ui::confirm_non_enemy_target() +{ + npc *const who = dynamic_cast( dst_critter ); + if( who && !who->guaranteed_hostile() ) { + return query_yn( _( "Really attack %s?" ), who->name.c_str() ); + } + return true; +} + void target_ui::toggle_snap_to_target( player &pc ) { if( snap_to_target ) { From e5e1475cd4dc91cb345380484dab7b63c0551e0a Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Sat, 18 Apr 2020 23:54:41 +0300 Subject: [PATCH 08/43] Deduplicate and refine target cycling --- src/ranged.cpp | 94 ++++++++++++++++++++++++-------------------------- 1 file changed, 46 insertions(+), 48 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index 66c1662ed3f67..2b9b736d1a877 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -150,10 +150,8 @@ class target_ui // Creature currently under cursor // nullptr if aiming at empty tile or yourself Creature *dst_critter = nullptr; - // List of visible hostile targets (TODO: better name) - std::vector t; - // Currently selected target (TODO: get rid of this) - int target = 0; + // List of visible hostile targets + std::vector targets; // 'true' if map has z levels and 3D fov is on bool allow_zlevel_shift; @@ -197,6 +195,9 @@ class target_ui // Toggle snap-to-target void toggle_snap_to_target( player &pc ); + // Cycle targets. 'direction' is either 1 or -1 + void cycle_targets( player &pc, int direction ); + // Set new view offset. Updates map cache if necessary void set_view_offset( player &pc, const tripoint &new_offset ); @@ -1132,16 +1133,6 @@ static int draw_targeting_window( const catacurses::window &w_target, const std: return lines_used; } -static int find_target( const std::vector &t, const tripoint &tpos ) -{ - for( size_t i = 0; i < t.size(); ++i ) { - if( t[i]->pos() == tpos ) { - return static_cast( i ); - } - } - return -1; -} - static void do_aim( player &p, const item &relevant, const double min_recoil ) { const double aim_amount = p.aim_per_move( relevant, p.recoil ); @@ -1611,11 +1602,11 @@ std::vector target_ui::run_normal_ui_old( player &pc ) // Print to target window mvwprintw( w_target, point( 1, line_number++ ), _( "Range: %d/%d Elevation: %d Targets: %d" ), - rl_dist( src, dst ), range, relative_elevation, t.size() ); + rl_dist( src, dst ), range, relative_elevation, targets.size() ); } else { mvwprintw( w_target, point( 1, line_number++ ), _( "Range: %d Elevation: %d Targets: %d" ), range, - relative_elevation, t.size() ); + relative_elevation, targets.size() ); } // Skip blank lines if we're short on space. @@ -1742,21 +1733,11 @@ std::vector target_ui::run_normal_ui_old( player &pc ) } pc.recoil = recalc_recoil( dst ); - } else if( ( action == "PREV_TARGET" ) && ( target != -1 ) ) { - int newtarget = find_target( t, dst ) - 1; - if( newtarget < 0 ) { - newtarget = t.size() - 1; - } - tripoint new_dst = t[newtarget]->pos(); - set_cursor_pos( pc, new_dst ); + } else if( action == "PREV_TARGET" ) { + cycle_targets( pc, -1 ); pc.recoil = recalc_recoil( dst ); - } else if( ( action == "NEXT_TARGET" ) && ( target != -1 ) ) { - int newtarget = find_target( t, dst ) + 1; - if( newtarget == static_cast( t.size() ) ) { - newtarget = 0; - } - tripoint new_dst = t[newtarget]->pos(); - set_cursor_pos( pc, new_dst ); + } else if( action == "NEXT_TARGET" ) { + cycle_targets( pc, 1 ); pc.recoil = recalc_recoil( dst ); } else if( action == "AIM" ) { if( src == dst ) { @@ -2010,11 +1991,11 @@ std::vector target_ui::run_spell_ui_old( player &pc ) // Print to target window mvwprintw( w_target, point( 1, line_number++ ), _( "Range: %d/%d Elevation: %d Targets: %d" ), - rl_dist( src, dst ), range, relative_elevation, t.size() ); + rl_dist( src, dst ), range, relative_elevation, targets.size() ); } else { mvwprintw( w_target, point( 1, line_number++ ), _( "Range: %d Elevation: %d Targets: %d" ), range, - relative_elevation, t.size() ); + relative_elevation, targets.size() ); } g->draw_cursor( dst ); @@ -2083,25 +2064,14 @@ std::vector target_ui::run_spell_ui_old( player &pc ) } else if( g->m.pl_sees( dst, -1 ) ) { g->m.drawsq( g->w_terrain, pc, dst, false, true, center ); } - } else if( ( action == "PREV_TARGET" ) && ( target != -1 ) ) { - int newtarget = find_target( t, dst ) - 1; - if( newtarget < 0 ) { - newtarget = t.size() - 1; - } - tripoint new_dst = t[newtarget]->pos(); - set_cursor_pos( pc, new_dst ); - } else if( ( action == "NEXT_TARGET" ) && ( target != -1 ) ) { - int newtarget = find_target( t, dst ) + 1; - if( newtarget == static_cast( t.size() ) ) { - newtarget = 0; - } - tripoint new_dst = t[newtarget]->pos(); - set_cursor_pos( pc, new_dst ); + } else if( action == "PREV_TARGET" ) { + cycle_targets( pc, -1 ); + } else if( action == "NEXT_TARGET" ) { + cycle_targets( pc, 1 ); } else if( action == "FIRE" ) { if( casting.damage() > 0 && !confirm_non_enemy_target() ) { continue; } - find_target( t, dst ); set_last_target( pc ); break; } else if( action == "CENTER" ) { @@ -2507,7 +2477,8 @@ target_handler::trajectory target_ui::run( player &pc ) // Initialize cursor position src = pc.pos(); tripoint initial_dst = pc.pos(); - update_targets( pc, range, t, target, src, initial_dst ); + int _target_idx = 0; + update_targets( pc, range, targets, _target_idx, src, initial_dst ); set_cursor_pos( pc, initial_dst ); // Create window @@ -2710,6 +2681,33 @@ void target_ui::toggle_snap_to_target( player &pc ) snap_to_target = !snap_to_target; } +void target_ui::cycle_targets( player &pc, int direction ) +{ + if( targets.empty() ) { + // Nothing to cycle + return; + } + + if( dst_critter ) { + auto t = std::find( targets.begin(), targets.end(), dst_critter ); + size_t new_target = 0; + if( t != targets.end() ) { + size_t idx = std::distance( targets.begin(), t ); + new_target = ( idx + targets.size() + direction ) % targets.size(); + set_cursor_pos( pc, targets[new_target]->pos() ); + return; + } + } + + // There is either no creature under the cursor or the player can't see it. + // Use the closest/farthest target in this case + if( direction == 1 ) { + set_cursor_pos( pc, targets.front()->pos() ); + } else { + set_cursor_pos( pc, targets.back()->pos() ); + } +} + void target_ui::set_view_offset( player &pc, const tripoint &new_offset ) { // TODO: player's sprite disappears when shifting view back into player's z level From 74cbe455c23a5dc716e78817b41973811f43f85f Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Sun, 19 Apr 2020 00:30:17 +0300 Subject: [PATCH 09/43] Extract some variables used by FIRE and SPELL modes --- src/ranged.cpp | 99 +++++++++++++++++++++++++++----------------------- 1 file changed, 54 insertions(+), 45 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index 2b9b736d1a877..c1c896c5acf3f 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -172,6 +172,20 @@ class target_ui // Input context input_context ctxt; + /* These members are relevant for TARGET_MODE_FIRE */ + // Weapon sight dispersion + int sight_dispersion = 0; + // List of available weapon aim types + std::vector aim_types; + // Currently selected aim mode + std::vector::iterator aim_mode; + + /* These members are relevant for TARGET_MODE_SPELL */ + // For AOE spells, list of tiles affected by the spell + std::set spell_aoe; + // Spell casting effect + std::string spell_fx; + // TODO: break down these functions into methods std::vector run_normal_ui_old( player &pc ); std::vector run_spell_ui_old( player &pc ); @@ -1531,28 +1545,9 @@ static void update_targets( player &pc, int range, std::vector &targ // TODO: Shunt redundant drawing code elsewhere std::vector target_ui::run_normal_ui_old( player &pc ) { - int sight_dispersion = 0; - if( mode == TARGET_MODE_FIRE ) { - sight_dispersion = pc.effective_dispersion( relevant->sight_dispersion() ); - } - double recoil_pc = pc.recoil; tripoint recoil_pos = dst; - std::vector aim_types; - std::vector::iterator aim_mode; - - if( mode == TARGET_MODE_FIRE ) { - // get_aim_types works for non-guns (null item) - aim_types = pc.get_aim_types( relevant ? *relevant : null_item_reference() ); - for( aim_type &type : aim_types ) { - if( type.has_threshold ) { - ctxt.register_action( type.action ); - } - } - aim_mode = aim_types.begin(); - } - // TODO: this assumes that relevant == null means firing turrets, but that may not // always be the case. Consider passing a name into this function. int num_instruction_lines = draw_targeting_window( w_target, @@ -1915,32 +1910,13 @@ std::vector target_ui::run_normal_ui_old( player &pc ) std::vector target_ui::run_spell_ui_old( player &pc ) { spell &casting = *this->casting; // TODO: make code use pointer - if( !no_mana && !casting.can_cast( pc ) ) { - pc.add_msg_if_player( m_bad, _( "You don't have enough %s to cast this spell" ), - casting.energy_string() ); - } - - std::set spell_aoe; - - std::vector aim_types; int num_instruction_lines = draw_targeting_window( w_target, casting.name(), TARGET_MODE_SPELL, ctxt, aim_types, tiny ); - const std::string fx = casting.effect(); const tripoint old_offset = pc.view_offset; do { - if( fx == "target_attack" || fx == "projectile_attack" || fx == "ter_transform" ) { - spell_aoe = spell_effect::spell_effect_blast( casting, src, ret.back(), casting.aoe(), true ); - } else if( fx == "cone_attack" ) { - spell_aoe = spell_effect::spell_effect_cone( casting, src, ret.back(), casting.aoe(), true ); - } else if( fx == "line_attack" ) { - spell_aoe = spell_effect::spell_effect_line( casting, src, ret.back(), casting.aoe(), true ); - } else { - spell_aoe.clear(); - } - tripoint center = pc.pos() + pc.view_offset; // Clear the target window. for( int i = 1; i <= getmaxy( w_target ) - num_instruction_lines - 2; i++ ) { @@ -2470,17 +2446,18 @@ double player::gun_value( const item &weap, int ammo ) const target_handler::trajectory target_ui::run( player &pc ) { + if( mode == TARGET_MODE_SPELL ) { + if( !no_mana && !casting->can_cast( pc ) ) { + pc.add_msg_if_player( m_bad, _( "You don't have enough %s to cast this spell" ), + casting->energy_string() ); + } + spell_fx = casting->effect(); + } + // Load settings allow_zlevel_shift = g->m.has_zlevels() && get_option( "FOV_3D" ); snap_to_target = get_option( "SNAP_TO_TARGET" ); - // Initialize cursor position - src = pc.pos(); - tripoint initial_dst = pc.pos(); - int _target_idx = 0; - update_targets( pc, range, targets, _target_idx, src, initial_dst ); - set_cursor_pos( pc, initial_dst ); - // Create window compact = TERMY < 41; tiny = TERMY < 31; @@ -2527,9 +2504,29 @@ target_handler::trajectory target_ui::run( player &pc ) ctxt.register_action( "SWITCH_AIM" ); } + if( mode == TARGET_MODE_FIRE ) { + sight_dispersion = pc.effective_dispersion( relevant->sight_dispersion() ); + + aim_types = pc.get_aim_types( *relevant ); + for( aim_type &type : aim_types ) { + if( type.has_threshold ) { + ctxt.register_action( type.action ); + } + } + + aim_mode = aim_types.begin(); + } + // FIXME: temporarily disable redrawing of lower UIs before this UI is migrated to `ui_adaptor` ui_adaptor ui( ui_adaptor::disable_uis_below {} ); + // Initialize cursor position + src = pc.pos(); + tripoint initial_dst = pc.pos(); + int _target_idx = 0; + update_targets( pc, range, targets, _target_idx, src, initial_dst ); + set_cursor_pos( pc, initial_dst ); + if( mode == TARGET_MODE_SPELL ) { return run_spell_ui_old( pc ); } else { @@ -2649,6 +2646,18 @@ bool target_ui::set_cursor_pos( player &pc, const tripoint &new_pos ) dst_critter = nullptr; } + // Update tiles affected by AOE spells + if( spell_fx == "target_attack" || spell_fx == "projectile_attack" || + spell_fx == "ter_transform" ) { + spell_aoe = spell_effect::spell_effect_blast( *casting, src, dst, casting->aoe(), true ); + } else if( spell_fx == "cone_attack" ) { + spell_aoe = spell_effect::spell_effect_cone( *casting, src, dst, casting->aoe(), true ); + } else if( spell_fx == "line_attack" ) { + spell_aoe = spell_effect::spell_effect_line( *casting, src, dst, casting->aoe(), true ); + } else { + spell_aoe.clear(); + } + return true; } From 64e9345ca530441a267e73ca65a3634824a2b1a5 Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Sun, 19 Apr 2020 02:18:59 +0300 Subject: [PATCH 10/43] Refactor how penalty from moving aim point applies to current aim --- src/ranged.cpp | 103 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 77 insertions(+), 26 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index c1c896c5acf3f..b24245bee7ed3 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -179,6 +179,12 @@ class target_ui std::vector aim_types; // Currently selected aim mode std::vector::iterator aim_mode; + // 'Recoil' value the player will reach if they + // start aiming at cursor position. Equals player's + // 'recoil' while they are actively spending moves to aim, + // but increases the further away the new aim point will be + // relative to the current one. + double predicted_recoil; /* These members are relevant for TARGET_MODE_SPELL */ // For AOE spells, list of tiles affected by the spell @@ -215,6 +221,16 @@ class target_ui // Set new view offset. Updates map cache if necessary void set_view_offset( player &pc, const tripoint &new_offset ); + // Recalculate 'recoil' penalty. This should be called if + // player's 'recoil' value has been modified + // Relevant for TARGET_MODE_FIRE + void recalc_aim_turning_penalty( player &pc ); + + // Apply penalty to player's 'recoil' value based on + // how much they moved their aim point. + // Relevant for TARGET_MODE_FIRE + void apply_aim_turning_penalty( player &pc ); + // On-selected-as-target checks that act as if they are on-hit checks. // `harmful` is `false` if using a non-damaging spell void on_target_accepted( player &pc, bool harmful ); @@ -1545,28 +1561,11 @@ static void update_targets( player &pc, int range, std::vector &targ // TODO: Shunt redundant drawing code elsewhere std::vector target_ui::run_normal_ui_old( player &pc ) { - double recoil_pc = pc.recoil; - tripoint recoil_pos = dst; - // TODO: this assumes that relevant == null means firing turrets, but that may not // always be the case. Consider passing a name into this function. int num_instruction_lines = draw_targeting_window( w_target, relevant ? relevant->tname() : _( "turrets" ), mode, ctxt, aim_types, tiny, src == dst ); - auto recalc_recoil = [&recoil_pc, &recoil_pos, &pc]( tripoint & dst ) { - if( pc.pos() == dst || pc.pos() == recoil_pos ) { - return MAX_RECOIL; - } - static const double recoil_per_deg = MAX_RECOIL / 180; - - const double phi = fmod( std::abs( coord_to_angle( pc.pos(), dst ) - - coord_to_angle( pc.pos(), recoil_pos ) ), - 360.0 ); - const double angle = phi > 180.0 ? 360.0 - phi : phi; - - return std::min( recoil_pc + angle * recoil_per_deg, MAX_RECOIL ); - }; - bool redraw = true; const tripoint old_offset = pc.view_offset; do { @@ -1656,6 +1655,10 @@ std::vector target_ui::run_normal_ui_old( player &pc ) // Assumes that relevant == null means firing turrets (maybe even multiple at once), // so printing their firing mode / ammo / ... of one of them is misleading. if( relevant && mode == TARGET_MODE_FIRE && src != dst ) { + // These 2 lines here keep the good ol' code working during the trying times of refactoring + double saved_pc_recoil = pc.recoil; + pc.recoil = predicted_recoil; + double predicted_recoil = pc.recoil; int predicted_delay = 0; if( aim_mode->has_threshold && aim_mode->threshold < pc.recoil ) { @@ -1681,6 +1684,9 @@ std::vector target_ui::run_normal_ui_old( player &pc ) mvwprintw( w_target, point( 1, line_number++ ), _( "%s Delay: %i" ), aim_mode->name, predicted_delay ); } + + // End of old code compatibility + pc.recoil = saved_pc_recoil; } else if( mode == TARGET_MODE_TURRET ) { list_turrets_in_range( veh, *vturrets, w_target, line_number, dst ); } else if( mode == TARGET_MODE_THROW && relevant ) { @@ -1726,14 +1732,10 @@ std::vector target_ui::run_normal_ui_old( player &pc ) } else { mvwputch( g->w_terrain, point( POSX, POSY ), c_black, 'X' ); } - - pc.recoil = recalc_recoil( dst ); } else if( action == "PREV_TARGET" ) { cycle_targets( pc, -1 ); - pc.recoil = recalc_recoil( dst ); } else if( action == "NEXT_TARGET" ) { cycle_targets( pc, 1 ); - pc.recoil = recalc_recoil( dst ); } else if( action == "AIM" ) { if( src == dst ) { // Skip this action if no target selected @@ -1742,10 +1744,8 @@ std::vector target_ui::run_normal_ui_old( player &pc ) // No confirm_non_enemy_target here because we have not initiated the firing. // Aiming can be stopped / aborted at any time. - recoil_pc = pc.recoil; - recoil_pos = dst; - set_last_target( pc ); + apply_aim_turning_penalty( pc ); const double min_recoil = calculate_aim_cap( pc, dst ); for( int i = 0; i < 10; ++i ) { do_aim( pc, *relevant, min_recoil ); @@ -1758,6 +1758,8 @@ std::vector target_ui::run_normal_ui_old( player &pc ) ret.clear(); return ret; } + // We've changed pc.recoil, update penalty + recalc_aim_turning_penalty( pc ); } else if( action == "SWITCH_MODE" ) { if( relevant && relevant->is_gun() ) { relevant->gun_cycle_mode(); @@ -1816,8 +1818,6 @@ std::vector target_ui::run_normal_ui_old( player &pc ) continue; } - recoil_pc = pc.recoil; - recoil_pos = dst; std::vector::iterator it; for( it = aim_types.begin(); it != aim_types.end(); it++ ) { if( action == it->action ) { @@ -1830,6 +1830,7 @@ std::vector target_ui::run_normal_ui_old( player &pc ) } int aim_threshold = it->threshold; set_last_target( pc ); + apply_aim_turning_penalty( pc ); const double min_recoil = calculate_aim_cap( pc, dst ); do { do_aim( pc, relevant ? *relevant : null_item_reference(), min_recoil ); @@ -2658,6 +2659,11 @@ bool target_ui::set_cursor_pos( player &pc, const tripoint &new_pos ) spell_aoe.clear(); } + // Update predicted 'recoil' + if( mode == TARGET_MODE_FIRE ) { + recalc_aim_turning_penalty( pc ); + } + return true; } @@ -2729,6 +2735,51 @@ void target_ui::set_view_offset( player &pc, const tripoint &new_offset ) pc.view_offset = new_offset; } +void target_ui::recalc_aim_turning_penalty( player &pc ) +{ + if( dst == src ) { + // Can't aim at yourself + predicted_recoil = MAX_RECOIL; + return; + } + + double curr_recoil = pc.recoil; + tripoint curr_recoil_pos; + const Creature *lt_ptr = pc.last_target.lock().get(); + if( lt_ptr ) { + curr_recoil_pos = lt_ptr->pos(); + } else if( pc.last_target_pos ) { + curr_recoil_pos = g->m.getlocal( *pc.last_target_pos ); + } else { + curr_recoil_pos = src; + // TODO: last_target and last_target_pos are both set to null when target dies, + // making automatic weapons not as efficient against groups of enemies as they should be + // (since you need to re-aim your gun from zero for every Zed). + } + + if( curr_recoil_pos == dst ) { + // We're aiming at that point right now, no penalty + predicted_recoil = curr_recoil; + } else if( curr_recoil_pos == src ) { + // The player wasn't aiming anywhere, max it out + predicted_recoil = MAX_RECOIL; + } else { + // Raise it proportionally to how much + // the player has to turn from previous aiming point + const double recoil_per_degree = MAX_RECOIL / 180.0; + const double angle_curr = coord_to_angle( src, curr_recoil_pos ); + const double angle_desired = coord_to_angle( src, dst ); + const double phi = fmod( std::abs( angle_curr - angle_desired ), 360.0 ); + const double angle = phi > 180.0 ? 360.0 - phi : phi; + predicted_recoil = std::min( MAX_RECOIL, curr_recoil + angle * recoil_per_degree ); + } +} + +void target_ui::apply_aim_turning_penalty( player &pc ) +{ + pc.recoil = predicted_recoil; +} + void target_ui::on_target_accepted( player &pc, bool harmful ) { // TODO: all of this should be moved into on-hit code From b677379c02c111d113e41bd8a8d821d9b97cc712 Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Sun, 19 Apr 2020 04:45:52 +0300 Subject: [PATCH 11/43] Deduplicate event loop --- src/ranged.cpp | 1033 +++++++++++++++++++++++++----------------------- 1 file changed, 532 insertions(+), 501 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index b24245bee7ed3..eccdebf5811b3 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -171,6 +171,8 @@ class target_ui catacurses::window w_target; // Input context input_context ctxt; + // Number of free instruction lines + int num_instruction_lines; /* These members are relevant for TARGET_MODE_FIRE */ // Weapon sight dispersion @@ -192,15 +194,16 @@ class target_ui // Spell casting effect std::string spell_fx; - // TODO: break down these functions into methods - std::vector run_normal_ui_old( player &pc ); - std::vector run_spell_ui_old( player &pc ); - // Returns new cursor position or current cursor position // based on user input // TODO: betters docs/name? tripoint get_desired_cursor_pos( const std::string &action ); + // Handle input related to cursor movement. + // Returns 'true' if action was recognized and processed. + // 'skip_redraw' is set to 'true' if there is no need to redraw the UI. + bool handle_cursor_movement( player &pc, const std::string &action, bool &skip_redraw ); + // Set cursor position. If new position is out of range, // selects closest position in range. // Returns 'false' if cursor position did not change @@ -231,6 +234,22 @@ class target_ui // Relevant for TARGET_MODE_FIRE void apply_aim_turning_penalty( player &pc ); + // Switch firing mode. + void action_switch_mode( player &pc ); + + // Switch ammo. Returns 'false' if requires a reloading UI. + bool action_switch_ammo(); + + // Aim for 10 turns. Returns 'false' if ran out of moves + bool action_aim( player &pc ); + + // Aim and shoot. Returns 'false' if ran out of moves + bool action_aim_and_shoot( player &pc, const std::string &action ); + + // TODO: finish breaking down drawing + void draw_normal_ui_old( player &pc ); + void draw_spell_ui_old( player &pc ); + // On-selected-as-target checks that act as if they are on-hit checks. // `harmful` is `false` if using a non-damaging spell void on_target_accepted( player &pc, bool harmful ); @@ -1558,518 +1577,261 @@ static void update_targets( player &pc, int range, std::vector &targ } } -// TODO: Shunt redundant drawing code elsewhere -std::vector target_ui::run_normal_ui_old( player &pc ) +// TODO: You ARE the redundant drawing code now, mwa-ha-ha! +void target_ui::draw_normal_ui_old( player &pc ) { - // TODO: this assumes that relevant == null means firing turrets, but that may not - // always be the case. Consider passing a name into this function. - int num_instruction_lines = draw_targeting_window( w_target, - relevant ? relevant->tname() : _( "turrets" ), mode, ctxt, aim_types, tiny, src == dst ); - - bool redraw = true; - const tripoint old_offset = pc.view_offset; - do { - tripoint center = pc.pos() + pc.view_offset; - if( redraw ) { - // Clear the target window. - for( int i = 1; i <= getmaxy( w_target ) - num_instruction_lines - 2; i++ ) { - // Clear width excluding borders. - for( int j = 1; j <= getmaxx( w_target ) - 2; j++ ) { - mvwputch( w_target, point( j, i ), c_white, ' ' ); - } - } - g->draw_ter( center, true ); - int line_number = 1; - Creature *critter = g->critter_at( dst, true ); - const int relative_elevation = dst.z - pc.pos().z; - if( dst != src ) { - // Only draw those tiles which are on current z-level - auto ret_this_zlevel = ret; - ret_this_zlevel.erase( std::remove_if( ret_this_zlevel.begin(), ret_this_zlevel.end(), - [¢er]( const tripoint & pt ) { - return pt.z != center.z; - } ), ret_this_zlevel.end() ); - // Only draw a highlighted trajectory if we can see the endpoint. - // Provides feedback to the player, and avoids leaking information - // about tiles they can't see. - g->draw_line( dst, center, ret_this_zlevel ); - - // Print to target window - mvwprintw( w_target, point( 1, line_number++ ), _( "Range: %d/%d Elevation: %d Targets: %d" ), - rl_dist( src, dst ), range, relative_elevation, targets.size() ); + tripoint center = pc.pos() + pc.view_offset; + // Clear the target window. + for( int i = 1; i <= getmaxy( w_target ) - num_instruction_lines - 2; i++ ) { + // Clear width excluding borders. + for( int j = 1; j <= getmaxx( w_target ) - 2; j++ ) { + mvwputch( w_target, point( j, i ), c_white, ' ' ); + } + } + g->draw_ter( center, true ); + int line_number = 1; + Creature *critter = g->critter_at( dst, true ); + const int relative_elevation = dst.z - pc.pos().z; + if( dst != src ) { + // Only draw those tiles which are on current z-level + auto ret_this_zlevel = ret; + ret_this_zlevel.erase( std::remove_if( ret_this_zlevel.begin(), ret_this_zlevel.end(), + [¢er]( const tripoint & pt ) { + return pt.z != center.z; + } ), ret_this_zlevel.end() ); + // Only draw a highlighted trajectory if we can see the endpoint. + // Provides feedback to the player, and avoids leaking information + // about tiles they can't see. + g->draw_line( dst, center, ret_this_zlevel ); + + // Print to target window + mvwprintw( w_target, point( 1, line_number++ ), _( "Range: %d/%d Elevation: %d Targets: %d" ), + rl_dist( src, dst ), range, relative_elevation, targets.size() ); - } else { - mvwprintw( w_target, point( 1, line_number++ ), _( "Range: %d Elevation: %d Targets: %d" ), range, - relative_elevation, targets.size() ); - } - - // Skip blank lines if we're short on space. - if( !compact ) { - line_number++; - } - // Assumes that relevant == null means firing turrets (maybe even multiple at once), - // so printing their firing mode / ammo / ... of one of them is misleading. - if( relevant && ( mode == TARGET_MODE_FIRE || mode == TARGET_MODE_TURRET_MANUAL ) ) { - auto m = relevant->gun_current_mode(); - std::string str; - nc_color col = c_light_gray; - if( relevant != m.target ) { - str = string_format( _( "Firing mode: %s %s (%d)" ), - m->tname(), m.tname(), m.qty ); - - print_colored_text( w_target, point( 1, line_number++ ), col, col, str ); - } else { - str = string_format( _( "Firing mode: %s (%d)" ), - m.tname(), m.qty ); - print_colored_text( w_target, point( 1, line_number++ ), col, col, str ); - } + } else { + mvwprintw( w_target, point( 1, line_number++ ), _( "Range: %d Elevation: %d Targets: %d" ), range, + relative_elevation, targets.size() ); + } - const itype *cur = ammo ? ammo : m->ammo_data(); - if( cur ) { - auto str = string_format( m->ammo_remaining() ? _( "Ammo: %s (%d/%d)" ) : _( "Ammo: %s" ), - colorize( cur->nname( std::max( m->ammo_remaining(), 1 ) ), cur->color ), - m->ammo_remaining(), m->ammo_capacity() ); + // Skip blank lines if we're short on space. + if( !compact ) { + line_number++; + } + // Assumes that relevant == null means firing turrets (maybe even multiple at once), + // so printing their firing mode / ammo / ... of one of them is misleading. + if( relevant && ( mode == TARGET_MODE_FIRE || mode == TARGET_MODE_TURRET_MANUAL ) ) { + auto m = relevant->gun_current_mode(); + std::string str; + nc_color col = c_light_gray; + if( relevant != m.target ) { + str = string_format( _( "Firing mode: %s %s (%d)" ), + m->tname(), m.tname(), m.qty ); - print_colored_text( w_target, point( 1, line_number++ ), col, col, str ); - } + print_colored_text( w_target, point( 1, line_number++ ), col, col, str ); + } else { + str = string_format( _( "Firing mode: %s (%d)" ), + m.tname(), m.qty ); + print_colored_text( w_target, point( 1, line_number++ ), col, col, str ); + } - print_colored_text( w_target, point( 1, line_number++ ), col, col, string_format( _( "%s" ), - print_recoil( g->u ) ) ); + const itype *cur = ammo ? ammo : m->ammo_data(); + if( cur ) { + auto str = string_format( m->ammo_remaining() ? _( "Ammo: %s (%d/%d)" ) : _( "Ammo: %s" ), + colorize( cur->nname( std::max( m->ammo_remaining(), 1 ) ), cur->color ), + m->ammo_remaining(), m->ammo_capacity() ); - // Skip blank lines if we're short on space. - if( !compact ) { - line_number++; - } - } + print_colored_text( w_target, point( 1, line_number++ ), col, col, str ); + } - if( critter && critter != &pc && pc.sees( *critter ) ) { - // The 12 is 2 for the border and 10 for aim bars. - // Just print the monster name if we're short on space. - int available_lines = compact ? 1 : ( height - num_instruction_lines - line_number - 12 ); - line_number = critter->print_info( w_target, line_number, available_lines, 1 ); - } else { - mvwputch( g->w_terrain, -center.xy() + dst.xy() + point( POSX, POSY ), - c_red, '*' ); - } + print_colored_text( w_target, point( 1, line_number++ ), col, col, string_format( _( "%s" ), + print_recoil( g->u ) ) ); - // Assumes that relevant == null means firing turrets (maybe even multiple at once), - // so printing their firing mode / ammo / ... of one of them is misleading. - if( relevant && mode == TARGET_MODE_FIRE && src != dst ) { - // These 2 lines here keep the good ol' code working during the trying times of refactoring - double saved_pc_recoil = pc.recoil; - pc.recoil = predicted_recoil; - - double predicted_recoil = pc.recoil; - int predicted_delay = 0; - if( aim_mode->has_threshold && aim_mode->threshold < pc.recoil ) { - do { - const double aim_amount = pc.aim_per_move( *relevant, predicted_recoil ); - if( aim_amount > 0 ) { - predicted_delay++; - predicted_recoil = std::max( predicted_recoil - aim_amount, 0.0 ); - } - } while( predicted_recoil > aim_mode->threshold && - predicted_recoil - sight_dispersion > 0 ); - } else { - predicted_recoil = pc.recoil; - } + // Skip blank lines if we're short on space. + if( !compact ) { + line_number++; + } + } - const double target_size = critter != nullptr ? critter->ranged_target_size() : - occupied_tile_fraction( MS_MEDIUM ); + if( critter && critter != &pc && pc.sees( *critter ) ) { + // The 12 is 2 for the border and 10 for aim bars. + // Just print the monster name if we're short on space. + int available_lines = compact ? 1 : ( height - num_instruction_lines - line_number - 12 ); + line_number = critter->print_info( w_target, line_number, available_lines, 1 ); + } else { + mvwputch( g->w_terrain, -center.xy() + dst.xy() + point( POSX, POSY ), + c_red, '*' ); + } - line_number = print_aim( pc, w_target, line_number, ctxt, &*relevant->gun_current_mode(), - target_size, dst, predicted_recoil ); + // Assumes that relevant == null means firing turrets (maybe even multiple at once), + // so printing their firing mode / ammo / ... of one of them is misleading. + if( relevant && mode == TARGET_MODE_FIRE && src != dst ) { + // These 2 lines here keep the good ol' code working during the trying times of refactoring + double saved_pc_recoil = pc.recoil; + pc.recoil = predicted_recoil; - if( aim_mode->has_threshold ) { - mvwprintw( w_target, point( 1, line_number++ ), _( "%s Delay: %i" ), aim_mode->name, - predicted_delay ); + double predicted_recoil = pc.recoil; + int predicted_delay = 0; + if( aim_mode->has_threshold && aim_mode->threshold < pc.recoil ) { + do { + const double aim_amount = pc.aim_per_move( *relevant, predicted_recoil ); + if( aim_amount > 0 ) { + predicted_delay++; + predicted_recoil = std::max( predicted_recoil - aim_amount, 0.0 ); } - - // End of old code compatibility - pc.recoil = saved_pc_recoil; - } else if( mode == TARGET_MODE_TURRET ) { - list_turrets_in_range( veh, *vturrets, w_target, line_number, dst ); - } else if( mode == TARGET_MODE_THROW && relevant ) { - draw_throw_aim( pc, w_target, line_number, ctxt, *relevant, dst, false ); - } else if( mode == TARGET_MODE_THROW_BLIND && relevant ) { - draw_throw_aim( pc, w_target, line_number, ctxt, *relevant, dst, true ); - } - - wrefresh( g->w_terrain ); - g->draw_panels(); - draw_targeting_window( w_target, relevant ? relevant->tname() : _( "turrets" ), - mode, ctxt, aim_types, tiny, src == dst ); - wrefresh( w_target ); - - catacurses::refresh(); - } - redraw = true; - std::string action; - if( pc.activity.id() == ACT_AIM && pc.activity.str_values[0] != "AIM" ) { - // If we're in 'aim and shoot' mode, - // skip retrieving input and go straight to the action. - action = pc.activity.str_values[0]; + } while( predicted_recoil > aim_mode->threshold && + predicted_recoil - sight_dispersion > 0 ); } else { - action = ctxt.handle_input( get_option( "EDGE_SCROLL" ) ); - } - // Clear the activity if any, we'll re-set it later if we need to. - pc.cancel_activity(); - - if( action == "FIRE" && mode == TARGET_MODE_FIRE && aim_mode->has_threshold ) { - action = aim_mode->action; + predicted_recoil = pc.recoil; } - // Move cursor based on user input - tripoint new_dst = get_desired_cursor_pos( action ); - - /* More drawing to terrain */ - if( set_cursor_pos( pc, new_dst ) ) { - const Creature *critter = g->critter_at( dst, true ); - if( critter != nullptr ) { - g->draw_critter( *critter, center ); - } else if( g->m.pl_sees( dst, -1 ) ) { - g->m.drawsq( g->w_terrain, pc, dst, false, true, center ); - } else { - mvwputch( g->w_terrain, point( POSX, POSY ), c_black, 'X' ); - } - } else if( action == "PREV_TARGET" ) { - cycle_targets( pc, -1 ); - } else if( action == "NEXT_TARGET" ) { - cycle_targets( pc, 1 ); - } else if( action == "AIM" ) { - if( src == dst ) { - // Skip this action if no target selected - continue; - } - - // No confirm_non_enemy_target here because we have not initiated the firing. - // Aiming can be stopped / aborted at any time. - set_last_target( pc ); - apply_aim_turning_penalty( pc ); - const double min_recoil = calculate_aim_cap( pc, dst ); - for( int i = 0; i < 10; ++i ) { - do_aim( pc, *relevant, min_recoil ); - } - if( pc.moves <= 0 ) { - // We've run out of moves, clear target vector, but leave target selected. - pc.assign_activity( ACT_AIM, 0, 0 ); - pc.activity.str_values.push_back( "AIM" ); - set_view_offset( pc, old_offset ); - ret.clear(); - return ret; - } - // We've changed pc.recoil, update penalty - recalc_aim_turning_penalty( pc ); - } else if( action == "SWITCH_MODE" ) { - if( relevant && relevant->is_gun() ) { - relevant->gun_cycle_mode(); - if( relevant->gun_current_mode().flags.count( "REACH_ATTACK" ) ) { - relevant->gun_cycle_mode(); - } - if( mode == TARGET_MODE_TURRET_MANUAL ) { - itype_id ammo_current = turret->ammo_current(); - if( ammo_current == "null" ) { - ammo = nullptr; - range = 0; - } else { - ammo = item::find_type( ammo_current ); - range = turret->range(); - } - } else { - ammo = relevant->gun_current_mode().target->ammo_data(); - range = relevant->gun_current_mode().target->gun_range( &pc ); - } - } - } else if( action == "SWITCH_AMMO" ) { - if( !relevant ) { - // skip this action - } else if( mode == TARGET_MODE_TURRET_MANUAL ) { - // For turrets that use vehicle tanks & can fire multiple liquids - if( turret->ammo_options().size() > 1 ) { - const auto opts = turret->ammo_options(); - auto iter = opts.find( turret->ammo_current() ); - turret->ammo_select( ++iter != opts.end() ? *iter : *opts.begin() ); - ammo = item::find_type( turret->ammo_current() ); - range = turret->range(); - } - } else if( !pc.has_item( *relevant ) ) { - add_msg( m_info, _( "You can't reload a %s!" ), relevant->tname() ); - } else { - item_location loc( pc, relevant ); - // TODO: make this compile. - // g->reload( loc, true ); - ret.clear(); - break; - } - } else if( action == "SWITCH_AIM" ) { - aim_mode++; - if( aim_mode == aim_types.end() ) { - aim_mode = aim_types.begin(); - } - } else if( action == "AIMED_SHOT" || action == "CAREFUL_SHOT" || action == "PRECISE_SHOT" ) { - if( src == dst ) { - // Skip this action if no target selected - continue; - } - - // This action basically means "FIRE" as well, the actual firing may be delayed - // through aiming, but there is usually no means to stop it. Therefore we query here. - if( !confirm_non_enemy_target() ) { - continue; - } + const double target_size = critter != nullptr ? critter->ranged_target_size() : + occupied_tile_fraction( MS_MEDIUM ); - std::vector::iterator it; - for( it = aim_types.begin(); it != aim_types.end(); it++ ) { - if( action == it->action ) { - break; - } - } - if( it == aim_types.end() ) { - debugmsg( "Could not find a valid aim_type for %s", action.c_str() ); - aim_mode = aim_types.begin(); - } - int aim_threshold = it->threshold; - set_last_target( pc ); - apply_aim_turning_penalty( pc ); - const double min_recoil = calculate_aim_cap( pc, dst ); - do { - do_aim( pc, relevant ? *relevant : null_item_reference(), min_recoil ); - } while( pc.moves > 0 && pc.recoil > aim_threshold && pc.recoil - sight_dispersion > min_recoil ); - - if( pc.recoil <= aim_threshold || - pc.recoil - sight_dispersion == min_recoil || - // if no critter is at dst then sight dispersion does not apply, - // so it would lock into an infinite loop - ( !g->critter_at( dst ) && pc.recoil == min_recoil ) ) { - // If we made it under the aim threshold, go ahead and fire. - // Also fire if we're at our best aim level already. - set_view_offset( pc, old_offset ); - return ret; + line_number = print_aim( pc, w_target, line_number, ctxt, &*relevant->gun_current_mode(), + target_size, dst, predicted_recoil ); - } else { - // We've run out of moves, set the activity to aim so we'll - // automatically re-enter the targeting menu next turn. - // Set the string value of the aim action to the right thing - // so we re-enter this loop. - // Also clear target vector, but leave target selected. - pc.assign_activity( ACT_AIM, 0, 0 ); - pc.activity.str_values.push_back( action ); - set_view_offset( pc, old_offset ); - ret.clear(); - return ret; - } - } else if( action == "FIRE" ) { - if( src == dst ) { - // Skip this action if no target selected - // TODO: Consider allowing firing vehicle turret at yourself - continue; - } - if( !confirm_non_enemy_target() ) { - continue; - } - break; - } else if( action == "CENTER" ) { - set_cursor_pos( pc, src ); - } else if( action == "TOGGLE_SNAP_TO_TARGET" ) { - toggle_snap_to_target( pc ); - } else if( action == "QUIT" ) { // return empty vector (cancel) - ret.clear(); - break; - } else if( action == "zoom_in" ) { - g->zoom_in(); - } else if( action == "zoom_out" ) { - g->zoom_out(); - } else if( action == "MOUSE_MOVE" || action == "TIMEOUT" ) { - tripoint edge_scroll = g->mouse_edge_scrolling_terrain( ctxt ); - if( edge_scroll == tripoint_zero ) { - redraw = false; - } else { - if( action == "MOUSE_MOVE" ) { - edge_scroll *= 2; - } - if( snap_to_target ) { - tripoint new_dst = dst + edge_scroll; - set_cursor_pos( pc, new_dst ); - } else { - set_view_offset( pc, pc.view_offset + edge_scroll ); - } - } + if( aim_mode->has_threshold ) { + mvwprintw( w_target, point( 1, line_number++ ), _( "%s Delay: %i" ), aim_mode->name, + predicted_delay ); } - } while( true ); - - set_view_offset( pc, old_offset ); - if( ret.empty() ) { - return ret; + // End of old code compatibility + pc.recoil = saved_pc_recoil; + } else if( mode == TARGET_MODE_TURRET ) { + list_turrets_in_range( veh, *vturrets, w_target, line_number, dst ); + } else if( mode == TARGET_MODE_THROW && relevant ) { + draw_throw_aim( pc, w_target, line_number, ctxt, *relevant, dst, false ); + } else if( mode == TARGET_MODE_THROW_BLIND && relevant ) { + draw_throw_aim( pc, w_target, line_number, ctxt, *relevant, dst, true ); } - on_target_accepted( pc, true ); + wrefresh( g->w_terrain ); + g->draw_panels(); + draw_targeting_window( w_target, relevant ? relevant->tname() : _( "turrets" ), + mode, ctxt, aim_types, tiny, src == dst ); wrefresh( w_target ); - return ret; + + catacurses::refresh(); + + if( critter != nullptr ) { + g->draw_critter( *critter, center ); + } else if( g->m.pl_sees( dst, -1 ) ) { + g->m.drawsq( g->w_terrain, pc, dst, false, true, center ); + } else { + mvwputch( g->w_terrain, point( POSX, POSY ), c_black, 'X' ); + } } -std::vector target_ui::run_spell_ui_old( player &pc ) +void target_ui::draw_spell_ui_old( player &pc ) { spell &casting = *this->casting; // TODO: make code use pointer - int num_instruction_lines = draw_targeting_window( w_target, casting.name(), - TARGET_MODE_SPELL, ctxt, aim_types, tiny ); - - const tripoint old_offset = pc.view_offset; - - do { - tripoint center = pc.pos() + pc.view_offset; - // Clear the target window. - for( int i = 1; i <= getmaxy( w_target ) - num_instruction_lines - 2; i++ ) { - // Clear width excluding borders. - for( int j = 1; j <= getmaxx( w_target ) - 2; j++ ) { - mvwputch( w_target, point( j, i ), c_white, ' ' ); - } - } - g->draw_ter( center, true ); - int line_number = 1; - Creature *critter = g->critter_at( dst, true ); - const int relative_elevation = dst.z - pc.pos().z; - mvwprintz( w_target, point( 1, line_number++ ), c_light_green, _( "Casting: %s (Level %u)" ), - casting.name(), - casting.get_level() ); - if( !no_mana || casting.energy_source() == none_energy ) { - if( casting.energy_source() == hp_energy ) { - line_number += fold_and_print( w_target, point( 1, line_number ), getmaxx( w_target ) - 2, - c_light_gray, - _( "Cost: %s %s" ), casting.energy_cost_string( pc ), casting.energy_string() ); - } else { - line_number += fold_and_print( w_target, point( 1, line_number ), getmaxx( w_target ) - 2, - c_light_gray, - _( "Cost: %s %s (Current: %s)" ), casting.energy_cost_string( pc ), casting.energy_string(), - casting.energy_cur_string( pc ) ); - } - } - nc_color clr = c_light_gray; - if( !no_fail ) { - print_colored_text( w_target, point( 1, line_number++ ), clr, clr, - casting.colorized_fail_percent( pc ) ); + tripoint center = pc.pos() + pc.view_offset; + // Clear the target window. + for( int i = 1; i <= getmaxy( w_target ) - num_instruction_lines - 2; i++ ) { + // Clear width excluding borders. + for( int j = 1; j <= getmaxx( w_target ) - 2; j++ ) { + mvwputch( w_target, point( j, i ), c_white, ' ' ); + } + } + g->draw_ter( center, true ); + int line_number = 1; + Creature *critter = g->critter_at( dst, true ); + const int relative_elevation = dst.z - pc.pos().z; + mvwprintz( w_target, point( 1, line_number++ ), c_light_green, _( "Casting: %s (Level %u)" ), + casting.name(), + casting.get_level() ); + if( !no_mana || casting.energy_source() == none_energy ) { + if( casting.energy_source() == hp_energy ) { + line_number += fold_and_print( w_target, point( 1, line_number ), getmaxx( w_target ) - 2, + c_light_gray, + _( "Cost: %s %s" ), casting.energy_cost_string( pc ), casting.energy_string() ); } else { - print_colored_text( w_target, point( 1, line_number++ ), clr, clr, - colorize( _( "0.0 % Failure Chance" ), - c_light_green ) ); - } - if( dst != src ) { - // Only draw those tiles which are on current z-level - auto ret_this_zlevel = ret; - ret_this_zlevel.erase( std::remove_if( ret_this_zlevel.begin(), ret_this_zlevel.end(), - [¢er]( const tripoint & pt ) { - return pt.z != center.z; - } ), ret_this_zlevel.end() ); - // Only draw a highlighted trajectory if we can see the endpoint. - // Provides feedback to the player, and avoids leaking information - // about tiles they can't see. - g->draw_line( dst, center, ret_this_zlevel ); - - // Print to target window - mvwprintw( w_target, point( 1, line_number++ ), _( "Range: %d/%d Elevation: %d Targets: %d" ), - rl_dist( src, dst ), range, relative_elevation, targets.size() ); - - } else { - mvwprintw( w_target, point( 1, line_number++ ), _( "Range: %d Elevation: %d Targets: %d" ), range, - relative_elevation, targets.size() ); - } - - g->draw_cursor( dst ); - for( const tripoint &area : spell_aoe ) { - g->m.drawsq( g->w_terrain, pc, area, true, true, center ); - } - - if( casting.aoe() > 0 ) { - nc_color color = c_light_gray; - if( casting.effect() == "projectile_attack" || casting.effect() == "target_attack" || - casting.effect() == "area_pull" || casting.effect() == "area_push" || - casting.effect() == "ter_transform" ) { - line_number += fold_and_print( w_target, point( 1, line_number ), getmaxx( w_target ) - 2, color, - _( "Effective Spell Radius: %s%s" ), casting.aoe_string(), - casting.in_aoe( src, dst ) ? colorize( _( " WARNING! IN RANGE" ), c_red ) : "" ); - } else if( casting.effect() == "cone_attack" ) { - line_number += fold_and_print( w_target, point( 1, line_number ), getmaxx( w_target ) - 2, color, - _( "Cone Arc: %s degrees" ), casting.aoe_string() ); - } else if( casting.effect() == "line_attack" ) { - line_number += fold_and_print( w_target, point( 1, line_number ), getmaxx( w_target ) - 2, color, - _( "Line width: %s" ), casting.aoe_string() ); - } + line_number += fold_and_print( w_target, point( 1, line_number ), getmaxx( w_target ) - 2, + c_light_gray, + _( "Cost: %s %s (Current: %s)" ), casting.energy_cost_string( pc ), casting.energy_string(), + casting.energy_cur_string( pc ) ); } - mvwprintz( w_target, point( 1, line_number++ ), c_light_red, _( "Damage: %s" ), - casting.damage_string() ); - line_number += fold_and_print( w_target, point( 1, line_number ), getmaxx( w_target ) - 2, clr, - casting.description() ); - // Skip blank lines if we're short on space. - if( !compact ) { - line_number++; - } - - if( critter && critter != &pc && pc.sees( *critter ) ) { - // The 12 is 2 for the border and 10 for aim bars. - // Just print the monster name if we're short on space. - int available_lines = compact ? 1 : ( height - num_instruction_lines - line_number - 12 ); - critter->print_info( w_target, line_number, available_lines, 1 ); - } - - wrefresh( g->w_terrain ); - draw_targeting_window( w_target, casting.name(), - TARGET_MODE_SPELL, ctxt, aim_types, tiny ); - wrefresh( w_target ); - - catacurses::refresh(); - - std::string action; - if( pc.activity.id() == ACT_AIM && pc.activity.str_values[0] != "AIM" ) { - // If we're in 'aim and shoot' mode, - // skip retrieving input and go straight to the action. - action = pc.activity.str_values[0]; - } else { - action = ctxt.handle_input(); - } - // Clear the activity if any, we'll re-set it later if we need to. - pc.cancel_activity(); - - // Move cursor based on user input - tripoint new_dst = get_desired_cursor_pos( action ); + } + nc_color clr = c_light_gray; + if( !no_fail ) { + print_colored_text( w_target, point( 1, line_number++ ), clr, clr, + casting.colorized_fail_percent( pc ) ); + } else { + print_colored_text( w_target, point( 1, line_number++ ), clr, clr, + colorize( _( "0.0 % Failure Chance" ), + c_light_green ) ); + } + if( dst != src ) { + // Only draw those tiles which are on current z-level + auto ret_this_zlevel = ret; + ret_this_zlevel.erase( std::remove_if( ret_this_zlevel.begin(), ret_this_zlevel.end(), + [¢er]( const tripoint & pt ) { + return pt.z != center.z; + } ), ret_this_zlevel.end() ); + // Only draw a highlighted trajectory if we can see the endpoint. + // Provides feedback to the player, and avoids leaking information + // about tiles they can't see. + g->draw_line( dst, center, ret_this_zlevel ); + + // Print to target window + mvwprintw( w_target, point( 1, line_number++ ), _( "Range: %d/%d Elevation: %d Targets: %d" ), + rl_dist( src, dst ), range, relative_elevation, targets.size() ); - /* More drawing to terrain */ - if( set_cursor_pos( pc, new_dst ) ) { - const Creature *critter = g->critter_at( dst, true ); - if( critter != nullptr ) { - g->draw_critter( *critter, center ); - } else if( g->m.pl_sees( dst, -1 ) ) { - g->m.drawsq( g->w_terrain, pc, dst, false, true, center ); - } - } else if( action == "PREV_TARGET" ) { - cycle_targets( pc, -1 ); - } else if( action == "NEXT_TARGET" ) { - cycle_targets( pc, 1 ); - } else if( action == "FIRE" ) { - if( casting.damage() > 0 && !confirm_non_enemy_target() ) { - continue; - } - set_last_target( pc ); - break; - } else if( action == "CENTER" ) { - set_cursor_pos( pc, src ); - } else if( action == "TOGGLE_SNAP_TO_TARGET" ) { - toggle_snap_to_target( pc ); - } else if( action == "QUIT" ) { // return empty vector (cancel) - ret.clear(); - break; - } - } while( true ); + } else { + mvwprintw( w_target, point( 1, line_number++ ), _( "Range: %d Elevation: %d Targets: %d" ), range, + relative_elevation, targets.size() ); + } + + g->draw_cursor( dst ); + for( const tripoint &area : spell_aoe ) { + g->m.drawsq( g->w_terrain, pc, area, true, true, center ); + } + + if( casting.aoe() > 0 ) { + nc_color color = c_light_gray; + if( casting.effect() == "projectile_attack" || casting.effect() == "target_attack" || + casting.effect() == "area_pull" || casting.effect() == "area_push" || + casting.effect() == "ter_transform" ) { + line_number += fold_and_print( w_target, point( 1, line_number ), getmaxx( w_target ) - 2, color, + _( "Effective Spell Radius: %s%s" ), casting.aoe_string(), + casting.in_aoe( src, dst ) ? colorize( _( " WARNING! IN RANGE" ), c_red ) : "" ); + } else if( casting.effect() == "cone_attack" ) { + line_number += fold_and_print( w_target, point( 1, line_number ), getmaxx( w_target ) - 2, color, + _( "Cone Arc: %s degrees" ), casting.aoe_string() ); + } else if( casting.effect() == "line_attack" ) { + line_number += fold_and_print( w_target, point( 1, line_number ), getmaxx( w_target ) - 2, color, + _( "Line width: %s" ), casting.aoe_string() ); + } + } + mvwprintz( w_target, point( 1, line_number++ ), c_light_red, _( "Damage: %s" ), + casting.damage_string() ); + line_number += fold_and_print( w_target, point( 1, line_number ), getmaxx( w_target ) - 2, clr, + casting.description() ); + // Skip blank lines if we're short on space. + if( !compact ) { + line_number++; + } + + if( critter && critter != &pc && pc.sees( *critter ) ) { + // The 12 is 2 for the border and 10 for aim bars. + // Just print the monster name if we're short on space. + int available_lines = compact ? 1 : ( height - num_instruction_lines - line_number - 12 ); + critter->print_info( w_target, line_number, available_lines, 1 ); + } + + wrefresh( g->w_terrain ); + draw_targeting_window( w_target, casting.name(), + TARGET_MODE_SPELL, ctxt, aim_types, tiny ); + wrefresh( w_target ); - set_view_offset( pc, old_offset ); + catacurses::refresh(); - if( ret.empty() || ret.back() == pc.pos() ) { - return ret; + if( critter != nullptr ) { + g->draw_critter( *critter, center ); + } else if( g->m.pl_sees( dst, -1 ) ) { + g->m.drawsq( g->w_terrain, pc, dst, false, true, center ); } - - on_target_accepted( pc, casting.damage() > 0 ); - wrefresh( w_target ); - return ret; } static projectile make_gun_projectile( const item &gun ) @@ -2487,8 +2249,6 @@ target_handler::trajectory target_ui::run( player &pc ) ctxt.register_action( "FIRE" ); ctxt.register_action( "NEXT_TARGET" ); ctxt.register_action( "PREV_TARGET" ); - ctxt.register_action( "LEVEL_UP" ); - ctxt.register_action( "LEVEL_DOWN" ); ctxt.register_action( "CENTER" ); ctxt.register_action( "TOGGLE_SNAP_TO_TARGET" ); ctxt.register_action( "HELP_KEYBINDINGS" ); @@ -2496,6 +2256,10 @@ target_handler::trajectory target_ui::run( player &pc ) ctxt.register_action( "MOUSE_MOVE" ); ctxt.register_action( "zoom_out" ); ctxt.register_action( "zoom_in" ); + if( allow_zlevel_shift ) { + ctxt.register_action( "LEVEL_UP" ); + ctxt.register_action( "LEVEL_DOWN" ); + } if( mode == TARGET_MODE_FIRE || mode == TARGET_MODE_TURRET_MANUAL ) { ctxt.register_action( "SWITCH_MODE" ); ctxt.register_action( "SWITCH_AMMO" ); @@ -2528,30 +2292,208 @@ target_handler::trajectory target_ui::run( player &pc ) update_targets( pc, range, targets, _target_idx, src, initial_dst ); set_cursor_pos( pc, initial_dst ); + // Handle multi-turn aiming + std::string action; + if( mode == TARGET_MODE_FIRE ) { + if( pc.activity.id() == ACT_AIM ) { + // We were in this UI during previous turn... + 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; + } + // Clear the activity, we'll re-set it later if we need to. + pc.cancel_activity(); + } + } + + // Initialize drawing (TODO: update this) if( mode == TARGET_MODE_SPELL ) { - return run_spell_ui_old( pc ); + num_instruction_lines = draw_targeting_window( w_target, casting->name(), + mode, ctxt, aim_types, tiny ); } else { - return run_normal_ui_old( pc ); + num_instruction_lines = draw_targeting_window( w_target, + relevant ? relevant->tname() : _( "turrets" ), mode, ctxt, aim_types, tiny, src == dst ); + } + + enum class ExitCode { + Abort, + Fire, + Timeout, + Reload + }; + ExitCode exit_code; + std::string timed_out_activity; + + bool skip_redraw = false; + const tripoint saved_view_offset = pc.view_offset; + for( ;; action.clear() ) { + // Old drawing + if( !skip_redraw ) { + if( mode == TARGET_MODE_SPELL ) { + draw_spell_ui_old( pc ); + } else { + draw_normal_ui_old( pc ); + } + } + skip_redraw = false; + + // Wait for user input (or use value retrieved from activity) + if( action.empty() ) { + int timeout = get_option( "EDGE_SCROLL" ); + action = ctxt.handle_input( timeout ); + } + + // If an aiming mode is selected, use "*_SHOT" instead of "FIRE" + if( mode == TARGET_MODE_FIRE && action == "FIRE" && aim_mode->has_threshold ) { + action = aim_mode->action; + } + + // Handle received input + if( handle_cursor_movement( pc, action, skip_redraw ) ) { + continue; + } else if( action == "TOGGLE_SNAP_TO_TARGET" ) { + toggle_snap_to_target( pc ); + } else if( action == "zoom_in" ) { + g->zoom_in(); + } else if( action == "zoom_out" ) { + g->zoom_out(); + } else if( action == "QUIT" ) { + exit_code = ExitCode::Abort; + break; + } else if( action == "SWITCH_MODE" ) { + action_switch_mode( pc ); + } else if( action == "SWITCH_AMMO" ) { + if( !action_switch_ammo() ) { + exit_code = ExitCode::Reload; + break; + } + } else if( action == "SWITCH_AIM" ) { + aim_mode++; + if( aim_mode == aim_types.end() ) { + aim_mode = aim_types.begin(); + } + } else if( action == "FIRE" ) { + if( src == dst ) { + // TODO: consider allowing aiming spells/turrets at yourself + continue; + } + bool can_skip_confirm = ( mode == TARGET_MODE_SPELL && casting->damage() <= 0 ); + if( !can_skip_confirm && !confirm_non_enemy_target() ) { + continue; + } + set_last_target( pc ); + exit_code = ExitCode::Fire; + break; + } else if( action == "AIM" ) { + if( src == dst ) { + continue; + } + + // No confirm_non_enemy_target here because we have not initiated the firing. + // Aiming can be stopped / aborted at any time. + + if( !action_aim( pc ) ) { + timed_out_activity = "AIM"; + exit_code = ExitCode::Timeout; + break; + } + } else if( action == "AIMED_SHOT" || action == "CAREFUL_SHOT" || action == "PRECISE_SHOT" ) { + if( src == dst ) { + continue; + } + + // This action basically means "Fire" as well; the actual firing may be delayed + // through aiming, but there is usually no means to abort it. Therefore we query now + if( !confirm_non_enemy_target() ) { + continue; + } + + if( action_aim_and_shoot( pc, action ) ) { + exit_code = ExitCode::Fire; + } else { + timed_out_activity = action; + exit_code = ExitCode::Timeout; + } + break; + } + } // for(;;) + + set_view_offset( pc, saved_view_offset ); + + switch( exit_code ) { + case ExitCode::Abort: { + ret.clear(); + break; + } + case ExitCode::Fire: { + on_target_accepted( pc, true ); + 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. + ret.clear(); + pc.assign_activity( ACT_AIM, 0, 0 ); + pc.activity.str_values.push_back( timed_out_activity ); + break; + } + case ExitCode::Reload: { + // TODO: make this work + ret.clear(); + break; + } } + + return ret; } -tripoint target_ui::get_desired_cursor_pos( const std::string &action ) +bool target_ui::handle_cursor_movement( player &pc, const std::string &action, bool &skip_redraw ) { - // Our coordinates will either be determined by coordinate input(mouse), - // by a direction key, or by the previous value. - tripoint new_dst = dst; cata::optional mouse_pos; - if( action == "SELECT" && ( mouse_pos = ctxt.get_coordinates( g->w_terrain ) ) ) { - new_dst = *mouse_pos; - } else if( const cata::optional vec = ctxt.get_direction( action ) ) { - new_dst.x += vec->x; - new_dst.y += vec->y; - } - if( allow_zlevel_shift && ( action == "LEVEL_UP" || action == "LEVEL_DOWN" ) ) { - const int delta_z = action == "LEVEL_UP" ? 1 : -1; - new_dst.z += delta_z; + + if( action == "MOUSE_MOVE" || action == "TIMEOUT" ) { + // Shift pos and/or view via edge scrolling + tripoint edge_scroll = g->mouse_edge_scrolling_terrain( ctxt ); + if( edge_scroll == tripoint_zero ) { + skip_redraw = true; + } else { + if( action == "MOUSE_MOVE" ) { + edge_scroll *= 2; + } + if( snap_to_target ) { + set_cursor_pos( pc, dst + edge_scroll ); + } else { + set_view_offset( pc, pc.view_offset + edge_scroll ); + } + } + } else if( const cata::optional delta = ctxt.get_direction( action ) ) { + // Shift pos with directional keys + set_cursor_pos( pc, dst + *delta ); + } else if( action == "SELECT" && ( mouse_pos = ctxt.get_coordinates( g->w_terrain ) ) ) { + // Set pos by clicking with mouse + set_cursor_pos( pc, *mouse_pos ); + } else if( action == "LEVEL_UP" || action == "LEVEL_DOWN" ) { + // Shift position up/down one z level + tripoint new_pos = dst; + new_pos.z += ( action == "LEVEL_UP" ? 1 : -1 ); + set_cursor_pos( pc, new_pos ); + } else if( action == "NEXT_TARGET" ) { + cycle_targets( pc, 1 ); + } else if( action == "PREV_TARGET" ) { + cycle_targets( pc, -1 ); + } else if( action == "CENTER" ) { + set_cursor_pos( pc, src ); + } else { + return false; } - return new_dst; + + return true; } bool target_ui::set_cursor_pos( player &pc, const tripoint &new_pos ) @@ -2780,6 +2722,95 @@ void target_ui::apply_aim_turning_penalty( player &pc ) pc.recoil = predicted_recoil; } +void target_ui::action_switch_mode( player &pc ) +{ + relevant->gun_cycle_mode(); + if( relevant->gun_current_mode().flags.count( "REACH_ATTACK" ) ) { + relevant->gun_cycle_mode(); + } + if( mode == TARGET_MODE_TURRET_MANUAL ) { + itype_id ammo_current = turret->ammo_current(); + if( ammo_current == "null" ) { + ammo = nullptr; + range = 0; + } else { + ammo = item::find_type( ammo_current ); + range = turret->range(); + } + } else { + ammo = relevant->gun_current_mode().target->ammo_data(); + range = relevant->gun_current_mode().target->gun_range( &pc ); + } +} + +bool target_ui::action_switch_ammo() +{ + if( mode == TARGET_MODE_TURRET_MANUAL ) { + // For turrets that use vehicle tanks & can fire multiple liquids + if( turret->ammo_options().size() > 1 ) { + const auto opts = turret->ammo_options(); + auto iter = opts.find( turret->ammo_current() ); + turret->ammo_select( ++iter != opts.end() ? *iter : *opts.begin() ); + ammo = item::find_type( turret->ammo_current() ); + range = turret->range(); + } + } else { + // Leave aiming UI and open reloading UI since + // reloading annihilates our aim anyway + return false; + } + return true; +} + +bool target_ui::action_aim( player &pc ) +{ + set_last_target( pc ); + apply_aim_turning_penalty( pc ); + const double min_recoil = calculate_aim_cap( pc, dst ); + for( int i = 0; i < 10; ++i ) { + do_aim( pc, *relevant, min_recoil ); + } + + // We've changed pc.recoil, update penalty + recalc_aim_turning_penalty( pc ); + + return pc.moves > 0; +} + +bool target_ui::action_aim_and_shoot( player &pc, const std::string &action ) +{ + std::vector::iterator it; + for( it = aim_types.begin(); it != aim_types.end(); it++ ) { + if( action == it->action ) { + break; + } + } + if( it == aim_types.end() ) { + debugmsg( "Could not find a valid aim_type for %s", action.c_str() ); + aim_mode = aim_types.begin(); + } + int aim_threshold = it->threshold; + set_last_target( pc ); + apply_aim_turning_penalty( pc ); + const double min_recoil = calculate_aim_cap( pc, dst ); + do { + do_aim( pc, relevant ? *relevant : null_item_reference(), min_recoil ); + } while( pc.moves > 0 && pc.recoil > aim_threshold && pc.recoil - sight_dispersion > min_recoil ); + + if( pc.recoil <= aim_threshold || + pc.recoil - sight_dispersion == min_recoil || + // if no critter is at dst then sight dispersion does not apply, + // so it would lock into an infinite loop + ( !g->critter_at( dst ) && pc.recoil == min_recoil ) ) { + // If we made it under the aim threshold, go ahead and fire. + // Also fire if we're at our best aim level already. + return true; + } else { + // We've run out of moves + return false; + } +} + void target_ui::on_target_accepted( player &pc, bool harmful ) { // TODO: all of this should be moved into on-hit code From 1625c61a7407fafaf6f503ad9cba7c4126d68dd2 Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Sun, 19 Apr 2020 21:49:32 +0300 Subject: [PATCH 12/43] Deduplicate terrain drawing; fix cursor-related visual bug. In non-spell version of target_ui, cursor was drawn using a custom implementation of draw_cursor. On CURSES it looked great, yes, but didn't work at all for TILES build. --- src/ranged.cpp | 122 +++++++++++++++++++++++++------------------------ 1 file changed, 62 insertions(+), 60 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index eccdebf5811b3..41ed138f8da2f 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -246,6 +246,12 @@ class target_ui // Aim and shoot. Returns 'false' if ran out of moves bool action_aim_and_shoot( player &pc, const std::string &action ); + // Drawing routines + void draw( player &pc ); + + // Draw terrain with UI-specific overlays + void draw_terrain( player &pc ); + // TODO: finish breaking down drawing void draw_normal_ui_old( player &pc ); void draw_spell_ui_old( player &pc ); @@ -1580,7 +1586,6 @@ static void update_targets( player &pc, int range, std::vector &targ // TODO: You ARE the redundant drawing code now, mwa-ha-ha! void target_ui::draw_normal_ui_old( player &pc ) { - tripoint center = pc.pos() + pc.view_offset; // Clear the target window. for( int i = 1; i <= getmaxy( w_target ) - num_instruction_lines - 2; i++ ) { // Clear width excluding borders. @@ -1588,22 +1593,10 @@ void target_ui::draw_normal_ui_old( player &pc ) mvwputch( w_target, point( j, i ), c_white, ' ' ); } } - g->draw_ter( center, true ); int line_number = 1; Creature *critter = g->critter_at( dst, true ); const int relative_elevation = dst.z - pc.pos().z; if( dst != src ) { - // Only draw those tiles which are on current z-level - auto ret_this_zlevel = ret; - ret_this_zlevel.erase( std::remove_if( ret_this_zlevel.begin(), ret_this_zlevel.end(), - [¢er]( const tripoint & pt ) { - return pt.z != center.z; - } ), ret_this_zlevel.end() ); - // Only draw a highlighted trajectory if we can see the endpoint. - // Provides feedback to the player, and avoids leaking information - // about tiles they can't see. - g->draw_line( dst, center, ret_this_zlevel ); - // Print to target window mvwprintw( w_target, point( 1, line_number++ ), _( "Range: %d/%d Elevation: %d Targets: %d" ), rl_dist( src, dst ), range, relative_elevation, targets.size() ); @@ -1657,9 +1650,6 @@ void target_ui::draw_normal_ui_old( player &pc ) // Just print the monster name if we're short on space. int available_lines = compact ? 1 : ( height - num_instruction_lines - line_number - 12 ); line_number = critter->print_info( w_target, line_number, available_lines, 1 ); - } else { - mvwputch( g->w_terrain, -center.xy() + dst.xy() + point( POSX, POSY ), - c_red, '*' ); } // Assumes that relevant == null means firing turrets (maybe even multiple at once), @@ -1705,28 +1695,15 @@ void target_ui::draw_normal_ui_old( player &pc ) draw_throw_aim( pc, w_target, line_number, ctxt, *relevant, dst, true ); } - wrefresh( g->w_terrain ); - g->draw_panels(); draw_targeting_window( w_target, relevant ? relevant->tname() : _( "turrets" ), mode, ctxt, aim_types, tiny, src == dst ); wrefresh( w_target ); - - catacurses::refresh(); - - if( critter != nullptr ) { - g->draw_critter( *critter, center ); - } else if( g->m.pl_sees( dst, -1 ) ) { - g->m.drawsq( g->w_terrain, pc, dst, false, true, center ); - } else { - mvwputch( g->w_terrain, point( POSX, POSY ), c_black, 'X' ); - } } void target_ui::draw_spell_ui_old( player &pc ) { spell &casting = *this->casting; // TODO: make code use pointer - tripoint center = pc.pos() + pc.view_offset; // Clear the target window. for( int i = 1; i <= getmaxy( w_target ) - num_instruction_lines - 2; i++ ) { // Clear width excluding borders. @@ -1734,7 +1711,6 @@ void target_ui::draw_spell_ui_old( player &pc ) mvwputch( w_target, point( j, i ), c_white, ' ' ); } } - g->draw_ter( center, true ); int line_number = 1; Creature *critter = g->critter_at( dst, true ); const int relative_elevation = dst.z - pc.pos().z; @@ -1763,17 +1739,6 @@ void target_ui::draw_spell_ui_old( player &pc ) c_light_green ) ); } if( dst != src ) { - // Only draw those tiles which are on current z-level - auto ret_this_zlevel = ret; - ret_this_zlevel.erase( std::remove_if( ret_this_zlevel.begin(), ret_this_zlevel.end(), - [¢er]( const tripoint & pt ) { - return pt.z != center.z; - } ), ret_this_zlevel.end() ); - // Only draw a highlighted trajectory if we can see the endpoint. - // Provides feedback to the player, and avoids leaking information - // about tiles they can't see. - g->draw_line( dst, center, ret_this_zlevel ); - // Print to target window mvwprintw( w_target, point( 1, line_number++ ), _( "Range: %d/%d Elevation: %d Targets: %d" ), rl_dist( src, dst ), range, relative_elevation, targets.size() ); @@ -1783,11 +1748,6 @@ void target_ui::draw_spell_ui_old( player &pc ) relative_elevation, targets.size() ); } - g->draw_cursor( dst ); - for( const tripoint &area : spell_aoe ) { - g->m.drawsq( g->w_terrain, pc, area, true, true, center ); - } - if( casting.aoe() > 0 ) { nc_color color = c_light_gray; if( casting.effect() == "projectile_attack" || casting.effect() == "target_attack" || @@ -1820,18 +1780,9 @@ void target_ui::draw_spell_ui_old( player &pc ) critter->print_info( w_target, line_number, available_lines, 1 ); } - wrefresh( g->w_terrain ); draw_targeting_window( w_target, casting.name(), TARGET_MODE_SPELL, ctxt, aim_types, tiny ); wrefresh( w_target ); - - catacurses::refresh(); - - if( critter != nullptr ) { - g->draw_critter( *critter, center ); - } else if( g->m.pl_sees( dst, -1 ) ) { - g->m.drawsq( g->w_terrain, pc, dst, false, true, center ); - } } static projectile make_gun_projectile( const item &gun ) @@ -2333,11 +2284,7 @@ target_handler::trajectory target_ui::run( player &pc ) for( ;; action.clear() ) { // Old drawing if( !skip_redraw ) { - if( mode == TARGET_MODE_SPELL ) { - draw_spell_ui_old( pc ); - } else { - draw_normal_ui_old( pc ); - } + draw( pc ); } skip_redraw = false; @@ -2811,6 +2758,61 @@ bool target_ui::action_aim_and_shoot( player &pc, const std::string &action ) } } +void target_ui::draw( player &pc ) +{ + draw_terrain( pc ); + + if( mode == TARGET_MODE_SPELL ) { + draw_spell_ui_old( pc ); + } else { + draw_normal_ui_old( pc ); + } + + g->draw_panels(); + catacurses::refresh(); +} + +void target_ui::draw_terrain( player &pc ) +{ + tripoint center = pc.pos() + pc.view_offset; + g->draw_ter( center, true ); + + // Draw trajectory + if( dst != src ) { + // But only points on this Z-level + std::vector this_z = ret; + this_z.erase( std::remove_if( this_z.begin(), this_z.end(), + [¢er]( const tripoint & p ) { + return p.z != center.z; + } ), this_z.end() ); + + // Draw a highlighted trajectory only if we can see the endpoint. + // Provides feedback to the player, but avoids leaking information + // about tiles they can't see. + // FIXME: TILES version of this function helpfully draws a cursor at 'this_z.back()'. + // This creates a fake cursor if 'dst' is on a z-level we cannot see. + g->draw_line( dst, center, this_z ); + } + + // Since draw_line does nothing if destination is not visible, + // cursor also disappears. Draw it explicitly. + if( dst.z == center.z ) { + g->draw_cursor( dst ); + } + + // Draw spell AOE + if( mode == TARGET_MODE_SPELL ) { + for( const tripoint &tile : spell_aoe ) { + if( tile.z != center.z ) { + continue; + } + g->m.drawsq( g->w_terrain, pc, tile, true, true, center ); + } + } + + wrefresh( g->w_terrain ); +} + void target_ui::on_target_accepted( player &pc, bool harmful ) { // TODO: all of this should be moved into on-hit code From fc74251349692fc678310db57c6fa170ed3d11ac Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Sun, 19 Apr 2020 23:55:26 +0300 Subject: [PATCH 13/43] Refactor drawing of window base and list of controls --- src/ranged.cpp | 282 +++++++++++++++++++++++-------------------------- 1 file changed, 131 insertions(+), 151 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index 41ed138f8da2f..cac5998f135d4 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -173,6 +173,9 @@ class target_ui input_context ctxt; // Number of free instruction lines int num_instruction_lines; + // If true, aiming and firing controls will be drawn using grey color + // to indicate invalid dst (e.g. shooting yourself) + bool grey_out_firing_controls = true; /* These members are relevant for TARGET_MODE_FIRE */ // Weapon sight dispersion @@ -252,6 +255,19 @@ class target_ui // Draw terrain with UI-specific overlays void draw_terrain( player &pc ); + // Draw aiming window + void draw_ui_window( player &pc ); + + // Generate ui window title + std::string uitext_title(); + + // Generate flavor text for 'Fire!' key + std::string uitext_fire(); + + // Draw list of available controls at the bottom of the window. + // Returns how much lines it took. + int draw_controls_list(); + // TODO: finish breaking down drawing void draw_normal_ui_old( player &pc ); void draw_spell_ui_old( player &pc ); @@ -1075,119 +1091,6 @@ static std::string print_recoil( const player &p ) return std::string(); } -// Draws the static portions of the targeting menu, -// returns the number of lines used to draw instructions. -static int draw_targeting_window( const catacurses::window &w_target, const std::string &name, - target_mode mode, input_context &ctxt, - const std::vector &aim_types, bool tiny, - bool grey_out_firing_controls = false ) -{ - draw_border( w_target ); - // Draw the "title" of the window. - mvwprintz( w_target, point( 2, 0 ), c_white, "< " ); - std::string title; - - switch( mode ) { - case TARGET_MODE_FIRE: - case TARGET_MODE_TURRET_MANUAL: - title = string_format( _( "Firing %s" ), name ); - break; - - case TARGET_MODE_THROW: - title = string_format( _( "Throwing %s" ), name ); - break; - - case TARGET_MODE_THROW_BLIND: - title = string_format( _( "Blind throwing %s" ), name ); - break; - - default: - title = _( "Set target" ); - } - - trim_and_print( w_target, point( 4, 0 ), getmaxx( w_target ) - 7, c_red, title ); - wprintz( w_target, c_white, " >" ); - - // Draw the help contents at the bottom of the window, leaving room for monster description - // and aiming status to be drawn dynamically. - // The - 2 accounts for the window border. - // If tiny is set we're critically low on space, let the final line overwrite the border. - int text_y = getmaxy( w_target ) - ( tiny ? 1 : 2 ); - if( is_mouse_enabled() ) { - // Reserve a line for mouse instructions. - --text_y; - } - - // Reserve lines for aiming and firing instructions. - if( mode == TARGET_MODE_FIRE ) { - text_y -= ( 3 + aim_types.size() ); - } else { - text_y -= 2; - } - - // The -1 is the -2 from above, but adjusted since this is a total, not an index. - int lines_used = getmaxy( w_target ) - 1 - text_y; - mvwprintz( w_target, point( 1, text_y++ ), c_white, - _( "Move cursor to target with directional keys" ) ); - - const auto front_or = [&]( const std::string & s, const char fallback ) { - const auto keys = ctxt.keys_bound_to( s ); - return keys.empty() ? fallback : keys.front(); - }; - - nc_color fire_color = grey_out_firing_controls ? c_light_gray : c_white; - - std::string label_fire; - if( mode == TARGET_MODE_THROW || mode == TARGET_MODE_THROW_BLIND ) { - label_fire = to_translation( "[Hotkey] to throw", "to throw" ).translated(); - } else if( mode == TARGET_MODE_REACH ) { - label_fire = to_translation( "[Hotkey] to attack", "to attack" ).translated(); - } else if( mode == TARGET_MODE_SPELL ) { - label_fire = to_translation( "[Hotkey] to cast the spell", "to cast" ).translated(); - } else { - label_fire = to_translation( "[Hotkey] to fire", "to fire" ).translated(); - } - - auto label_cycle = string_format( _( "[%s] Cycle targets;" ), ctxt.get_desc( "NEXT_TARGET", 1 ) ); - int text_x = utf8_width( label_cycle ) + 2; // '2' for border + space at the end - mvwprintz( w_target, point( 1, text_y ), c_white, label_cycle ); - mvwprintz( w_target, point( text_x, text_y++ ), fire_color, "[%c] %s.", front_or( "FIRE", ' ' ), - label_fire ); - - mvwprintz( w_target, point( 1, text_y++ ), c_white, - _( "[%c] target self; [%c] toggle snap-to-target" ), - front_or( "CENTER", ' ' ), front_or( "TOGGLE_SNAP_TO_TARGET", ' ' ) ); - - if( mode == TARGET_MODE_FIRE ) { - mvwprintz( w_target, point( 1, text_y++ ), fire_color, _( "[%c] to steady your aim. (10 moves)" ), - front_or( "AIM", ' ' ) ); - std::string aim_and_fire; - for( const auto &e : aim_types ) { - if( e.has_threshold ) { - aim_and_fire += string_format( "[%s] ", front_or( e.action, ' ' ) ); - } - } - mvwprintz( w_target, point( 1, text_y++ ), fire_color, _( "%sto aim and fire" ), aim_and_fire ); - mvwprintz( w_target, point( 1, text_y++ ), c_white, _( "[%c] to switch aiming modes." ), - front_or( "SWITCH_AIM", ' ' ) ); - } - - if( mode == TARGET_MODE_FIRE || mode == TARGET_MODE_TURRET_MANUAL ) { - mvwprintz( w_target, point( 1, text_y++ ), c_white, _( "[%c] to switch firing modes." ), - front_or( "SWITCH_MODE", ' ' ) ); - mvwprintz( w_target, point( 1, text_y++ ), c_white, _( "[%c] to reload/switch ammo." ), - front_or( "SWITCH_AMMO", ' ' ) ); - } - - if( is_mouse_enabled() ) { - const char *label_mouse = "Mouse: LMB: Target, Wheel: Cycle,"; - int text_x = utf8_width( label_mouse ) + 2; // '2' for border + space at the end - mvwprintz( w_target, point( 1, text_y ), c_white, label_mouse ); - mvwprintz( w_target, point( text_x, text_y ), fire_color, _( "RMB: Fire" ) ); - } - return lines_used; -} - static void do_aim( player &p, const item &relevant, const double min_recoil ) { const double aim_amount = p.aim_per_move( relevant, p.recoil ); @@ -1586,13 +1489,6 @@ static void update_targets( player &pc, int range, std::vector &targ // TODO: You ARE the redundant drawing code now, mwa-ha-ha! void target_ui::draw_normal_ui_old( player &pc ) { - // Clear the target window. - for( int i = 1; i <= getmaxy( w_target ) - num_instruction_lines - 2; i++ ) { - // Clear width excluding borders. - for( int j = 1; j <= getmaxx( w_target ) - 2; j++ ) { - mvwputch( w_target, point( j, i ), c_white, ' ' ); - } - } int line_number = 1; Creature *critter = g->critter_at( dst, true ); const int relative_elevation = dst.z - pc.pos().z; @@ -1694,23 +1590,12 @@ void target_ui::draw_normal_ui_old( player &pc ) } else if( mode == TARGET_MODE_THROW_BLIND && relevant ) { draw_throw_aim( pc, w_target, line_number, ctxt, *relevant, dst, true ); } - - draw_targeting_window( w_target, relevant ? relevant->tname() : _( "turrets" ), - mode, ctxt, aim_types, tiny, src == dst ); - wrefresh( w_target ); } void target_ui::draw_spell_ui_old( player &pc ) { spell &casting = *this->casting; // TODO: make code use pointer - // Clear the target window. - for( int i = 1; i <= getmaxy( w_target ) - num_instruction_lines - 2; i++ ) { - // Clear width excluding borders. - for( int j = 1; j <= getmaxx( w_target ) - 2; j++ ) { - mvwputch( w_target, point( j, i ), c_white, ' ' ); - } - } int line_number = 1; Creature *critter = g->critter_at( dst, true ); const int relative_elevation = dst.z - pc.pos().z; @@ -1779,10 +1664,6 @@ void target_ui::draw_spell_ui_old( player &pc ) int available_lines = compact ? 1 : ( height - num_instruction_lines - line_number - 12 ); critter->print_info( w_target, line_number, available_lines, 1 ); } - - draw_targeting_window( w_target, casting.name(), - TARGET_MODE_SPELL, ctxt, aim_types, tiny ); - wrefresh( w_target ); } static projectile make_gun_projectile( const item &gun ) @@ -2261,15 +2142,6 @@ target_handler::trajectory target_ui::run( player &pc ) } } - // Initialize drawing (TODO: update this) - if( mode == TARGET_MODE_SPELL ) { - num_instruction_lines = draw_targeting_window( w_target, casting->name(), - mode, ctxt, aim_types, tiny ); - } else { - num_instruction_lines = draw_targeting_window( w_target, - relevant ? relevant->tname() : _( "turrets" ), mode, ctxt, aim_types, tiny, src == dst ); - } - enum class ExitCode { Abort, Fire, @@ -2553,6 +2425,9 @@ bool target_ui::set_cursor_pos( player &pc, const tripoint &new_pos ) recalc_aim_turning_penalty( pc ); } + // Update UI colors + grey_out_firing_controls = ( dst == src ); + return true; } @@ -2761,14 +2636,8 @@ bool target_ui::action_aim_and_shoot( player &pc, const std::string &action ) void target_ui::draw( player &pc ) { draw_terrain( pc ); - - if( mode == TARGET_MODE_SPELL ) { - draw_spell_ui_old( pc ); - } else { - draw_normal_ui_old( pc ); - } - g->draw_panels(); + draw_ui_window( pc ); catacurses::refresh(); } @@ -2813,6 +2682,117 @@ void target_ui::draw_terrain( player &pc ) wrefresh( g->w_terrain ); } +void target_ui::draw_ui_window( player &pc ) +{ + // Clear target window and make it non-transparent. + for( int y = 0; y < height; y++ ) { + for( int x = 0; x < width; x++ ) { + mvwputch( w_target, point( x, y ), c_white, ' ' ); + } + } + + draw_border( w_target ); + + // Draw title + mvwprintz( w_target, point( 2, 0 ), c_white, "< " ); + trim_and_print( w_target, point( 4, 0 ), getmaxx( w_target ) - 7, c_red, uitext_title() ); + wprintz( w_target, c_white, " >" ); + + // Draw controls + int occupied_lines = draw_controls_list(); + + // Draw old stuff (TODO: refactor this) + num_instruction_lines = height - 1 - occupied_lines; // TODO: is there an off-by-one error? + if( mode == TARGET_MODE_SPELL ) { + draw_spell_ui_old( pc ); + } else { + draw_normal_ui_old( pc ); + } + + wrefresh( w_target ); +} + +std::string target_ui::uitext_title() +{ + switch( mode ) { + case TARGET_MODE_FIRE: + case TARGET_MODE_TURRET_MANUAL: + return string_format( _( "Firing %s" ), relevant->tname() ); + case TARGET_MODE_THROW: + return string_format( _( "Throwing %s" ), relevant->tname() ); + case TARGET_MODE_THROW_BLIND: + return string_format( _( "Blind throwing %s" ), relevant->tname() ); + default: + return _( "Set target" ); + } +} + +std::string target_ui::uitext_fire() +{ + if( mode == TARGET_MODE_THROW || mode == TARGET_MODE_THROW_BLIND ) { + return to_translation( "[Hotkey] to throw", "to throw" ).translated(); + } else if( mode == TARGET_MODE_REACH ) { + return to_translation( "[Hotkey] to attack", "to attack" ).translated(); + } else if( mode == TARGET_MODE_SPELL ) { + return to_translation( "[Hotkey] to cast the spell", "to cast" ).translated(); + } else { + return to_translation( "[Hotkey] to fire", "to fire" ).translated(); + } +} + +int target_ui::draw_controls_list() +{ + // Get first key bound to given action OR ' ' if there are none. + const auto bound_key = [this]( const std::string & s ) { + const auto keys = ctxt.keys_bound_to( s ); + return keys.empty() ? ' ' : keys.front(); + }; + + nc_color fire_color = grey_out_firing_controls ? c_light_gray : c_white; + + // Since this list is of variable length and positioned + // at the bottom, we draw everything in reverse order + int text_y = height - ( tiny ? 1 : 2 ); // If we're short on space, draw over bottom border + if( is_mouse_enabled() ) { + const char *label_mouse = "Mouse: LMB: Target, Wheel: Cycle,"; + int text_x = utf8_width( label_mouse ) + 2; // '2' for border + space at the end + mvwprintz( w_target, point( 1, text_y ), c_white, label_mouse ); + mvwprintz( w_target, point( text_x, text_y-- ), fire_color, _( "RMB: Fire" ) ); + } + if( mode == TARGET_MODE_FIRE || mode == TARGET_MODE_TURRET_MANUAL ) { + mvwprintz( w_target, point( 1, text_y-- ), c_white, _( "[%c] to reload/switch ammo." ), + bound_key( "SWITCH_AMMO" ) ); + mvwprintz( w_target, point( 1, text_y-- ), c_white, _( "[%c] to switch firing modes." ), + bound_key( "SWITCH_MODE" ) ); + } + if( mode == TARGET_MODE_FIRE ) { + std::string aim_and_fire; + for( const auto &e : aim_types ) { + if( e.has_threshold ) { + aim_and_fire += string_format( "[%c] ", bound_key( e.action ) ); + } + } + + mvwprintz( w_target, point( 1, text_y-- ), c_white, _( "[%c] to switch aiming modes." ), + bound_key( "SWITCH_AIM" ) ); + mvwprintz( w_target, point( 1, text_y-- ), fire_color, _( "%sto aim and fire" ), aim_and_fire ); + mvwprintz( w_target, point( 1, text_y-- ), fire_color, _( "[%c] to steady your aim. (10 moves)" ), + bound_key( "AIM" ) ); + } + mvwprintz( w_target, point( 1, text_y-- ), c_white, + _( "[%c] target self; [%c] toggle snap-to-target" ), bound_key( "CENTER" ), + bound_key( "TOGGLE_SNAP_TO_TARGET" ) ); + + auto label_cycle = string_format( _( "[%s] Cycle targets;" ), ctxt.get_desc( "NEXT_TARGET", 1 ) ); + int text_x = utf8_width( label_cycle ) + 2; // '2' for border + space at the end + mvwprintz( w_target, point( 1, text_y ), c_white, label_cycle ); + mvwprintz( w_target, point( text_x, text_y-- ), fire_color, "[%c] %s.", bound_key( "FIRE" ), + uitext_fire() ); + + mvwprintz( w_target, point( 1, text_y-- ), c_white, _( "Move cursor with directional keys" ) ); + return height - text_y; +} + void target_ui::on_target_accepted( player &pc, bool harmful ) { // TODO: all of this should be moved into on-hit code From 304a5427375243dae9b7c22598a7dbe174d0504f Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Mon, 20 Apr 2020 01:46:44 +0300 Subject: [PATCH 14/43] Break up and reorganize rendering of panels inside aiming window This should fix parts of the UI jumping up and down when moving aim point or overwriting aim bars (when aiming using wielded guns) for 'compact' layout --- src/ranged.cpp | 443 ++++++++++++++++++++++++------------------------- 1 file changed, 220 insertions(+), 223 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index cac5998f135d4..4b264f65c5dc1 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -212,6 +212,9 @@ class target_ui // Returns 'false' if cursor position did not change bool set_cursor_pos( player &pc, const tripoint &new_pos ); + // Calculates distance from 'src'. For consistency, prefer using this over rl_dist. + int dist_fn( const tripoint &p ); + // Set creature (or tile) under cursor as player's last target void set_last_target( player &pc ); @@ -264,13 +267,18 @@ class target_ui // Generate flavor text for 'Fire!' key std::string uitext_fire(); + void draw_window_title(); + // Draw list of available controls at the bottom of the window. // Returns how much lines it took. int draw_controls_list(); - // TODO: finish breaking down drawing - void draw_normal_ui_old( player &pc ); - void draw_spell_ui_old( player &pc ); + void panel_cursor_info( int &text_y ); + void panel_gun_info( int &text_y ); + void panel_recoil( player &pc, int &text_y ); + void panel_spell_info( player &pc, int &text_y ); + void panel_target_info( player &pc, int &text_y ); + void panel_fire_mode_aim( player &pc, int &text_y ); // On-selected-as-target checks that act as if they are on-hit checks. // `harmful` is `false` if using a non-damaging spell @@ -1070,27 +1078,6 @@ dealt_projectile_attack player::throw_item( const tripoint &target, const item & return dealt_attack; } -static std::string print_recoil( const player &p ) -{ - if( p.weapon.is_gun() ) { - const int val = p.recoil_total(); - const int min_recoil = p.effective_dispersion( p.weapon.sight_dispersion() ); - const int recoil_range = MAX_RECOIL - min_recoil; - std::string level; - if( val >= min_recoil + ( recoil_range * 2 / 3 ) ) { - level = pgettext( "amount of backward momentum", "High" ); - } else if( val >= min_recoil + ( recoil_range / 2 ) ) { - level = pgettext( "amount of backward momentum", "Medium" ); - } else if( val >= min_recoil + ( recoil_range / 4 ) ) { - level = pgettext( "amount of backward momentum", "Low" ); - } else { - level = pgettext( "amount of backward momentum", "None" ); - } - return string_format( _( "Recoil: %s" ), level ); - } - return std::string(); -} - static void do_aim( player &p, const item &relevant, const double min_recoil ) { const double aim_amount = p.aim_per_move( relevant, p.recoil ); @@ -1486,186 +1473,6 @@ static void update_targets( player &pc, int range, std::vector &targ } } -// TODO: You ARE the redundant drawing code now, mwa-ha-ha! -void target_ui::draw_normal_ui_old( player &pc ) -{ - int line_number = 1; - Creature *critter = g->critter_at( dst, true ); - const int relative_elevation = dst.z - pc.pos().z; - if( dst != src ) { - // Print to target window - mvwprintw( w_target, point( 1, line_number++ ), _( "Range: %d/%d Elevation: %d Targets: %d" ), - rl_dist( src, dst ), range, relative_elevation, targets.size() ); - - } else { - mvwprintw( w_target, point( 1, line_number++ ), _( "Range: %d Elevation: %d Targets: %d" ), range, - relative_elevation, targets.size() ); - } - - // Skip blank lines if we're short on space. - if( !compact ) { - line_number++; - } - // Assumes that relevant == null means firing turrets (maybe even multiple at once), - // so printing their firing mode / ammo / ... of one of them is misleading. - if( relevant && ( mode == TARGET_MODE_FIRE || mode == TARGET_MODE_TURRET_MANUAL ) ) { - auto m = relevant->gun_current_mode(); - std::string str; - nc_color col = c_light_gray; - if( relevant != m.target ) { - str = string_format( _( "Firing mode: %s %s (%d)" ), - m->tname(), m.tname(), m.qty ); - - print_colored_text( w_target, point( 1, line_number++ ), col, col, str ); - } else { - str = string_format( _( "Firing mode: %s (%d)" ), - m.tname(), m.qty ); - print_colored_text( w_target, point( 1, line_number++ ), col, col, str ); - } - - const itype *cur = ammo ? ammo : m->ammo_data(); - if( cur ) { - auto str = string_format( m->ammo_remaining() ? _( "Ammo: %s (%d/%d)" ) : _( "Ammo: %s" ), - colorize( cur->nname( std::max( m->ammo_remaining(), 1 ) ), cur->color ), - m->ammo_remaining(), m->ammo_capacity() ); - - print_colored_text( w_target, point( 1, line_number++ ), col, col, str ); - } - - print_colored_text( w_target, point( 1, line_number++ ), col, col, string_format( _( "%s" ), - print_recoil( g->u ) ) ); - - // Skip blank lines if we're short on space. - if( !compact ) { - line_number++; - } - } - - if( critter && critter != &pc && pc.sees( *critter ) ) { - // The 12 is 2 for the border and 10 for aim bars. - // Just print the monster name if we're short on space. - int available_lines = compact ? 1 : ( height - num_instruction_lines - line_number - 12 ); - line_number = critter->print_info( w_target, line_number, available_lines, 1 ); - } - - // Assumes that relevant == null means firing turrets (maybe even multiple at once), - // so printing their firing mode / ammo / ... of one of them is misleading. - if( relevant && mode == TARGET_MODE_FIRE && src != dst ) { - // These 2 lines here keep the good ol' code working during the trying times of refactoring - double saved_pc_recoil = pc.recoil; - pc.recoil = predicted_recoil; - - double predicted_recoil = pc.recoil; - int predicted_delay = 0; - if( aim_mode->has_threshold && aim_mode->threshold < pc.recoil ) { - do { - const double aim_amount = pc.aim_per_move( *relevant, predicted_recoil ); - if( aim_amount > 0 ) { - predicted_delay++; - predicted_recoil = std::max( predicted_recoil - aim_amount, 0.0 ); - } - } while( predicted_recoil > aim_mode->threshold && - predicted_recoil - sight_dispersion > 0 ); - } else { - predicted_recoil = pc.recoil; - } - - const double target_size = critter != nullptr ? critter->ranged_target_size() : - occupied_tile_fraction( MS_MEDIUM ); - - line_number = print_aim( pc, w_target, line_number, ctxt, &*relevant->gun_current_mode(), - target_size, dst, predicted_recoil ); - - if( aim_mode->has_threshold ) { - mvwprintw( w_target, point( 1, line_number++ ), _( "%s Delay: %i" ), aim_mode->name, - predicted_delay ); - } - - // End of old code compatibility - pc.recoil = saved_pc_recoil; - } else if( mode == TARGET_MODE_TURRET ) { - list_turrets_in_range( veh, *vturrets, w_target, line_number, dst ); - } else if( mode == TARGET_MODE_THROW && relevant ) { - draw_throw_aim( pc, w_target, line_number, ctxt, *relevant, dst, false ); - } else if( mode == TARGET_MODE_THROW_BLIND && relevant ) { - draw_throw_aim( pc, w_target, line_number, ctxt, *relevant, dst, true ); - } -} - -void target_ui::draw_spell_ui_old( player &pc ) -{ - spell &casting = *this->casting; // TODO: make code use pointer - - int line_number = 1; - Creature *critter = g->critter_at( dst, true ); - const int relative_elevation = dst.z - pc.pos().z; - mvwprintz( w_target, point( 1, line_number++ ), c_light_green, _( "Casting: %s (Level %u)" ), - casting.name(), - casting.get_level() ); - if( !no_mana || casting.energy_source() == none_energy ) { - if( casting.energy_source() == hp_energy ) { - line_number += fold_and_print( w_target, point( 1, line_number ), getmaxx( w_target ) - 2, - c_light_gray, - _( "Cost: %s %s" ), casting.energy_cost_string( pc ), casting.energy_string() ); - } else { - line_number += fold_and_print( w_target, point( 1, line_number ), getmaxx( w_target ) - 2, - c_light_gray, - _( "Cost: %s %s (Current: %s)" ), casting.energy_cost_string( pc ), casting.energy_string(), - casting.energy_cur_string( pc ) ); - } - } - nc_color clr = c_light_gray; - if( !no_fail ) { - print_colored_text( w_target, point( 1, line_number++ ), clr, clr, - casting.colorized_fail_percent( pc ) ); - } else { - print_colored_text( w_target, point( 1, line_number++ ), clr, clr, - colorize( _( "0.0 % Failure Chance" ), - c_light_green ) ); - } - if( dst != src ) { - // Print to target window - mvwprintw( w_target, point( 1, line_number++ ), _( "Range: %d/%d Elevation: %d Targets: %d" ), - rl_dist( src, dst ), range, relative_elevation, targets.size() ); - - } else { - mvwprintw( w_target, point( 1, line_number++ ), _( "Range: %d Elevation: %d Targets: %d" ), range, - relative_elevation, targets.size() ); - } - - if( casting.aoe() > 0 ) { - nc_color color = c_light_gray; - if( casting.effect() == "projectile_attack" || casting.effect() == "target_attack" || - casting.effect() == "area_pull" || casting.effect() == "area_push" || - casting.effect() == "ter_transform" ) { - line_number += fold_and_print( w_target, point( 1, line_number ), getmaxx( w_target ) - 2, color, - _( "Effective Spell Radius: %s%s" ), casting.aoe_string(), - casting.in_aoe( src, dst ) ? colorize( _( " WARNING! IN RANGE" ), c_red ) : "" ); - } else if( casting.effect() == "cone_attack" ) { - line_number += fold_and_print( w_target, point( 1, line_number ), getmaxx( w_target ) - 2, color, - _( "Cone Arc: %s degrees" ), casting.aoe_string() ); - } else if( casting.effect() == "line_attack" ) { - line_number += fold_and_print( w_target, point( 1, line_number ), getmaxx( w_target ) - 2, color, - _( "Line width: %s" ), casting.aoe_string() ); - } - } - mvwprintz( w_target, point( 1, line_number++ ), c_light_red, _( "Damage: %s" ), - casting.damage_string() ); - line_number += fold_and_print( w_target, point( 1, line_number ), getmaxx( w_target ) - 2, clr, - casting.description() ); - // Skip blank lines if we're short on space. - if( !compact ) { - line_number++; - } - - if( critter && critter != &pc && pc.sees( *critter ) ) { - // The 12 is 2 for the border and 10 for aim bars. - // Just print the monster name if we're short on space. - int available_lines = compact ? 1 : ( height - num_instruction_lines - line_number - 12 ); - critter->print_info( w_target, line_number, available_lines, 1 ); - } -} - static projectile make_gun_projectile( const item &gun ) { projectile proj; @@ -2055,7 +1862,7 @@ target_handler::trajectory target_ui::run( player &pc ) // Create window compact = TERMY < 41; - tiny = TERMY < 31; + tiny = TERMY < 32; int top = 0; width = 55; if( tiny ) { @@ -2063,10 +1870,10 @@ target_handler::trajectory target_ui::run( player &pc ) height = TERMY; } else if( compact ) { // Cover up more low-value ui elements if we're tight on space. - height = 25; + height = 28; } else { // Go all out - height = 31; + height = 32; } w_target = catacurses::newwin( height, width, point( TERMX - width, top ) ); @@ -2335,10 +2142,6 @@ bool target_ui::set_cursor_pos( player &pc, const tripoint &new_pos ) valid_pos = new_traj[0]; } } else if( trigdist ) { - const auto dist_fn = [this]( const tripoint & p ) { - return static_cast( std::round( trig_dist( this->src, p ) ) ); - }; - if( dist_fn( valid_pos ) > range ) { // Find the farthest point that is still in range for( size_t i = new_traj.size(); i > 0; i-- ) { @@ -2431,6 +2234,11 @@ bool target_ui::set_cursor_pos( player &pc, const tripoint &new_pos ) return true; } +int target_ui::dist_fn( const tripoint &p ) +{ + return static_cast( std::round( trig_dist( src, p ) ) ); +} + void target_ui::set_last_target( player &pc ) { pc.last_target_pos = g->m.getabs( dst ); @@ -2692,21 +2500,36 @@ void target_ui::draw_ui_window( player &pc ) } draw_border( w_target ); + draw_window_title(); + draw_controls_list(); - // Draw title - mvwprintz( w_target, point( 2, 0 ), c_white, "< " ); - trim_and_print( w_target, point( 4, 0 ), getmaxx( w_target ) - 7, c_red, uitext_title() ); - wprintz( w_target, c_white, " >" ); + int text_y = 1; // Skip top border - // Draw controls - int occupied_lines = draw_controls_list(); + panel_cursor_info( text_y ); + text_y += compact ? 0 : 1; - // Draw old stuff (TODO: refactor this) - num_instruction_lines = height - 1 - occupied_lines; // TODO: is there an off-by-one error? - if( mode == TARGET_MODE_SPELL ) { - draw_spell_ui_old( pc ); - } else { - draw_normal_ui_old( pc ); + if( mode == TARGET_MODE_FIRE || mode == TARGET_MODE_TURRET_MANUAL ) { + panel_gun_info( text_y ); + panel_recoil( pc, text_y ); + text_y += compact ? 0 : 1; + } else if( mode == TARGET_MODE_SPELL ) { + panel_spell_info( pc, text_y ); + text_y += compact ? 0 : 1; + } + + panel_target_info( pc, text_y ); + text_y += compact ? 0 : 1; + + if( src != dst ) { + // TODO: these are old, consider refactoring + if( mode == TARGET_MODE_FIRE ) { + panel_fire_mode_aim( pc, text_y ); + } else if( mode == TARGET_MODE_TURRET ) { + list_turrets_in_range( veh, *vturrets, w_target, text_y, dst ); + } else if( mode == TARGET_MODE_THROW || mode == TARGET_MODE_THROW_BLIND ) { + bool blind = ( mode == TARGET_MODE_THROW_BLIND ); + draw_throw_aim( pc, w_target, text_y, ctxt, *relevant, dst, blind ); + } } wrefresh( w_target ); @@ -2740,6 +2563,13 @@ std::string target_ui::uitext_fire() } } +void target_ui::draw_window_title() +{ + mvwprintz( w_target, point( 2, 0 ), c_white, "< " ); + trim_and_print( w_target, point( 4, 0 ), getmaxx( w_target ) - 7, c_red, uitext_title() ); + wprintz( w_target, c_white, " >" ); +} + int target_ui::draw_controls_list() { // Get first key bound to given action OR ' ' if there are none. @@ -2793,6 +2623,173 @@ int target_ui::draw_controls_list() return height - text_y; } +void target_ui::panel_cursor_info( int &text_y ) +{ + int dz = dst.z - src.z; + std::string label_range; + if( dst != src ) { + label_range = string_format( "%d/%d", dist_fn( dst ), range ); + } else { + label_range = string_format( "%d", range ); + }; + mvwprintw( w_target, point( 1, text_y++ ), _( "Range: %s Elevation: %d Targets: %d" ), label_range, + dz, targets.size() ); +} + +void target_ui::panel_gun_info( int &text_y ) +{ + gun_mode m = relevant->gun_current_mode(); + std::string mode_name = m.tname(); + std::string gunmod_name = ""; + if( m.target != relevant ) { + // Gun mode comes from a gunmod, not base gun. Add gunmod's name + gunmod_name = m->tname() + " "; + } + std::string str = string_format( _( "Firing mode: %s%s (%d)" ), + gunmod_name, mode_name, m.qty + ); + nc_color clr = c_light_gray; + print_colored_text( w_target, point( 1, text_y++ ), clr, clr, str ); + + if( ammo ) { + str = string_format( m->ammo_remaining() ? _( "Ammo: %s (%d/%d)" ) : _( "Ammo: %s" ), + colorize( ammo->nname( std::max( m->ammo_remaining(), 1 ) ), ammo->color ), m->ammo_remaining(), + m->ammo_capacity() ); + print_colored_text( w_target, point( 1, text_y++ ), clr, clr, str ); + } else { + // Skip ammo line. + text_y++; + } +} + +void target_ui::panel_recoil( player &pc, int &text_y ) +{ + const int val = pc.recoil_total(); + const int min_recoil = pc.effective_dispersion( relevant->sight_dispersion() ); + const int recoil_range = MAX_RECOIL - min_recoil; + std::string str; + if( val >= min_recoil + ( recoil_range * 2 / 3 ) ) { + str = pgettext( "amount of backward momentum", "High" ); + } else if( val >= min_recoil + ( recoil_range / 2 ) ) { + str = pgettext( "amount of backward momentum", "Medium" ); + } else if( val >= min_recoil + ( recoil_range / 4 ) ) { + str = pgettext( "amount of backward momentum", "Low" ); + } else { + str = pgettext( "amount of backward momentum", "None" ); + } + str = string_format( _( "Recoil: %s" ), str ); + nc_color clr = c_light_gray; + print_colored_text( w_target, point( 1, text_y++ ), clr, clr, str ); +} + +void target_ui::panel_spell_info( player &pc, int &text_y ) +{ + nc_color clr = c_light_gray; + + mvwprintz( w_target, point( 1, text_y++ ), c_light_green, _( "Casting: %s (Level %u)" ), + casting->name(), + casting->get_level() ); + if( !no_mana || casting->energy_source() == none_energy ) { + if( casting->energy_source() == hp_energy ) { + text_y += fold_and_print( w_target, point( 1, text_y ), getmaxx( w_target ) - 2, + clr, + _( "Cost: %s %s" ), casting->energy_cost_string( pc ), casting->energy_string() ); + } else { + text_y += fold_and_print( w_target, point( 1, text_y ), getmaxx( w_target ) - 2, + clr, + _( "Cost: %s %s (Current: %s)" ), casting->energy_cost_string( pc ), casting->energy_string(), + casting->energy_cur_string( pc ) ); + } + } + + std::string fail_str; + if( no_fail ) { + fail_str = colorize( _( "0.0 % Failure Chance" ), c_light_green ); + } else { + fail_str = casting->colorized_fail_percent( pc ); + } + print_colored_text( w_target, point( 1, text_y++ ), clr, clr, fail_str ); + + if( casting->aoe() > 0 ) { + nc_color color = c_light_gray; + const std::string fx = casting->effect(); + const std::string aoes = casting->aoe_string(); + if( fx == "projectile_attack" || fx == "target_attack" || + fx == "area_pull" || fx == "area_push" || + fx == "ter_transform" ) { + text_y += fold_and_print( w_target, point( 1, text_y ), getmaxx( w_target ) - 2, color, + _( "Effective Spell Radius: %s%s" ), aoes, + casting->in_aoe( src, dst ) ? colorize( _( " WARNING! IN RANGE" ), c_red ) : "" ); + } else if( fx == "cone_attack" ) { + text_y += fold_and_print( w_target, point( 1, text_y ), getmaxx( w_target ) - 2, color, + _( "Cone Arc: %s degrees" ), aoes ); + } else if( fx == "line_attack" ) { + text_y += fold_and_print( w_target, point( 1, text_y ), getmaxx( w_target ) - 2, color, + _( "Line width: %s" ), aoes ); + } + } + + mvwprintz( w_target, point( 1, text_y++ ), c_light_red, _( "Damage: %s" ), + casting->damage_string() ); + + text_y += fold_and_print( w_target, point( 1, text_y ), getmaxx( w_target ) - 2, clr, + casting->description() ); +} + +void target_ui::panel_target_info( player &pc, int &text_y ) +{ + int max_lines = 4; + if( dst_critter && pc.sees( *dst_critter ) ) { + // FIXME: print_info doesn't really care about line limit + // and can always occupy up to 4 of them (or even more?). + // To make things consistent, we ask it for 2 lines + // and somewhat reliably get 4. + int fix_for_print_info = max_lines - 2; + dst_critter->print_info( w_target, text_y, fix_for_print_info, 1 ); + } else { + // Fill with blank lines to prevent other panels from jumping around + // when the cursor moves. + // TODO: print info about tile? + } + text_y += max_lines; +} + +void target_ui::panel_fire_mode_aim( player &pc, int &text_y ) +{ + // These 2 lines here keep the good ol' code working during the trying times of refactoring + double saved_pc_recoil = pc.recoil; + pc.recoil = predicted_recoil; + + double predicted_recoil = pc.recoil; + int predicted_delay = 0; + if( aim_mode->has_threshold && aim_mode->threshold < pc.recoil ) { + do { + const double aim_amount = pc.aim_per_move( *relevant, predicted_recoil ); + if( aim_amount > 0 ) { + predicted_delay++; + predicted_recoil = std::max( predicted_recoil - aim_amount, 0.0 ); + } + } while( predicted_recoil > aim_mode->threshold && + predicted_recoil - sight_dispersion > 0 ); + } else { + predicted_recoil = pc.recoil; + } + + const double target_size = dst_critter ? dst_critter->ranged_target_size() : + occupied_tile_fraction( MS_MEDIUM ); + + text_y = print_aim( pc, w_target, text_y, ctxt, &*relevant->gun_current_mode(), + target_size, dst, predicted_recoil ); + + if( aim_mode->has_threshold ) { + mvwprintw( w_target, point( 1, text_y++ ), _( "%s Delay: %i" ), aim_mode->name, + predicted_delay ); + } + + // End of old code compatibility + pc.recoil = saved_pc_recoil; +} + void target_ui::on_target_accepted( player &pc, bool harmful ) { // TODO: all of this should be moved into on-hit code From 6516d418d93d856f585cc97c15df3b7fe5b6ad0d Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Mon, 20 Apr 2020 17:35:20 +0300 Subject: [PATCH 15/43] Re-implement reloading guns from aiming UI It had to wait until event loop has been refactored --- src/avatar_action.cpp | 8 +++++++- src/game.cpp | 4 ++-- src/game.h | 2 +- src/ranged.cpp | 47 ++++++++++++++++++++++++++----------------- src/ranged.h | 7 +++++-- 5 files changed, 43 insertions(+), 25 deletions(-) diff --git a/src/avatar_action.cpp b/src/avatar_action.cpp index 907dcd810a34e..d074ef305194d 100644 --- a/src/avatar_action.cpp +++ b/src/avatar_action.cpp @@ -900,7 +900,8 @@ void avatar_action::aim_do_turn( avatar &you, map &m ) g->temp_exit_fullscreen(); m.draw( g->w_terrain, you.pos() ); - target_handler::trajectory trajectory = target_handler::mode_fire( you, weapon ); + 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(); @@ -915,6 +916,11 @@ void avatar_action::aim_do_turn( avatar &you, map &m ) you.moves = previous_moves; } g->reenter_fullscreen(); + + if( reload_requested ) { + // Reload the gun / select different arrows + g->reload_wielded( true ); + } return; } // Recenter our view diff --git a/src/game.cpp b/src/game.cpp index e513452de96ca..9031450354be9 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -8503,14 +8503,14 @@ void game::reload_item() reload( item_loc ); } -void game::reload_wielded() +void game::reload_wielded( bool prompt ) { if( u.weapon.is_null() || !u.weapon.is_reloadable() ) { add_msg( _( "You aren't holding something you can reload." ) ); return; } item_location item_loc = item_location( u, &u.weapon ); - reload( item_loc ); + reload( item_loc, prompt ); } void game::reload_weapon( bool try_everything ) diff --git a/src/game.h b/src/game.h index 26d0d33ef47e3..de47c4fb0e4de 100644 --- a/src/game.h +++ b/src/game.h @@ -781,7 +781,7 @@ class game void reload( item_location &loc, bool prompt = false, bool empty = true ); public: void reload_item(); // Reload an item - void reload_wielded(); + void reload_wielded( bool prompt = false ); void reload_weapon( bool try_everything = true ); // Reload a wielded gun/tool 'r' // Places the player at the specified point; hurts feet, lists items etc. point place_player( const tripoint &dest ); diff --git a/src/ranged.cpp b/src/ranged.cpp index 4b264f65c5dc1..f95b1bf915ee7 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -137,7 +137,15 @@ class target_ui // Spell does not require mana bool no_mana = false; - target_handler::trajectory run( player &pc ); + enum class ExitCode { + Abort, + Fire, + Timeout, + Reload + }; + // Initialize UI and run the event loop. + // If exit_code != nullptr, exit code will be written into provided address + target_handler::trajectory run( player &pc, ExitCode *exit_code = nullptr ); private: // Current trajectory (TODO: better name) @@ -285,7 +293,8 @@ class target_ui void on_target_accepted( player &pc, bool harmful ); }; -target_handler::trajectory target_handler::mode_fire( player &pc, item *weapon ) +target_handler::trajectory target_handler::mode_fire( player &pc, item *weapon, + bool &reload_requested ) { target_ui ui = target_ui(); ui.mode = TARGET_MODE_FIRE; @@ -294,7 +303,10 @@ target_handler::trajectory target_handler::mode_fire( player &pc, item *weapon ) ui.range = gun.target->gun_range( &pc ); ui.ammo = gun->ammo_data(); - return ui.run( pc ); + target_ui::ExitCode exit_code; + trajectory result = ui.run( pc, &exit_code ); + reload_requested = exit_code == target_ui::ExitCode::Reload; + return result; } target_handler::trajectory target_handler::mode_throw( player &pc, item *relevant, @@ -1846,7 +1858,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 ) +target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) { if( mode == TARGET_MODE_SPELL ) { if( !no_mana && !casting->can_cast( pc ) ) { @@ -1949,13 +1961,7 @@ target_handler::trajectory target_ui::run( player &pc ) } } - enum class ExitCode { - Abort, - Fire, - Timeout, - Reload - }; - ExitCode exit_code; + ExitCode loop_exit_code; std::string timed_out_activity; bool skip_redraw = false; @@ -1988,13 +1994,13 @@ target_handler::trajectory target_ui::run( player &pc ) } else if( action == "zoom_out" ) { g->zoom_out(); } else if( action == "QUIT" ) { - exit_code = ExitCode::Abort; + loop_exit_code = ExitCode::Abort; break; } else if( action == "SWITCH_MODE" ) { action_switch_mode( pc ); } else if( action == "SWITCH_AMMO" ) { if( !action_switch_ammo() ) { - exit_code = ExitCode::Reload; + loop_exit_code = ExitCode::Reload; break; } } else if( action == "SWITCH_AIM" ) { @@ -2012,7 +2018,7 @@ target_handler::trajectory target_ui::run( player &pc ) continue; } set_last_target( pc ); - exit_code = ExitCode::Fire; + loop_exit_code = ExitCode::Fire; break; } else if( action == "AIM" ) { if( src == dst ) { @@ -2024,7 +2030,7 @@ target_handler::trajectory target_ui::run( player &pc ) if( !action_aim( pc ) ) { timed_out_activity = "AIM"; - exit_code = ExitCode::Timeout; + loop_exit_code = ExitCode::Timeout; break; } } else if( action == "AIMED_SHOT" || action == "CAREFUL_SHOT" || action == "PRECISE_SHOT" ) { @@ -2039,10 +2045,10 @@ target_handler::trajectory target_ui::run( player &pc ) } if( action_aim_and_shoot( pc, action ) ) { - exit_code = ExitCode::Fire; + loop_exit_code = ExitCode::Fire; } else { timed_out_activity = action; - exit_code = ExitCode::Timeout; + loop_exit_code = ExitCode::Timeout; } break; } @@ -2050,7 +2056,7 @@ target_handler::trajectory target_ui::run( player &pc ) set_view_offset( pc, saved_view_offset ); - switch( exit_code ) { + switch( loop_exit_code ) { case ExitCode::Abort: { ret.clear(); break; @@ -2070,12 +2076,15 @@ target_handler::trajectory target_ui::run( player &pc ) break; } case ExitCode::Reload: { - // TODO: make this work + // Calling function will handle reloading for us ret.clear(); break; } } + if( exit_code ) { + *exit_code = loop_exit_code; + } return ret; } diff --git a/src/ranged.h b/src/ranged.h index 8f8473905db44..072d0f722a2b2 100644 --- a/src/ranged.h +++ b/src/ranged.h @@ -73,8 +73,11 @@ namespace target_handler // Trajectory to target. Empty if selection was aborted or player ran out of moves using trajectory = std::vector; -/** Firing ranged weapon. This mode allows spending moves on aiming. */ -trajectory mode_fire( player &pc, item *weapon ); +/** + * 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 ); /** Throwing item */ trajectory mode_throw( player &pc, item *relevant, bool blind_throwing ); From 7a0d7fbeae0301e161a78335582239f6affe3506 Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Mon, 20 Apr 2020 18:23:21 +0300 Subject: [PATCH 16/43] Handle empty gun modes and gun modes with different range --- src/ranged.cpp | 94 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 70 insertions(+), 24 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index f95b1bf915ee7..ae8b61e6c1ce0 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -148,6 +148,16 @@ class target_ui target_handler::trajectory run( player &pc, ExitCode *exit_code = nullptr ); private: + enum class Status { + Good, // All UI elements are enabled + BadTarget, // Bad 'dst' selected; forbid aiming/firing + OutOfAmmo, // Selected gun mode is out of ammo; forbid moving cursor,aiming and firing + OutOfRange // Selected target is out of range of current gun mode; forbid aiming/firing + }; + + // Ui status (affects which UI controls are temporarily disabled) + Status status; + // Current trajectory (TODO: better name) std::vector ret; // Aiming source (player's position) @@ -181,9 +191,6 @@ class target_ui input_context ctxt; // Number of free instruction lines int num_instruction_lines; - // If true, aiming and firing controls will be drawn using grey color - // to indicate invalid dst (e.g. shooting yourself) - bool grey_out_firing_controls = true; /* These members are relevant for TARGET_MODE_FIRE */ // Weapon sight dispersion @@ -220,6 +227,9 @@ class target_ui // Returns 'false' if cursor position did not change bool set_cursor_pos( player &pc, const tripoint &new_pos ); + // Update 'status' variable + void update_status(); + // Calculates distance from 'src'. For consistency, prefer using this over rl_dist. int dist_fn( const tripoint &p ); @@ -2009,8 +2019,7 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) aim_mode = aim_types.begin(); } } else if( action == "FIRE" ) { - if( src == dst ) { - // TODO: consider allowing aiming spells/turrets at yourself + if( status != Status::Good ) { continue; } bool can_skip_confirm = ( mode == TARGET_MODE_SPELL && casting->damage() <= 0 ); @@ -2021,7 +2030,7 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) loop_exit_code = ExitCode::Fire; break; } else if( action == "AIM" ) { - if( src == dst ) { + if( status != Status::Good ) { continue; } @@ -2034,7 +2043,7 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) break; } } else if( action == "AIMED_SHOT" || action == "CAREFUL_SHOT" || action == "PRECISE_SHOT" ) { - if( src == dst ) { + if( status != Status::Good ) { continue; } @@ -2136,6 +2145,10 @@ bool target_ui::set_cursor_pos( player &pc, const tripoint &new_pos ) if( dst == new_pos ) { return false; } + if( status == Status::OutOfAmmo && new_pos != src ) { + // range == 0, no sense in moving cursor + return false; + } // Make sure new position is valid or find a closest valid position std::vector new_traj; @@ -2237,12 +2250,35 @@ bool target_ui::set_cursor_pos( player &pc, const tripoint &new_pos ) recalc_aim_turning_penalty( pc ); } - // Update UI colors - grey_out_firing_controls = ( dst == src ); + // Update UI controls & colors + update_status(); return true; } +void target_ui::update_status() +{ + if( mode == TARGET_MODE_FIRE || mode == TARGET_MODE_TURRET_MANUAL ) { + if( range == 0 ) { + // Selected gun mode is empty + status = Status::OutOfAmmo; + return; + } + } + if( src == dst ) { + // TODO: consider allowing targeting yourself with spells/turrets + status = Status::BadTarget; + } else if( dist_fn( dst ) > range ) { + // We're out of range. This can happen if we switch from long-ranged + // gun mode to short-ranged. We can, of course, move the cursor into range automatically, + // but that would be rude. Instead, wait for directional keys/etc. and *then* move the cursor. + status = Status::OutOfRange; + // TODO: add check when none of the vehicle turrets are in range + } else { + status = Status::Good; + } +} + int target_ui::dist_fn( const tripoint &p ) { return static_cast( std::round( trig_dist( src, p ) ) ); @@ -2318,8 +2354,8 @@ void target_ui::set_view_offset( player &pc, const tripoint &new_offset ) void target_ui::recalc_aim_turning_penalty( player &pc ) { - if( dst == src ) { - // Can't aim at yourself + if( status != Status::Good ) { + // We don't care about invalid situations predicted_recoil = MAX_RECOIL; return; } @@ -2380,6 +2416,7 @@ void target_ui::action_switch_mode( player &pc ) ammo = relevant->gun_current_mode().target->ammo_data(); range = relevant->gun_current_mode().target->gun_range( &pc ); } + update_status(); } bool target_ui::action_switch_ammo() @@ -2398,6 +2435,7 @@ bool target_ui::action_switch_ammo() // reloading annihilates our aim anyway return false; } + update_status(); return true; } @@ -2529,7 +2567,7 @@ void target_ui::draw_ui_window( player &pc ) panel_target_info( pc, text_y ); text_y += compact ? 0 : 1; - if( src != dst ) { + if( status == Status::Good ) { // TODO: these are old, consider refactoring if( mode == TARGET_MODE_FIRE ) { panel_fire_mode_aim( pc, text_y ); @@ -2587,7 +2625,8 @@ int target_ui::draw_controls_list() return keys.empty() ? ' ' : keys.front(); }; - nc_color fire_color = grey_out_firing_controls ? c_light_gray : c_white; + nc_color move_color = ( status != Status::OutOfAmmo ? c_white : c_light_gray ); + nc_color fire_color = ( status == Status::Good ? c_white : c_light_gray ); // Since this list is of variable length and positioned // at the bottom, we draw everything in reverse order @@ -2595,7 +2634,7 @@ int target_ui::draw_controls_list() if( is_mouse_enabled() ) { const char *label_mouse = "Mouse: LMB: Target, Wheel: Cycle,"; int text_x = utf8_width( label_mouse ) + 2; // '2' for border + space at the end - mvwprintz( w_target, point( 1, text_y ), c_white, label_mouse ); + mvwprintz( w_target, point( 1, text_y ), move_color, label_mouse ); mvwprintz( w_target, point( text_x, text_y-- ), fire_color, _( "RMB: Fire" ) ); } if( mode == TARGET_MODE_FIRE || mode == TARGET_MODE_TURRET_MANUAL ) { @@ -2624,11 +2663,11 @@ int target_ui::draw_controls_list() auto label_cycle = string_format( _( "[%s] Cycle targets;" ), ctxt.get_desc( "NEXT_TARGET", 1 ) ); int text_x = utf8_width( label_cycle ) + 2; // '2' for border + space at the end - mvwprintz( w_target, point( 1, text_y ), c_white, label_cycle ); + mvwprintz( w_target, point( 1, text_y ), move_color, label_cycle ); mvwprintz( w_target, point( text_x, text_y-- ), fire_color, "[%c] %s.", bound_key( "FIRE" ), uitext_fire() ); - mvwprintz( w_target, point( 1, text_y-- ), c_white, _( "Move cursor with directional keys" ) ); + mvwprintz( w_target, point( 1, text_y-- ), move_color, _( "Move cursor with directional keys" ) ); return height - text_y; } @@ -2636,13 +2675,18 @@ void target_ui::panel_cursor_info( int &text_y ) { int dz = dst.z - src.z; std::string label_range; - if( dst != src ) { - label_range = string_format( "%d/%d", dist_fn( dst ), range ); + if( src == dst ) { + label_range = string_format( "Range: %d", range ); } else { - label_range = string_format( "%d", range ); - }; - mvwprintw( w_target, point( 1, text_y++ ), _( "Range: %s Elevation: %d Targets: %d" ), label_range, - dz, targets.size() ); + label_range = string_format( "Range: %d/%d", dist_fn( dst ), range ); + } + if( status == Status::OutOfRange ) { + label_range = colorize( label_range, c_red ); + } + label_range = string_format( _( "%s Elevation: %d Targets: %d" ), label_range, dz, + targets.size() ); + nc_color clr = c_light_gray; + print_colored_text( w_target, point( 1, text_y++ ), clr, clr, label_range ); } void target_ui::panel_gun_info( int &text_y ) @@ -2660,13 +2704,15 @@ void target_ui::panel_gun_info( int &text_y ) nc_color clr = c_light_gray; print_colored_text( w_target, point( 1, text_y++ ), clr, clr, str ); - if( ammo ) { + if( status == Status::OutOfAmmo ) { + mvwprintz( w_target, point( 1, text_y++ ), c_red, _( "OUT OF AMMO" ) ); + } else if( ammo ) { str = string_format( m->ammo_remaining() ? _( "Ammo: %s (%d/%d)" ) : _( "Ammo: %s" ), colorize( ammo->nname( std::max( m->ammo_remaining(), 1 ) ), ammo->color ), m->ammo_remaining(), m->ammo_capacity() ); print_colored_text( w_target, point( 1, text_y++ ), clr, clr, str ); } else { - // Skip ammo line. + // Weapon doesn't use ammunition text_y++; } } From b67a64692ce5f27dd90e7de323c999ad516370cf Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Mon, 20 Apr 2020 19:08:22 +0300 Subject: [PATCH 17/43] Some minor improvements & cleanup --- src/activity_handlers.cpp | 2 +- src/avatar_action.cpp | 6 +- src/handle_action.cpp | 2 +- src/ranged.cpp | 137 ++++++++++++++++++-------------------- src/ranged.h | 12 ++-- src/turret.cpp | 2 +- 6 files changed, 76 insertions(+), 85 deletions(-) diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index 7c8812dbe56ca..0eb1286cb330e 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -4780,7 +4780,7 @@ void activity_handlers::spellcasting_finish( player_activity *act, player *p ) if( spell_being_cast.range() > 0 && !spell_being_cast.is_valid_target( target_none ) && !spell_being_cast.has_flag( RANDOM_TARGET ) ) { do { - std::vector trajectory = target_handler::mode_spell( *p, &spell_being_cast, no_fail, + std::vector trajectory = target_handler::mode_spell( *p, spell_being_cast, no_fail, no_mana ); if( !trajectory.empty() ) { target = trajectory.back(); diff --git a/src/avatar_action.cpp b/src/avatar_action.cpp index d074ef305194d..621dd296c3374 100644 --- a/src/avatar_action.cpp +++ b/src/avatar_action.cpp @@ -901,7 +901,7 @@ void avatar_action::aim_do_turn( avatar &you, map &m ) 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, reload_requested ); //may be changed in target_ui gun = weapon->gun_current_mode(); @@ -983,7 +983,7 @@ void avatar_action::fire_turret_manual( avatar &you, map &m, turret_data &turret g->temp_exit_fullscreen(); g->m.draw( g->w_terrain, you.pos() ); - target_handler::trajectory trajectory = target_handler::mode_turret_manual( you, &turret ); + target_handler::trajectory trajectory = target_handler::mode_turret_manual( you, turret ); if( !trajectory.empty() ) { // Recenter our view @@ -1177,7 +1177,7 @@ void avatar_action::plthrow( avatar &you, item_location loc, g->temp_exit_fullscreen(); g->m.draw( g->w_terrain, you.pos() ); - target_handler::trajectory trajectory = target_handler::mode_throw( you, &you.weapon, + target_handler::trajectory trajectory = target_handler::mode_throw( you, you.weapon, blind_throw_from_pos.has_value() ); // If we previously shifted our position, put ourselves back now that we've picked our target. diff --git a/src/handle_action.cpp b/src/handle_action.cpp index ae725acfea84a..a31c7bd6217f4 100644 --- a/src/handle_action.cpp +++ b/src/handle_action.cpp @@ -1289,7 +1289,7 @@ static void reach_attack( player &u ) g->temp_exit_fullscreen(); g->m.draw( g->w_terrain, u.pos() ); - target_handler::trajectory traj = target_handler::mode_reach( u, &u.weapon ); + target_handler::trajectory traj = target_handler::mode_reach( u, u.weapon ); if( !traj.empty() ) { u.reach_attack( traj.back() ); diff --git a/src/ranged.cpp b/src/ranged.cpp index ae8b61e6c1ce0..37300247e89d9 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -116,6 +116,8 @@ enum target_mode : int { class target_ui { public: + /* None of the public members (except ammo and range) should be modified during execution */ + // Interface mode target_mode mode = TARGET_MODE_FIRE; // Weapon being fired/thrown @@ -143,7 +145,9 @@ class target_ui Timeout, 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 ); @@ -158,8 +162,8 @@ class target_ui // Ui status (affects which UI controls are temporarily disabled) Status status; - // Current trajectory (TODO: better name) - std::vector ret; + // Current trajectory + std::vector traj; // Aiming source (player's position) tripoint src; // Aiming destination (cursor position) @@ -179,18 +183,12 @@ class target_ui // Compact layout - slightly smaller then normal bool compact; - // Tiny layout - smallest possible + // Tiny layout - uses whole sidebar bool tiny; - // Window width - int height; - // Window height - int width; // Window catacurses::window w_target; // Input context input_context ctxt; - // Number of free instruction lines - int num_instruction_lines; /* These members are relevant for TARGET_MODE_FIRE */ // Weapon sight dispersion @@ -206,16 +204,9 @@ class target_ui // relative to the current one. double predicted_recoil; - /* These members are relevant for TARGET_MODE_SPELL */ // For AOE spells, list of tiles affected by the spell + // relevant for TARGET_MODE_SPELL std::set spell_aoe; - // Spell casting effect - std::string spell_fx; - - // Returns new cursor position or current cursor position - // based on user input - // TODO: betters docs/name? - tripoint get_desired_cursor_pos( const std::string &action ); // Handle input related to cursor movement. // Returns 'true' if action was recognized and processed. @@ -303,13 +294,13 @@ class target_ui void on_target_accepted( player &pc, bool harmful ); }; -target_handler::trajectory target_handler::mode_fire( player &pc, item *weapon, +target_handler::trajectory target_handler::mode_fire( player &pc, item &weapon, bool &reload_requested ) { target_ui ui = target_ui(); ui.mode = TARGET_MODE_FIRE; - ui.relevant = weapon; - gun_mode gun = weapon->gun_current_mode(); + ui.relevant = &weapon; + gun_mode gun = weapon.gun_current_mode(); ui.range = gun.target->gun_range( &pc ); ui.ammo = gun->ammo_data(); @@ -319,49 +310,49 @@ target_handler::trajectory target_handler::mode_fire( player &pc, item *weapon, return result; } -target_handler::trajectory target_handler::mode_throw( player &pc, item *relevant, +target_handler::trajectory target_handler::mode_throw( player &pc, item &relevant, bool blind_throwing ) { target_ui ui = target_ui(); ui.mode = blind_throwing ? TARGET_MODE_THROW_BLIND : TARGET_MODE_THROW; - ui.relevant = relevant; - ui.range = pc.throw_range( *relevant ); + ui.relevant = &relevant; + ui.range = pc.throw_range( relevant ); return ui.run( pc ); } -target_handler::trajectory target_handler::mode_reach( player &pc, item *weapon ) +target_handler::trajectory target_handler::mode_reach( player &pc, item &weapon ) { target_ui ui = target_ui(); ui.mode = TARGET_MODE_REACH; - ui.relevant = weapon; - ui.range = weapon->current_reach_range( pc ); + ui.relevant = &weapon; + ui.range = weapon.current_reach_range( pc ); return ui.run( pc ); } -target_handler::trajectory target_handler::mode_turret_manual( player &pc, turret_data *turret ) +target_handler::trajectory target_handler::mode_turret_manual( player &pc, turret_data &turret ) { target_ui ui = target_ui(); ui.mode = TARGET_MODE_TURRET_MANUAL; - ui.turret = turret; - ui.relevant = &*turret->base(); - ui.range = turret->range(); - ui.ammo = turret->ammo_data(); + ui.turret = &turret; + ui.relevant = &*turret.base(); + ui.range = turret.range(); + ui.ammo = turret.ammo_data(); return ui.run( pc ); } -target_handler::trajectory target_handler::mode_turrets( player &pc, vehicle *veh, - const std::vector *turrets ) +target_handler::trajectory target_handler::mode_turrets( player &pc, vehicle &veh, + const std::vector &turrets ) { // Find radius of a circle centered at u encompassing all points turrets can aim at // FIXME: this calculation is fine for square distances, but results in an underestimation // when used with real circles int range_total = 0; - for( vehicle_part *t : *turrets ) { - int range = veh->turret_query( *t ).range(); - tripoint pos = veh->global_part_pos3( *t ); + for( vehicle_part *t : turrets ) { + int range = veh.turret_query( *t ).range(); + tripoint pos = veh.global_part_pos3( *t ); int res = 0; res = std::max( res, rl_dist( g->u.pos(), pos + point( range, 0 ) ) ); @@ -373,20 +364,20 @@ target_handler::trajectory target_handler::mode_turrets( player &pc, vehicle *ve target_ui ui = target_ui(); ui.mode = TARGET_MODE_TURRET; - ui.veh = veh; - ui.vturrets = turrets; + ui.veh = &veh; + ui.vturrets = &turrets; ui.range = range_total; return ui.run( pc ); } -target_handler::trajectory target_handler::mode_spell( player &pc, spell *casting, bool no_fail, +target_handler::trajectory target_handler::mode_spell( player &pc, spell &casting, bool no_fail, bool no_mana ) { target_ui ui = target_ui(); ui.mode = TARGET_MODE_SPELL; - ui.casting = casting; - ui.range = casting->range(); + ui.casting = &casting; + ui.range = casting.range(); ui.no_fail = no_fail; ui.no_mana = no_mana; @@ -396,7 +387,7 @@ target_handler::trajectory target_handler::mode_spell( player &pc, spell *castin target_handler::trajectory target_handler::mode_spell( player &pc, spell_id sp, bool no_fail, bool no_mana ) { - return mode_spell( pc, &g->u.magic.get_spell( sp ), no_fail, no_mana ); + return mode_spell( pc, g->u.magic.get_spell( sp ), no_fail, no_mana ); } bool targeting_data::is_valid() const @@ -1870,12 +1861,9 @@ double player::gun_value( const item &weap, int ammo ) const target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) { - if( mode == TARGET_MODE_SPELL ) { - if( !no_mana && !casting->can_cast( pc ) ) { - pc.add_msg_if_player( m_bad, _( "You don't have enough %s to cast this spell" ), - casting->energy_string() ); - } - spell_fx = casting->effect(); + if( mode == TARGET_MODE_SPELL && !no_mana && !casting->can_cast( pc ) ) { + pc.add_msg_if_player( m_bad, _( "You don't have enough %s to cast this spell" ), + casting->energy_string() ); } // Load settings @@ -1886,7 +1874,8 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) compact = TERMY < 41; tiny = TERMY < 32; int top = 0; - width = 55; + int width = 55; + int height; if( tiny ) { // If we're extremely short on space, use the whole sidebar. height = TERMY; @@ -1972,7 +1961,7 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) } ExitCode loop_exit_code; - std::string timed_out_activity; + std::string timed_out_action; bool skip_redraw = false; const tripoint saved_view_offset = pc.view_offset; @@ -2038,7 +2027,7 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) // Aiming can be stopped / aborted at any time. if( !action_aim( pc ) ) { - timed_out_activity = "AIM"; + timed_out_action = "AIM"; loop_exit_code = ExitCode::Timeout; break; } @@ -2056,7 +2045,7 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) if( action_aim_and_shoot( pc, action ) ) { loop_exit_code = ExitCode::Fire; } else { - timed_out_activity = action; + timed_out_action = action; loop_exit_code = ExitCode::Timeout; } break; @@ -2067,7 +2056,7 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) switch( loop_exit_code ) { case ExitCode::Abort: { - ret.clear(); + traj.clear(); break; } case ExitCode::Fire: { @@ -2079,14 +2068,14 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) // 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. - ret.clear(); + traj.clear(); pc.assign_activity( ACT_AIM, 0, 0 ); - pc.activity.str_values.push_back( timed_out_activity ); + pc.activity.str_values.push_back( timed_out_action ); break; } case ExitCode::Reload: { // Calling function will handle reloading for us - ret.clear(); + traj.clear(); break; } } @@ -2094,7 +2083,7 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) if( exit_code ) { *exit_code = loop_exit_code; } - return ret; + return traj; } bool target_ui::handle_cursor_movement( player &pc, const std::string &action, bool &skip_redraw ) @@ -2198,10 +2187,10 @@ bool target_ui::set_cursor_pos( player &pc, const tripoint &new_pos ) } else if( new_pos == valid_pos ) { // We can reuse new_traj dst = valid_pos; - ret = new_traj; + traj = new_traj; } else { dst = valid_pos; - ret = g->m.find_clear_path( src, dst ); + traj = g->m.find_clear_path( src, dst ); } if( snap_to_target ) { @@ -2233,21 +2222,20 @@ bool target_ui::set_cursor_pos( player &pc, const tripoint &new_pos ) dst_critter = nullptr; } - // Update tiles affected by AOE spells - if( spell_fx == "target_attack" || spell_fx == "projectile_attack" || - spell_fx == "ter_transform" ) { - spell_aoe = spell_effect::spell_effect_blast( *casting, src, dst, casting->aoe(), true ); - } else if( spell_fx == "cone_attack" ) { - spell_aoe = spell_effect::spell_effect_cone( *casting, src, dst, casting->aoe(), true ); - } else if( spell_fx == "line_attack" ) { - spell_aoe = spell_effect::spell_effect_line( *casting, src, dst, casting->aoe(), true ); - } else { - spell_aoe.clear(); - } - - // Update predicted 'recoil' + // Update mode-specific stuff if( mode == TARGET_MODE_FIRE ) { recalc_aim_turning_penalty( pc ); + } else if( mode == TARGET_MODE_SPELL ) { + const std::string fx = casting->effect(); + if( fx == "target_attack" || fx == "projectile_attack" || fx == "ter_transform" ) { + spell_aoe = spell_effect::spell_effect_blast( *casting, src, dst, casting->aoe(), true ); + } else if( fx == "cone_attack" ) { + spell_aoe = spell_effect::spell_effect_cone( *casting, src, dst, casting->aoe(), true ); + } else if( fx == "line_attack" ) { + spell_aoe = spell_effect::spell_effect_line( *casting, src, dst, casting->aoe(), true ); + } else { + spell_aoe.clear(); + } } // Update UI controls & colors @@ -2504,7 +2492,7 @@ void target_ui::draw_terrain( player &pc ) // Draw trajectory if( dst != src ) { // But only points on this Z-level - std::vector this_z = ret; + std::vector this_z = traj; this_z.erase( std::remove_if( this_z.begin(), this_z.end(), [¢er]( const tripoint & p ) { return p.z != center.z; @@ -2540,6 +2528,8 @@ void target_ui::draw_terrain( player &pc ) void target_ui::draw_ui_window( player &pc ) { // Clear target window and make it non-transparent. + int width = getmaxx( w_target ); + int height = getmaxy( w_target ); for( int y = 0; y < height; y++ ) { for( int x = 0; x < width; x++ ) { mvwputch( w_target, point( x, y ), c_white, ' ' ); @@ -2627,6 +2617,7 @@ int target_ui::draw_controls_list() nc_color move_color = ( status != Status::OutOfAmmo ? c_white : c_light_gray ); nc_color fire_color = ( status == Status::Good ? c_white : c_light_gray ); + int height = getmaxy( w_target ); // Since this list is of variable length and positioned // at the bottom, we draw everything in reverse order diff --git a/src/ranged.h b/src/ranged.h index 072d0f722a2b2..874ed34998acc 100644 --- a/src/ranged.h +++ b/src/ranged.h @@ -77,22 +77,22 @@ 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, bool &reload_requested ); /** Throwing item */ -trajectory mode_throw( player &pc, item *relevant, bool blind_throwing ); +trajectory mode_throw( player &pc, item &relevant, bool blind_throwing ); /** Reach attacking */ -trajectory mode_reach( player &pc, item *weapon ); +trajectory mode_reach( player &pc, item &weapon ); /** Manually firing vehicle turret */ -trajectory mode_turret_manual( player &pc, turret_data *turret ); +trajectory mode_turret_manual( player &pc, turret_data &turret ); /** Selecting target for turrets (when using vehicle controls) */ -trajectory mode_turrets( player &pc, vehicle *veh, const std::vector *turrets ); +trajectory mode_turrets( player &pc, vehicle &veh, const std::vector &turrets ); /** Casting a spell */ -trajectory mode_spell( player &pc, spell *casting, bool no_fail, bool no_mana ); +trajectory mode_spell( player &pc, spell &casting, bool no_fail, bool no_mana ); trajectory mode_spell( player &pc, spell_id sp, bool no_fail, bool no_mana ); } diff --git a/src/turret.cpp b/src/turret.cpp index 2084d5d107128..bb753d19622ca 100644 --- a/src/turret.cpp +++ b/src/turret.cpp @@ -389,7 +389,7 @@ bool vehicle::turrets_aim( std::vector &turrets ) } // Get target - target_handler::trajectory trajectory = target_handler::mode_turrets( g->u, this, &turrets ); + target_handler::trajectory trajectory = target_handler::mode_turrets( g->u, *this, turrets ); bool got_target = !trajectory.empty(); if( got_target ) { From 6a0e7b705aefd3fcab06bcdebe41746f3ad06178 Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Mon, 20 Apr 2020 21:06:18 +0300 Subject: [PATCH 18/43] Refactored display of vehicle turrets in range; made 'fire' not work if there are no turrets in range --- src/ranged.cpp | 76 +++++++++++++++++++++++++++++--------------------- 1 file changed, 44 insertions(+), 32 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index 37300247e89d9..6f0e0ea3dd491 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -208,6 +208,9 @@ class target_ui // relevant for TARGET_MODE_SPELL std::set spell_aoe; + // List of vehicle turrets in range (out of those listed in 'vturrets') + std::vector turrets_in_range; + // Handle input related to cursor movement. // Returns 'true' if action was recognized and processed. // 'skip_redraw' is set to 'true' if there is no need to redraw the UI. @@ -239,6 +242,9 @@ class target_ui // Set new view offset. Updates map cache if necessary void set_view_offset( player &pc, const tripoint &new_offset ); + // Updates 'turrets_in_range' + void update_turrets_in_range(); + // Recalculate 'recoil' penalty. This should be called if // player's 'recoil' value has been modified // Relevant for TARGET_MODE_FIRE @@ -288,6 +294,7 @@ class target_ui void panel_spell_info( player &pc, int &text_y ); void panel_target_info( player &pc, int &text_y ); void panel_fire_mode_aim( player &pc, int &text_y ); + void panel_turret_list( int &text_y ); // On-selected-as-target checks that act as if they are on-hit checks. // `harmful` is `false` if using a non-damaging spell @@ -1327,25 +1334,6 @@ static int print_aim( const player &p, const catacurses::window &w, int line_num range, target_size, predicted_recoil ); } -static int list_turrets_in_range( vehicle *veh, const std::vector &turrets, - const catacurses::window &w, int line_number, const tripoint &target ) -{ - std::vector in_range; - for( vehicle_part *t : turrets ) { - if( veh->turret_query( *t ).in_range( target ) ) { - in_range.push_back( string_format( "* %s", t->name() ) ); - } - } - - mvwprintw( w, point( 1, line_number++ ), _( "Turrets in range: %d" ), in_range.size() ); - for( const std::string &text : in_range ) { - nc_color col = c_white; - print_colored_text( w, point( 1, line_number++ ), col, c_white, text ); - } - - return line_number; -} - static int draw_throw_aim( const player &p, const catacurses::window &w, int line_number, input_context &ctxt, const item &weapon, const tripoint &target_pos, bool is_blind_throw ) @@ -2236,6 +2224,8 @@ bool target_ui::set_cursor_pos( player &pc, const tripoint &new_pos ) } else { spell_aoe.clear(); } + } else if( mode == TARGET_MODE_TURRET ) { + update_turrets_in_range(); } // Update UI controls & colors @@ -2246,14 +2236,13 @@ bool target_ui::set_cursor_pos( player &pc, const tripoint &new_pos ) void target_ui::update_status() { - if( mode == TARGET_MODE_FIRE || mode == TARGET_MODE_TURRET_MANUAL ) { - if( range == 0 ) { - // Selected gun mode is empty - status = Status::OutOfAmmo; - return; - } - } - if( src == dst ) { + if( mode == TARGET_MODE_TURRET && turrets_in_range.empty() ) { + // None of the turrets are in range + status = Status::OutOfRange; + } else if( ( mode == TARGET_MODE_FIRE || mode == TARGET_MODE_TURRET_MANUAL ) && range == 0 ) { + // Selected gun mode is empty + status = Status::OutOfAmmo; + } else if( src == dst ) { // TODO: consider allowing targeting yourself with spells/turrets status = Status::BadTarget; } else if( dist_fn( dst ) > range ) { @@ -2261,7 +2250,6 @@ void target_ui::update_status() // gun mode to short-ranged. We can, of course, move the cursor into range automatically, // but that would be rude. Instead, wait for directional keys/etc. and *then* move the cursor. status = Status::OutOfRange; - // TODO: add check when none of the vehicle turrets are in range } else { status = Status::Good; } @@ -2340,6 +2328,16 @@ void target_ui::set_view_offset( player &pc, const tripoint &new_offset ) pc.view_offset = new_offset; } +void target_ui::update_turrets_in_range() +{ + turrets_in_range.clear(); + for( vehicle_part *t : *vturrets ) { + if( veh->turret_query( *t ).in_range( dst ) ) { + turrets_in_range.push_back( t ); + } + } +} + void target_ui::recalc_aim_turning_penalty( player &pc ) { if( status != Status::Good ) { @@ -2557,12 +2555,12 @@ void target_ui::draw_ui_window( player &pc ) panel_target_info( pc, text_y ); text_y += compact ? 0 : 1; - if( status == Status::Good ) { + if( mode == TARGET_MODE_TURRET ) { + panel_turret_list( text_y ); + } else if( status == Status::Good ) { // TODO: these are old, consider refactoring if( mode == TARGET_MODE_FIRE ) { panel_fire_mode_aim( pc, text_y ); - } else if( mode == TARGET_MODE_TURRET ) { - list_turrets_in_range( veh, *vturrets, w_target, text_y, dst ); } else if( mode == TARGET_MODE_THROW || mode == TARGET_MODE_THROW_BLIND ) { bool blind = ( mode == TARGET_MODE_THROW_BLIND ); draw_throw_aim( pc, w_target, text_y, ctxt, *relevant, dst, blind ); @@ -2671,7 +2669,9 @@ void target_ui::panel_cursor_info( int &text_y ) } else { label_range = string_format( "Range: %d/%d", dist_fn( dst ), range ); } - if( status == Status::OutOfRange ) { + if( status == Status::OutOfRange && mode != TARGET_MODE_TURRET ) { + // Since each turret has its own range, highlighting cursor + // range with red is misleading label_range = colorize( label_range, c_red ); } label_range = string_format( _( "%s Elevation: %d Targets: %d" ), label_range, dz, @@ -2836,6 +2836,18 @@ void target_ui::panel_fire_mode_aim( player &pc, int &text_y ) pc.recoil = saved_pc_recoil; } +void target_ui::panel_turret_list( int &text_y ) +{ + mvwprintw( w_target, point( 1, text_y++ ), "Turrets in range: %d/%d", turrets_in_range.size(), + vturrets->size() ); + + for( vehicle_part *t : turrets_in_range ) { + std::string str = string_format( "* %s", t->name() ); + nc_color clr = c_white; + print_colored_text( w_target, point( 1, text_y++ ), clr, clr, str ); + } +} + void target_ui::on_target_accepted( player &pc, bool harmful ) { // TODO: all of this should be moved into on-hit code From 69c2ec9db4e72eec8c67a04c3415344c6b3c5f45 Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Mon, 20 Apr 2020 21:07:24 +0300 Subject: [PATCH 19/43] Keep 'snap-to-target' state across turns Fixes bug when view snaps back to the player if they have temporarily toggled snap-to-target on, moved aim point away, started aiming and ran out of moves --- src/ranged.cpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index 6f0e0ea3dd491..99e8783e52662 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -1923,13 +1923,6 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) // FIXME: temporarily disable redrawing of lower UIs before this UI is migrated to `ui_adaptor` ui_adaptor ui( ui_adaptor::disable_uis_below {} ); - // Initialize cursor position - src = pc.pos(); - tripoint initial_dst = pc.pos(); - int _target_idx = 0; - update_targets( pc, range, targets, _target_idx, src, initial_dst ); - set_cursor_pos( pc, initial_dst ); - // Handle multi-turn aiming std::string action; if( mode == TARGET_MODE_FIRE ) { @@ -1943,16 +1936,25 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) // So, skip retrieving input and go straight to the action. action = act_data; } + // Load state of snap-to-target mode to keep it consistent between turns + snap_to_target = pc.activity.str_values[1] == "snap"; // Clear the activity, we'll re-set it later if we need to. pc.cancel_activity(); } } + const tripoint saved_view_offset = pc.view_offset; + + // Initialize cursor position + src = pc.pos(); + tripoint initial_dst = pc.pos(); + int _target_idx = 0; + update_targets( pc, range, targets, _target_idx, src, initial_dst ); + set_cursor_pos( pc, initial_dst ); + ExitCode loop_exit_code; std::string timed_out_action; - bool skip_redraw = false; - const tripoint saved_view_offset = pc.view_offset; for( ;; action.clear() ) { // Old drawing if( !skip_redraw ) { @@ -2056,9 +2058,11 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) // 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. + // pc.activity.str_values[1] remembers state of snap-to-target mode traj.clear(); pc.assign_activity( ACT_AIM, 0, 0 ); pc.activity.str_values.push_back( timed_out_action ); + pc.activity.str_values.push_back( snap_to_target ? "snap" : "nosnap" ); break; } case ExitCode::Reload: { From e5dee872d17e050b2e2769ac53a9bb551bcfccaa Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Mon, 20 Apr 2020 22:56:49 +0300 Subject: [PATCH 20/43] Refactor target selection; save aim when killing target --- src/melee.cpp | 2 + src/ranged.cpp | 182 ++++++++++++++++++++++++++----------------------- 2 files changed, 99 insertions(+), 85 deletions(-) diff --git a/src/melee.cpp b/src/melee.cpp index 432e626fefe68..359346e56d044 100644 --- a/src/melee.cpp +++ b/src/melee.cpp @@ -608,6 +608,8 @@ void player::reach_attack( const tripoint &p ) int target_size = critter != nullptr ? critter->get_size() : 2; // Reset last target pos last_target_pos = cata::nullopt; + // Max out recoil + recoil = MAX_RECOIL; int move_cost = attack_speed( weapon ); int skill = std::min( 10, get_skill_level( skill_stabbing ) ); diff --git a/src/ranged.cpp b/src/ranged.cpp index 99e8783e52662..3e64cb11cc486 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -221,6 +221,12 @@ class target_ui // Returns 'false' if cursor position did not change bool set_cursor_pos( player &pc, const tripoint &new_pos ); + // Updates 'targets' and tries to find something to aim at. + // Validates pc.last_target and pc.last_target_pos. + // Sets 'new_dst' as the initial aiming point. + // Returns 'true' if we can proceed with aim-and-shoot. + bool init_targeting( player &pc, tripoint &new_dst ); + // Update 'status' variable void update_status(); @@ -819,21 +825,20 @@ int player::fire_gun( const tripoint &target, int shots, item &gun ) for( item *mod : gun.gunmods() ) { mod->set_var( "shot_counter", mod->get_var( "shot_counter", 0 ) + curshot ); } - // apply delayed recoil - recoil += delay; - if( is_mech_weapon ) { - // mechs can handle recoil far better. they are built around their main gun. - recoil = recoil / 2; - } - // Reset aim for bows and other reload-and-shoot weapons. if( gun.has_flag( flag_RELOAD_AND_SHOOT ) ) { + // Reset aim for bows and other reload-and-shoot weapons. recoil = MAX_RECOIL; + } else { + // apply delayed recoil + recoil += delay; + if( is_mech_weapon ) { + // mechs can handle recoil far better. they are built around their main gun. + // TODO: shouldn't this affect only recoil accumulated during this function? + recoil = recoil / 2; + } + // Cap + recoil = std::min( MAX_RECOIL, recoil ); } - // Cap - recoil = std::min( MAX_RECOIL, recoil ); - - // Reset last target pos - last_target_pos = cata::nullopt; // Use different amounts of time depending on the type of gun and our skill moves -= time_to_attack( *this, *gun.type ); @@ -1094,6 +1099,7 @@ dealt_projectile_attack player::throw_item( const tripoint &target, const item & } // Reset last target pos last_target_pos = cata::nullopt; + recoil = MAX_RECOIL; return dealt_attack; } @@ -1407,73 +1413,6 @@ std::vector Character::get_aim_types( const item &gun ) const return aim_types; } -static void update_targets( player &pc, int range, std::vector &targets, int &idx, - const tripoint &src, tripoint &dst ) -{ - targets = pc.get_targetable_creatures( range ); - - // Convert and check last_target_pos is a valid aim point - cata::optional local_last_tgt_pos = cata::nullopt; - if( pc.last_target_pos ) { - local_last_tgt_pos = g->m.getlocal( *pc.last_target_pos ); - if( rl_dist( src, *local_last_tgt_pos ) > range ) { - local_last_tgt_pos = cata::nullopt; - } - } - - if( targets.empty() ) { - idx = -1; - - if( pc.last_target_pos ) { - - if( local_last_tgt_pos ) { - dst = *local_last_tgt_pos; - } - if( ( pc.last_target.expired() || !pc.sees( *pc.last_target.lock() ) ) && - pc.has_activity( ACT_AIM ) ) { - //We lost our target. Stop auto aiming. - pc.cancel_activity(); - } - - } else { - const std::vector adjacent = closest_tripoints_first( dst, range ); - const auto target_spot = std::find_if( adjacent.begin(), adjacent.end(), - [&pc]( const tripoint & pt ) { - return g->m.tr_at( pt ).id == tr_practice_target && pc.sees( pt ); - } ); - - if( target_spot != adjacent.end() ) { - dst = *target_spot; - } - } - return; - } - - std::sort( targets.begin(), targets.end(), [&]( const Creature * lhs, const Creature * rhs ) { - return rl_dist_exact( lhs->pos(), pc.pos() ) < rl_dist_exact( rhs->pos(), pc.pos() ); - } ); - - // TODO: last_target should be member of target_handler - const auto iter = std::find( targets.begin(), targets.end(), pc.last_target.lock().get() ); - - if( iter != targets.end() ) { - idx = std::distance( targets.begin(), iter ); - dst = targets[idx]->pos(); - pc.last_target_pos = cata::nullopt; - } else { - idx = 0; - // No remembered target creature, if we have a remembered aim point, use that. - if( local_last_tgt_pos ) { - dst = *local_last_tgt_pos; - } else { - // If we don't have an aim point either, pick the nearest target. - dst = targets[0]->pos(); - pc.recoil = MAX_RECOIL; - } - pc.last_target.reset(); - } -} - static projectile make_gun_projectile( const item &gun ) { projectile proj; @@ -1947,9 +1886,11 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) // Initialize cursor position src = pc.pos(); - tripoint initial_dst = pc.pos(); - int _target_idx = 0; - update_targets( pc, range, targets, _target_idx, src, initial_dst ); + tripoint initial_dst = src; + if( !init_targeting( pc, initial_dst ) ) { + // We've lost our target + action.clear(); + } set_cursor_pos( pc, initial_dst ); ExitCode loop_exit_code; @@ -2238,6 +2179,80 @@ bool target_ui::set_cursor_pos( player &pc, const tripoint &new_pos ) return true; } +bool target_ui::init_targeting( player &pc, tripoint &new_dst ) +{ + // Get targets in range and sort them by distance (targets[0] is the closest) + // FIXME: get_targetable_creatures does not consider some of the visible creatures + // as targets (e.g. those behind fences), but you can still see and shoot them + targets = pc.get_targetable_creatures( range ); + std::sort( targets.begin(), targets.end(), [&]( const Creature * lhs, const Creature * rhs ) { + return rl_dist_exact( lhs->pos(), pc.pos() ) < rl_dist_exact( rhs->pos(), pc.pos() ); + } ); + + // Determine if we had a target and it is still visible + const auto old_target = std::find( targets.begin(), targets.end(), pc.last_target.lock().get() ); + if( old_target == targets.end() ) { + // No luck + pc.last_target.reset(); + } else { + // There it is! + new_dst = ( *old_target )->pos(); + pc.last_target_pos = g->m.getabs( new_dst ); + std::cout << "Using old target" << std::endl; + return true; + } + + // Check if we were aiming at a tile or a (now missing) creature in a tile + // and still can aim at that tile. + cata::optional local_last_tgt_pos = cata::nullopt; + if( pc.last_target_pos ) { + tripoint local = g->m.getlocal( *pc.last_target_pos ); + if( dist_fn( local ) > range ) { + // No luck + pc.last_target_pos = cata::nullopt; + } else { + local_last_tgt_pos = local; + } + } + if( mode == TARGET_MODE_FIRE && pc.recoil == MAX_RECOIL ) { + // We've either moved away, used a bow or a gun with MASSIVE recoil. It doesn't really matter + // where we were aiming at, might as well start from scratch. + pc.last_target_pos = cata::nullopt; + local_last_tgt_pos = cata::nullopt; + } + if( local_last_tgt_pos ) { + new_dst = *local_last_tgt_pos; + std::cout << "Using old aim point" << std::endl; + return false; + } + + // Try to find at least something + if( targets.empty() ) { + // The closest practice target + const std::vector nearby = closest_tripoints_first( src, range ); + const auto target_spot = std::find_if( nearby.begin(), nearby.end(), + [&pc]( const tripoint & pt ) { + return g->m.tr_at( pt ).id == tr_practice_target && pc.sees( pt ); + } ); + + if( target_spot != nearby.end() ) { + new_dst = *target_spot; + std::cout << "Using nearby practice target" << std::endl; + return false; + } + } else { + // The closest living target + new_dst = targets[0]->pos(); + std::cout << "Using closest living target" << std::endl; + return false; + } + + // We've got nothing. + new_dst = src; + std::cout << "No target found" << std::endl; + return false; +} + void target_ui::update_status() { if( mode == TARGET_MODE_TURRET && turrets_in_range.empty() ) { @@ -2359,9 +2374,6 @@ void target_ui::recalc_aim_turning_penalty( player &pc ) curr_recoil_pos = g->m.getlocal( *pc.last_target_pos ); } else { curr_recoil_pos = src; - // TODO: last_target and last_target_pos are both set to null when target dies, - // making automatic weapons not as efficient against groups of enemies as they should be - // (since you need to re-aim your gun from zero for every Zed). } if( curr_recoil_pos == dst ) { From 7bb50206300ae8668b2ba7f51aaad834fdaa1b25 Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Mon, 20 Apr 2020 23:41:05 +0300 Subject: [PATCH 21/43] Remove old comments --- src/ranged.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index 3e64cb11cc486..a510216f04a9c 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -1897,7 +1897,6 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) std::string timed_out_action; bool skip_redraw = false; for( ;; action.clear() ) { - // Old drawing if( !skip_redraw ) { draw( pc ); } @@ -2818,7 +2817,7 @@ void target_ui::panel_target_info( player &pc, int &text_y ) void target_ui::panel_fire_mode_aim( player &pc, int &text_y ) { - // These 2 lines here keep the good ol' code working during the trying times of refactoring + // TODO: saving & restoring pc.recoil may actually be unnecessary double saved_pc_recoil = pc.recoil; pc.recoil = predicted_recoil; @@ -2848,7 +2847,6 @@ void target_ui::panel_fire_mode_aim( player &pc, int &text_y ) predicted_delay ); } - // End of old code compatibility pc.recoil = saved_pc_recoil; } From 46f2157465acfeae0fb28871073566f1b5b026d3 Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Tue, 21 Apr 2020 00:21:36 +0300 Subject: [PATCH 22/43] Extract window creation and input context setup into function --- src/ranged.cpp | 134 ++++++++++++++++++++++++++----------------------- 1 file changed, 71 insertions(+), 63 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index a510216f04a9c..d118f23a430a4 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -211,6 +211,9 @@ class target_ui // List of vehicle turrets in range (out of those listed in 'vturrets') std::vector turrets_in_range; + // Create window and set up input context + void init_window_and_input( player &pc ); + // Handle input related to cursor movement. // Returns 'true' if action was recognized and processed. // 'skip_redraw' is set to 'true' if there is no need to redraw the UI. @@ -1791,6 +1794,8 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) if( mode == TARGET_MODE_SPELL && !no_mana && !casting->can_cast( pc ) ) { pc.add_msg_if_player( m_bad, _( "You don't have enough %s to cast this spell" ), casting->energy_string() ); + } else if( mode == TARGET_MODE_FIRE ) { + sight_dispersion = pc.effective_dispersion( relevant->sight_dispersion() ); } // Load settings @@ -1798,69 +1803,7 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) snap_to_target = get_option( "SNAP_TO_TARGET" ); // Create window - compact = TERMY < 41; - tiny = TERMY < 32; - int top = 0; - int width = 55; - int height; - if( tiny ) { - // If we're extremely short on space, use the whole sidebar. - height = TERMY; - } else if( compact ) { - // Cover up more low-value ui elements if we're tight on space. - height = 28; - } else { - // Go all out - height = 32; - } - w_target = catacurses::newwin( height, width, point( TERMX - width, top ) ); - - ctxt = input_context( "TARGET" ); - ctxt.set_iso( true ); - // "ANY_INPUT" should be added before any real help strings - // Or strings will be written on window border. - ctxt.register_action( "ANY_INPUT" ); - ctxt.register_directions(); - ctxt.register_action( "COORDINATE" ); - ctxt.register_action( "SELECT" ); - ctxt.register_action( "FIRE" ); - ctxt.register_action( "NEXT_TARGET" ); - ctxt.register_action( "PREV_TARGET" ); - ctxt.register_action( "CENTER" ); - ctxt.register_action( "TOGGLE_SNAP_TO_TARGET" ); - ctxt.register_action( "HELP_KEYBINDINGS" ); - ctxt.register_action( "QUIT" ); - ctxt.register_action( "MOUSE_MOVE" ); - ctxt.register_action( "zoom_out" ); - ctxt.register_action( "zoom_in" ); - if( allow_zlevel_shift ) { - ctxt.register_action( "LEVEL_UP" ); - ctxt.register_action( "LEVEL_DOWN" ); - } - if( mode == TARGET_MODE_FIRE || mode == TARGET_MODE_TURRET_MANUAL ) { - ctxt.register_action( "SWITCH_MODE" ); - ctxt.register_action( "SWITCH_AMMO" ); - } - if( mode == TARGET_MODE_FIRE ) { - ctxt.register_action( "AIM" ); - ctxt.register_action( "SWITCH_AIM" ); - } - - if( mode == TARGET_MODE_FIRE ) { - sight_dispersion = pc.effective_dispersion( relevant->sight_dispersion() ); - - aim_types = pc.get_aim_types( *relevant ); - for( aim_type &type : aim_types ) { - if( type.has_threshold ) { - ctxt.register_action( type.action ); - } - } - - aim_mode = aim_types.begin(); - } - - // FIXME: temporarily disable redrawing of lower UIs before this UI is migrated to `ui_adaptor` - ui_adaptor ui( ui_adaptor::disable_uis_below {} ); + init_window_and_input( pc ); // Handle multi-turn aiming std::string action; @@ -1893,6 +1836,7 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) } set_cursor_pos( pc, initial_dst ); + // Event loop! ExitCode loop_exit_code; std::string timed_out_action; bool skip_redraw = false; @@ -2018,6 +1962,70 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) return traj; } +void target_ui::init_window_and_input( player &pc ) +{ + compact = TERMY < 41; + tiny = TERMY < 32; + int top = 0; + int width = 55; + int height; + if( tiny ) { + // If we're extremely short on space, use the whole sidebar. + height = TERMY; + } else if( compact ) { + // Cover up more low-value ui elements if we're tight on space. + height = 28; + } else { + // Go all out + height = 32; + } + w_target = catacurses::newwin( height, width, point( TERMX - width, top ) ); + + ctxt = input_context( "TARGET" ); + ctxt.set_iso( true ); + // "ANY_INPUT" should be added before any real help strings + // Or strings will be written on window border. + ctxt.register_action( "ANY_INPUT" ); + ctxt.register_directions(); + ctxt.register_action( "COORDINATE" ); + ctxt.register_action( "SELECT" ); + ctxt.register_action( "FIRE" ); + ctxt.register_action( "NEXT_TARGET" ); + ctxt.register_action( "PREV_TARGET" ); + ctxt.register_action( "CENTER" ); + ctxt.register_action( "TOGGLE_SNAP_TO_TARGET" ); + ctxt.register_action( "HELP_KEYBINDINGS" ); + ctxt.register_action( "QUIT" ); + ctxt.register_action( "MOUSE_MOVE" ); + ctxt.register_action( "zoom_out" ); + ctxt.register_action( "zoom_in" ); + if( allow_zlevel_shift ) { + ctxt.register_action( "LEVEL_UP" ); + ctxt.register_action( "LEVEL_DOWN" ); + } + if( mode == TARGET_MODE_FIRE || mode == TARGET_MODE_TURRET_MANUAL ) { + ctxt.register_action( "SWITCH_MODE" ); + ctxt.register_action( "SWITCH_AMMO" ); + } + if( mode == TARGET_MODE_FIRE ) { + ctxt.register_action( "AIM" ); + ctxt.register_action( "SWITCH_AIM" ); + } + + if( mode == TARGET_MODE_FIRE ) { + aim_types = pc.get_aim_types( *relevant ); + for( aim_type &type : aim_types ) { + if( type.has_threshold ) { + ctxt.register_action( type.action ); + } + } + aim_mode = aim_types.begin(); + } + + // FIXME: temporarily disable redrawing of lower UIs before this UI is migrated to `ui_adaptor` + ui_adaptor ui( ui_adaptor::disable_uis_below {} ); +} + bool target_ui::handle_cursor_movement( player &pc, const std::string &action, bool &skip_redraw ) { cata::optional mouse_pos; From eca972b88fd5287e67316d8ece5bdfde41932307 Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Tue, 21 Apr 2020 00:21:43 +0300 Subject: [PATCH 23/43] Replace enum TARGET_MODE_* with enum class TargetMode --- src/ranged.cpp | 122 +++++++++++++++++++++++++------------------------ 1 file changed, 62 insertions(+), 60 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index d118f23a430a4..7899bfcf41d4f 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -103,23 +103,23 @@ int time_to_attack( const Character &p, const itype &firing ); static void cycle_action( item &weap, const tripoint &pos ); void make_gun_sound_effect( const player &p, bool burst, item *weapon ); -enum target_mode : int { - TARGET_MODE_FIRE, - TARGET_MODE_THROW, - TARGET_MODE_TURRET, - TARGET_MODE_TURRET_MANUAL, - TARGET_MODE_REACH, - TARGET_MODE_THROW_BLIND, - TARGET_MODE_SPELL -}; - class target_ui { public: /* None of the public members (except ammo and range) should be modified during execution */ + enum class TargetMode { + Fire, + Throw, + ThrowBlind, + Turrets, + TurretManual, + Reach, + Spell + }; + // Interface mode - target_mode mode = TARGET_MODE_FIRE; + TargetMode mode = TargetMode::Fire; // Weapon being fired/thrown item *relevant = nullptr; // Cached selection range from player's position @@ -190,7 +190,7 @@ class target_ui // Input context input_context ctxt; - /* These members are relevant for TARGET_MODE_FIRE */ + /* These members are relevant for TargetMode::Fire */ // Weapon sight dispersion int sight_dispersion = 0; // List of available weapon aim types @@ -205,7 +205,7 @@ class target_ui double predicted_recoil; // For AOE spells, list of tiles affected by the spell - // relevant for TARGET_MODE_SPELL + // relevant for TargetMode::Spell std::set spell_aoe; // List of vehicle turrets in range (out of those listed in 'vturrets') @@ -256,12 +256,12 @@ class target_ui // Recalculate 'recoil' penalty. This should be called if // player's 'recoil' value has been modified - // Relevant for TARGET_MODE_FIRE + // Relevant for TargetMode::Fire void recalc_aim_turning_penalty( player &pc ); // Apply penalty to player's 'recoil' value based on // how much they moved their aim point. - // Relevant for TARGET_MODE_FIRE + // Relevant for TargetMode::Fire void apply_aim_turning_penalty( player &pc ); // Switch firing mode. @@ -314,7 +314,7 @@ target_handler::trajectory target_handler::mode_fire( player &pc, item &weapon, bool &reload_requested ) { target_ui ui = target_ui(); - ui.mode = TARGET_MODE_FIRE; + ui.mode = target_ui::TargetMode::Fire; ui.relevant = &weapon; gun_mode gun = weapon.gun_current_mode(); ui.range = gun.target->gun_range( &pc ); @@ -330,7 +330,7 @@ target_handler::trajectory target_handler::mode_throw( player &pc, item &relevan bool blind_throwing ) { target_ui ui = target_ui(); - ui.mode = blind_throwing ? TARGET_MODE_THROW_BLIND : TARGET_MODE_THROW; + ui.mode = blind_throwing ? target_ui::TargetMode::ThrowBlind : target_ui::TargetMode::Throw; ui.relevant = &relevant; ui.range = pc.throw_range( relevant ); @@ -340,7 +340,7 @@ target_handler::trajectory target_handler::mode_throw( player &pc, item &relevan target_handler::trajectory target_handler::mode_reach( player &pc, item &weapon ) { target_ui ui = target_ui(); - ui.mode = TARGET_MODE_REACH; + ui.mode = target_ui::TargetMode::Reach; ui.relevant = &weapon; ui.range = weapon.current_reach_range( pc ); @@ -350,7 +350,7 @@ target_handler::trajectory target_handler::mode_reach( player &pc, item &weapon target_handler::trajectory target_handler::mode_turret_manual( player &pc, turret_data &turret ) { target_ui ui = target_ui(); - ui.mode = TARGET_MODE_TURRET_MANUAL; + ui.mode = target_ui::TargetMode::TurretManual; ui.turret = &turret; ui.relevant = &*turret.base(); ui.range = turret.range(); @@ -379,7 +379,7 @@ target_handler::trajectory target_handler::mode_turrets( player &pc, vehicle &ve } target_ui ui = target_ui(); - ui.mode = TARGET_MODE_TURRET; + ui.mode = target_ui::TargetMode::Turrets; ui.veh = &veh; ui.vturrets = &turrets; ui.range = range_total; @@ -391,7 +391,7 @@ target_handler::trajectory target_handler::mode_spell( player &pc, spell &castin bool no_mana ) { target_ui ui = target_ui(); - ui.mode = TARGET_MODE_SPELL; + ui.mode = target_ui::TargetMode::Spell; ui.casting = &casting; ui.range = casting.range(); ui.no_fail = no_fail; @@ -1202,7 +1202,7 @@ static std::string get_colored_bar( const double val, const int width, const std } static int print_ranged_chance( const player &p, const catacurses::window &w, int line_number, - target_mode mode, input_context &ctxt, const item &ranged_weapon, + target_ui::TargetMode mode, input_context &ctxt, const item &ranged_weapon, const dispersion_sources &dispersion, const std::vector &confidence_config, double range, double target_size, int recoil = 0 ) { @@ -1212,7 +1212,7 @@ static int print_ranged_chance( const player &p, const catacurses::window &w, in nc_color col = c_dark_gray; std::vector aim_types; - if( mode == TARGET_MODE_THROW || mode == TARGET_MODE_THROW_BLIND ) { + if( mode == target_ui::TargetMode::Throw || mode == target_ui::TargetMode::ThrowBlind ) { aim_types = get_default_aim_type(); } else { aim_types = p.get_aim_types( ranged_weapon ); @@ -1246,7 +1246,7 @@ static int print_ranged_chance( const player &p, const catacurses::window &w, in } int moves_to_fire; - if( mode == TARGET_MODE_THROW || mode == TARGET_MODE_THROW_BLIND ) { + if( mode == target_ui::TargetMode::Throw || mode == target_ui::TargetMode::ThrowBlind ) { moves_to_fire = throw_cost( p, ranged_weapon ); } else { moves_to_fire = p.gun_engagement_moves( ranged_weapon, threshold, recoil ) + time_to_attack( p, @@ -1338,7 +1338,8 @@ static int print_aim( const player &p, const catacurses::window &w, int line_num const double range = rl_dist( p.pos(), pos ); line_number = print_steadiness( w, line_number, steadiness ); - return print_ranged_chance( p, w, line_number, TARGET_MODE_FIRE, ctxt, *weapon, dispersion, + return print_ranged_chance( p, w, line_number, target_ui::TargetMode::Fire, ctxt, *weapon, + dispersion, confidence_config, range, target_size, predicted_recoil ); } @@ -1370,8 +1371,9 @@ static int draw_throw_aim( const player &p, const catacurses::window &w, int lin const auto &confidence_config = target != nullptr ? confidence_config_critter : confidence_config_object; - const target_mode throwing_target_mode = is_blind_throw ? TARGET_MODE_THROW_BLIND : - TARGET_MODE_THROW; + const target_ui::TargetMode throwing_target_mode = is_blind_throw ? + target_ui::TargetMode::ThrowBlind : + target_ui::TargetMode::Throw; return print_ranged_chance( p, w, line_number, throwing_target_mode, ctxt, weapon, dispersion, confidence_config, range, target_size ); @@ -1791,10 +1793,10 @@ double player::gun_value( const item &weap, int ammo ) const target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) { - if( mode == TARGET_MODE_SPELL && !no_mana && !casting->can_cast( 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" ), casting->energy_string() ); - } else if( mode == TARGET_MODE_FIRE ) { + } else if( mode == TargetMode::Fire ) { sight_dispersion = pc.effective_dispersion( relevant->sight_dispersion() ); } @@ -1807,7 +1809,7 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) // Handle multi-turn aiming std::string action; - if( mode == TARGET_MODE_FIRE ) { + if( mode == TargetMode::Fire ) { if( pc.activity.id() == ACT_AIM ) { // We were in this UI during previous turn... std::string act_data = pc.activity.str_values[0]; @@ -1853,7 +1855,7 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) } // If an aiming mode is selected, use "*_SHOT" instead of "FIRE" - if( mode == TARGET_MODE_FIRE && action == "FIRE" && aim_mode->has_threshold ) { + if( mode == TargetMode::Fire && action == "FIRE" && aim_mode->has_threshold ) { action = aim_mode->action; } @@ -1885,7 +1887,7 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) if( status != Status::Good ) { continue; } - bool can_skip_confirm = ( mode == TARGET_MODE_SPELL && casting->damage() <= 0 ); + bool can_skip_confirm = ( mode == TargetMode::Spell && casting->damage() <= 0 ); if( !can_skip_confirm && !confirm_non_enemy_target() ) { continue; } @@ -2003,16 +2005,16 @@ void target_ui::init_window_and_input( player &pc ) ctxt.register_action( "LEVEL_UP" ); ctxt.register_action( "LEVEL_DOWN" ); } - if( mode == TARGET_MODE_FIRE || mode == TARGET_MODE_TURRET_MANUAL ) { + if( mode == TargetMode::Fire || mode == TargetMode::TurretManual ) { ctxt.register_action( "SWITCH_MODE" ); ctxt.register_action( "SWITCH_AMMO" ); } - if( mode == TARGET_MODE_FIRE ) { + if( mode == TargetMode::Fire ) { ctxt.register_action( "AIM" ); ctxt.register_action( "SWITCH_AIM" ); } - if( mode == TARGET_MODE_FIRE ) { + if( mode == TargetMode::Fire ) { aim_types = pc.get_aim_types( *relevant ); for( aim_type &type : aim_types ) { if( type.has_threshold ) { @@ -2163,9 +2165,9 @@ bool target_ui::set_cursor_pos( player &pc, const tripoint &new_pos ) } // Update mode-specific stuff - if( mode == TARGET_MODE_FIRE ) { + if( mode == TargetMode::Fire ) { recalc_aim_turning_penalty( pc ); - } else if( mode == TARGET_MODE_SPELL ) { + } else if( mode == TargetMode::Spell ) { const std::string fx = casting->effect(); if( fx == "target_attack" || fx == "projectile_attack" || fx == "ter_transform" ) { spell_aoe = spell_effect::spell_effect_blast( *casting, src, dst, casting->aoe(), true ); @@ -2176,7 +2178,7 @@ bool target_ui::set_cursor_pos( player &pc, const tripoint &new_pos ) } else { spell_aoe.clear(); } - } else if( mode == TARGET_MODE_TURRET ) { + } else if( mode == TargetMode::Turrets ) { update_turrets_in_range(); } @@ -2221,7 +2223,7 @@ bool target_ui::init_targeting( player &pc, tripoint &new_dst ) local_last_tgt_pos = local; } } - if( mode == TARGET_MODE_FIRE && pc.recoil == MAX_RECOIL ) { + if( mode == TargetMode::Fire && pc.recoil == MAX_RECOIL ) { // We've either moved away, used a bow or a gun with MASSIVE recoil. It doesn't really matter // where we were aiming at, might as well start from scratch. pc.last_target_pos = cata::nullopt; @@ -2262,10 +2264,10 @@ bool target_ui::init_targeting( player &pc, tripoint &new_dst ) void target_ui::update_status() { - if( mode == TARGET_MODE_TURRET && turrets_in_range.empty() ) { + if( mode == TargetMode::Turrets && turrets_in_range.empty() ) { // None of the turrets are in range status = Status::OutOfRange; - } else if( ( mode == TARGET_MODE_FIRE || mode == TARGET_MODE_TURRET_MANUAL ) && range == 0 ) { + } else if( ( mode == TargetMode::Fire || mode == TargetMode::TurretManual ) && range == 0 ) { // Selected gun mode is empty status = Status::OutOfAmmo; } else if( src == dst ) { @@ -2412,7 +2414,7 @@ void target_ui::action_switch_mode( player &pc ) if( relevant->gun_current_mode().flags.count( "REACH_ATTACK" ) ) { relevant->gun_cycle_mode(); } - if( mode == TARGET_MODE_TURRET_MANUAL ) { + if( mode == TargetMode::TurretManual ) { itype_id ammo_current = turret->ammo_current(); if( ammo_current == "null" ) { ammo = nullptr; @@ -2430,7 +2432,7 @@ void target_ui::action_switch_mode( player &pc ) bool target_ui::action_switch_ammo() { - if( mode == TARGET_MODE_TURRET_MANUAL ) { + if( mode == TargetMode::TurretManual ) { // For turrets that use vehicle tanks & can fire multiple liquids if( turret->ammo_options().size() > 1 ) { const auto opts = turret->ammo_options(); @@ -2534,7 +2536,7 @@ void target_ui::draw_terrain( player &pc ) } // Draw spell AOE - if( mode == TARGET_MODE_SPELL ) { + if( mode == TargetMode::Spell ) { for( const tripoint &tile : spell_aoe ) { if( tile.z != center.z ) { continue; @@ -2566,11 +2568,11 @@ void target_ui::draw_ui_window( player &pc ) panel_cursor_info( text_y ); text_y += compact ? 0 : 1; - if( mode == TARGET_MODE_FIRE || mode == TARGET_MODE_TURRET_MANUAL ) { + if( mode == TargetMode::Fire || mode == TargetMode::TurretManual ) { panel_gun_info( text_y ); panel_recoil( pc, text_y ); text_y += compact ? 0 : 1; - } else if( mode == TARGET_MODE_SPELL ) { + } else if( mode == TargetMode::Spell ) { panel_spell_info( pc, text_y ); text_y += compact ? 0 : 1; } @@ -2578,14 +2580,14 @@ void target_ui::draw_ui_window( player &pc ) panel_target_info( pc, text_y ); text_y += compact ? 0 : 1; - if( mode == TARGET_MODE_TURRET ) { + if( mode == TargetMode::Turrets ) { panel_turret_list( text_y ); } else if( status == Status::Good ) { // TODO: these are old, consider refactoring - if( mode == TARGET_MODE_FIRE ) { + if( mode == TargetMode::Fire ) { panel_fire_mode_aim( pc, text_y ); - } else if( mode == TARGET_MODE_THROW || mode == TARGET_MODE_THROW_BLIND ) { - bool blind = ( mode == TARGET_MODE_THROW_BLIND ); + } else if( mode == TargetMode::Throw || mode == TargetMode::ThrowBlind ) { + bool blind = ( mode == TargetMode::ThrowBlind ); draw_throw_aim( pc, w_target, text_y, ctxt, *relevant, dst, blind ); } } @@ -2596,12 +2598,12 @@ void target_ui::draw_ui_window( player &pc ) std::string target_ui::uitext_title() { switch( mode ) { - case TARGET_MODE_FIRE: - case TARGET_MODE_TURRET_MANUAL: + case TargetMode::Fire: + case TargetMode::TurretManual: return string_format( _( "Firing %s" ), relevant->tname() ); - case TARGET_MODE_THROW: + case TargetMode::Throw: return string_format( _( "Throwing %s" ), relevant->tname() ); - case TARGET_MODE_THROW_BLIND: + case TargetMode::ThrowBlind: return string_format( _( "Blind throwing %s" ), relevant->tname() ); default: return _( "Set target" ); @@ -2610,11 +2612,11 @@ std::string target_ui::uitext_title() std::string target_ui::uitext_fire() { - if( mode == TARGET_MODE_THROW || mode == TARGET_MODE_THROW_BLIND ) { + if( mode == TargetMode::Throw || mode == TargetMode::ThrowBlind ) { return to_translation( "[Hotkey] to throw", "to throw" ).translated(); - } else if( mode == TARGET_MODE_REACH ) { + } else if( mode == TargetMode::Reach ) { return to_translation( "[Hotkey] to attack", "to attack" ).translated(); - } else if( mode == TARGET_MODE_SPELL ) { + } else if( mode == TargetMode::Spell ) { return to_translation( "[Hotkey] to cast the spell", "to cast" ).translated(); } else { return to_translation( "[Hotkey] to fire", "to fire" ).translated(); @@ -2649,13 +2651,13 @@ int target_ui::draw_controls_list() mvwprintz( w_target, point( 1, text_y ), move_color, label_mouse ); mvwprintz( w_target, point( text_x, text_y-- ), fire_color, _( "RMB: Fire" ) ); } - if( mode == TARGET_MODE_FIRE || mode == TARGET_MODE_TURRET_MANUAL ) { + if( mode == TargetMode::Fire || mode == TargetMode::TurretManual ) { mvwprintz( w_target, point( 1, text_y-- ), c_white, _( "[%c] to reload/switch ammo." ), bound_key( "SWITCH_AMMO" ) ); mvwprintz( w_target, point( 1, text_y-- ), c_white, _( "[%c] to switch firing modes." ), bound_key( "SWITCH_MODE" ) ); } - if( mode == TARGET_MODE_FIRE ) { + if( mode == TargetMode::Fire ) { std::string aim_and_fire; for( const auto &e : aim_types ) { if( e.has_threshold ) { @@ -2692,7 +2694,7 @@ void target_ui::panel_cursor_info( int &text_y ) } else { label_range = string_format( "Range: %d/%d", dist_fn( dst ), range ); } - if( status == Status::OutOfRange && mode != TARGET_MODE_TURRET ) { + if( status == Status::OutOfRange && mode != TargetMode::Turrets ) { // Since each turret has its own range, highlighting cursor // range with red is misleading label_range = colorize( label_range, c_red ); From f3948406109cf3d71ad9414387ff40819a438317 Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Tue, 21 Apr 2020 00:44:58 +0300 Subject: [PATCH 24/43] Show info about creatures only if player can see them --- src/ranged.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index 7899bfcf41d4f..4833082e39cb3 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -169,8 +169,8 @@ class target_ui // Aiming destination (cursor position) // Use set_cursor_pos() to modify tripoint dst; - // Creature currently under cursor - // nullptr if aiming at empty tile or yourself + // Creature currently under cursor. nullptr if aiming at empty tile, + // yourself or a creature you cannot see Creature *dst_critter = nullptr; // List of visible hostile targets std::vector targets; @@ -301,7 +301,7 @@ class target_ui void panel_gun_info( int &text_y ); void panel_recoil( player &pc, int &text_y ); void panel_spell_info( player &pc, int &text_y ); - void panel_target_info( player &pc, int &text_y ); + void panel_target_info( int &text_y ); void panel_fire_mode_aim( player &pc, int &text_y ); void panel_turret_list( int &text_y ); @@ -2159,7 +2159,12 @@ bool target_ui::set_cursor_pos( player &pc, const tripoint &new_pos ) // Cache creature under cursor if( src != dst ) { - dst_critter = g->critter_at( dst, true ); + Creature *cr = g->critter_at( dst, true ); + if( cr && ( pc.sees( *cr ) || pc.sees_with_infrared( *cr ) ) ) { + dst_critter = cr; + } else { + dst_critter = nullptr; + } } else { dst_critter = nullptr; } @@ -2577,7 +2582,7 @@ void target_ui::draw_ui_window( player &pc ) text_y += compact ? 0 : 1; } - panel_target_info( pc, text_y ); + panel_target_info( text_y ); text_y += compact ? 0 : 1; if( mode == TargetMode::Turrets ) { @@ -2807,10 +2812,10 @@ void target_ui::panel_spell_info( player &pc, int &text_y ) casting->description() ); } -void target_ui::panel_target_info( player &pc, int &text_y ) +void target_ui::panel_target_info( int &text_y ) { int max_lines = 4; - if( dst_critter && pc.sees( *dst_critter ) ) { + if( dst_critter ) { // FIXME: print_info doesn't really care about line limit // and can always occupy up to 4 of them (or even more?). // To make things consistent, we ask it for 2 lines From dc93ba3b07a945cbd8ba8df278a211cb8b15d59a Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Tue, 21 Apr 2020 17:59:10 +0300 Subject: [PATCH 25/43] Update list of targets when range changes --- src/ranged.cpp | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index 4ba2b03c08de5..7464129315606 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -224,11 +224,17 @@ class target_ui // Returns 'false' if cursor position did not change bool set_cursor_pos( player &pc, const tripoint &new_pos ); - // Updates 'targets' and tries to find something to aim at. + // Called when range/ammo changes (or may have changed) + void on_range_ammo_changed( player &pc ); + + // Updates 'targets' for current range + void update_target_list( player &pc ); + + // Tries to find something to aim at. // Validates pc.last_target and pc.last_target_pos. // Sets 'new_dst' as the initial aiming point. // Returns 'true' if we can proceed with aim-and-shoot. - bool init_targeting( player &pc, tripoint &new_dst ); + bool choose_initial_target( player &pc, tripoint &new_dst ); // Update 'status' variable void update_status(); @@ -268,7 +274,7 @@ class target_ui void action_switch_mode( player &pc ); // Switch ammo. Returns 'false' if requires a reloading UI. - bool action_switch_ammo(); + bool action_switch_ammo( player &pc ); // Aim for 10 turns. Returns 'false' if ran out of moves bool action_aim( player &pc ); @@ -1832,8 +1838,9 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) // Initialize cursor position src = pc.pos(); tripoint initial_dst = src; - if( !init_targeting( pc, initial_dst ) ) { - // We've lost our target + update_target_list( pc ); + if( !choose_initial_target( pc, initial_dst ) ) { + // We've lost our target from previous turn action.clear(); } set_cursor_pos( pc, initial_dst ); @@ -1874,7 +1881,7 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) } else if( action == "SWITCH_MODE" ) { action_switch_mode( pc ); } else if( action == "SWITCH_AMMO" ) { - if( !action_switch_ammo() ) { + if( !action_switch_ammo( pc ) ) { loop_exit_code = ExitCode::Reload; break; } @@ -2193,8 +2200,19 @@ bool target_ui::set_cursor_pos( player &pc, const tripoint &new_pos ) return true; } -bool target_ui::init_targeting( player &pc, tripoint &new_dst ) +void target_ui::on_range_ammo_changed( player &pc ) { + update_status(); + update_target_list( pc ); +} + +void target_ui::update_target_list( player &pc ) +{ + if( range == 0 ) { + targets.clear(); + return; + } + // Get targets in range and sort them by distance (targets[0] is the closest) // FIXME: get_targetable_creatures does not consider some of the visible creatures // as targets (e.g. those behind fences), but you can still see and shoot them @@ -2202,7 +2220,10 @@ bool target_ui::init_targeting( player &pc, tripoint &new_dst ) std::sort( targets.begin(), targets.end(), [&]( const Creature * lhs, const Creature * rhs ) { return rl_dist_exact( lhs->pos(), pc.pos() ) < rl_dist_exact( rhs->pos(), pc.pos() ); } ); +} +bool target_ui::choose_initial_target( player &pc, tripoint &new_dst ) +{ // Determine if we had a target and it is still visible const auto old_target = std::find( targets.begin(), targets.end(), pc.last_target.lock().get() ); if( old_target == targets.end() ) { @@ -2432,10 +2453,10 @@ void target_ui::action_switch_mode( player &pc ) ammo = relevant->gun_current_mode().target->ammo_data(); range = relevant->gun_current_mode().target->gun_range( &pc ); } - update_status(); + on_range_ammo_changed( pc ); } -bool target_ui::action_switch_ammo() +bool target_ui::action_switch_ammo( player &pc ) { if( mode == TargetMode::TurretManual ) { // For turrets that use vehicle tanks & can fire multiple liquids @@ -2451,7 +2472,7 @@ bool target_ui::action_switch_ammo() // reloading annihilates our aim anyway return false; } - update_status(); + on_range_ammo_changed( pc ); return true; } From 82342c39b38931db78cac085c66455934bdc3318 Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Tue, 21 Apr 2020 18:12:17 +0300 Subject: [PATCH 26/43] Prevent an exploit when firing from monster list pc.recoil is tied to pc.last_target, so repeatedly firing from monster list by setting pc.last_target will bypass the penalty for swinging the gun around, allowing 180 no-scope --- src/game.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/game.cpp b/src/game.cpp index 36a3080f02526..a3039c59118ab 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -7641,6 +7641,7 @@ game::vmenu_ret game::list_monsters( const std::vector &monster_list } else if( action == "fire" ) { if( cCurMon != nullptr && rl_dist( u.pos(), cCurMon->pos() ) <= max_gun_range ) { u.last_target = shared_from( *cCurMon ); + u.recoil = MAX_RECOIL; u.view_offset = stored_view_offset; return game::vmenu_ret::FIRE; } From 5907ca05c321d9d4e37a401a8d2e88959fe0ecc9 Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Tue, 21 Apr 2020 18:26:23 +0300 Subject: [PATCH 27/43] Remove debug output --- src/ranged.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index 7464129315606..08493db5f8b5a 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -2233,7 +2233,6 @@ bool target_ui::choose_initial_target( player &pc, tripoint &new_dst ) // There it is! new_dst = ( *old_target )->pos(); pc.last_target_pos = g->m.getabs( new_dst ); - std::cout << "Using old target" << std::endl; return true; } @@ -2257,7 +2256,6 @@ bool target_ui::choose_initial_target( player &pc, tripoint &new_dst ) } if( local_last_tgt_pos ) { new_dst = *local_last_tgt_pos; - std::cout << "Using old aim point" << std::endl; return false; } @@ -2272,19 +2270,16 @@ bool target_ui::choose_initial_target( player &pc, tripoint &new_dst ) if( target_spot != nearby.end() ) { new_dst = *target_spot; - std::cout << "Using nearby practice target" << std::endl; return false; } } else { // The closest living target new_dst = targets[0]->pos(); - std::cout << "Using closest living target" << std::endl; return false; } // We've got nothing. new_dst = src; - std::cout << "No target found" << std::endl; return false; } From ca0ea7c59b7360b4f06356514f2ee937a8cfd395 Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Tue, 21 Apr 2020 18:42:01 +0300 Subject: [PATCH 28/43] Disable switching aim mode when you can't aim/shoot --- src/ranged.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index 08493db5f8b5a..302fdc06f776e 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -1886,6 +1886,9 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) break; } } else if( action == "SWITCH_AIM" ) { + if( status != Status::Good ) { + continue; + } aim_mode++; if( aim_mode == aim_types.end() ) { aim_mode = aim_types.begin(); @@ -2686,7 +2689,7 @@ int target_ui::draw_controls_list() } } - mvwprintz( w_target, point( 1, text_y-- ), c_white, _( "[%c] to switch aiming modes." ), + mvwprintz( w_target, point( 1, text_y-- ), fire_color, _( "[%c] to switch aiming modes." ), bound_key( "SWITCH_AIM" ) ); mvwprintz( w_target, point( 1, text_y-- ), fire_color, _( "%sto aim and fire" ), aim_and_fire ); mvwprintz( w_target, point( 1, text_y-- ), fire_color, _( "[%c] to steady your aim. (10 moves)" ), From 80097f31be41120d755913045eda35edf3cde822 Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Tue, 21 Apr 2020 19:14:46 +0300 Subject: [PATCH 29/43] For compact version, draw over bottom border --- src/ranged.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index 302fdc06f776e..676fbc2383bc5 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -181,10 +181,8 @@ class target_ui // or temporarily in this window bool snap_to_target; - // Compact layout - slightly smaller then normal + // Compact layout bool compact; - // Tiny layout - uses whole sidebar - bool tiny; // Window catacurses::window w_target; // Input context @@ -1977,7 +1975,7 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) void target_ui::init_window_and_input( player &pc ) { compact = TERMY < 41; - tiny = TERMY < 32; + bool tiny = TERMY < 32; int top = 0; int width = 55; int height; @@ -2668,7 +2666,7 @@ int target_ui::draw_controls_list() // Since this list is of variable length and positioned // at the bottom, we draw everything in reverse order - int text_y = height - ( tiny ? 1 : 2 ); // If we're short on space, draw over bottom border + int text_y = height - ( compact ? 1 : 2 ); // If we're short on space, draw over bottom border if( is_mouse_enabled() ) { const char *label_mouse = "Mouse: LMB: Target, Wheel: Cycle,"; int text_x = utf8_width( label_mouse ) + 2; // '2' for border + space at the end From a6d5eb20591ab9507071a67506c564f2ec2dfe8f Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Tue, 21 Apr 2020 20:12:29 +0300 Subject: [PATCH 30/43] Invalidate aim point when moving --- src/game.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/game.cpp b/src/game.cpp index a3039c59118ab..c554a5c53438d 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -8999,8 +8999,9 @@ bool game::walk_move( const tripoint &dest_loc ) u.burn_move_stamina( 0.50 * ( previous_moves - u.moves ) ); } } - // Max out recoil + // Max out recoil & reset aim point u.recoil = MAX_RECOIL; + u.last_target_pos = cata::nullopt; // Print a message if movement is slow const int mcost_to = m.move_cost( dest_loc ); //calculate this _after_ calling grabbed_move From 0a61cb2518fc756c7113e8fdd6f799662ae9e42a Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Tue, 21 Apr 2020 20:13:12 +0300 Subject: [PATCH 31/43] Fix player's sprite disappearing when changing Z levels --- src/ranged.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index 676fbc2383bc5..b22a1ce4a2058 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -2368,14 +2368,14 @@ void target_ui::cycle_targets( player &pc, int direction ) void target_ui::set_view_offset( player &pc, const tripoint &new_offset ) { - // TODO: player's sprite disappears when shifting view back into player's z level - if( pc.view_offset.z != new_offset.z ) { + bool changed_z = pc.view_offset.z != new_offset.z; + pc.view_offset = new_offset; + if( changed_z ) { // We need to do a bunch of redrawing and cache updates since we're // looking at a different z-level. g->m.invalidate_map_cache( new_offset.z ); g->refresh_all(); } - pc.view_offset = new_offset; } void target_ui::update_turrets_in_range() From 3e8d0920fcc6bed99cdeaa101e8bcbecaea924c5 Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Tue, 21 Apr 2020 22:48:24 +0300 Subject: [PATCH 32/43] Appease clang-tidy --- src/ranged.cpp | 21 ++++++++------------- src/ranged.h | 2 +- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index b22a1ce4a2058..f6d0a08524f7b 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -2507,18 +2507,13 @@ bool target_ui::action_aim_and_shoot( player &pc, const std::string &action ) do_aim( pc, relevant ? *relevant : null_item_reference(), min_recoil ); } while( pc.moves > 0 && pc.recoil > aim_threshold && pc.recoil - sight_dispersion > min_recoil ); - if( pc.recoil <= aim_threshold || - pc.recoil - sight_dispersion == min_recoil || - // if no critter is at dst then sight dispersion does not apply, - // so it would lock into an infinite loop - ( !g->critter_at( dst ) && pc.recoil == min_recoil ) ) { - // If we made it under the aim threshold, go ahead and fire. - // Also fire if we're at our best aim level already. - return true; - } else { - // We've run out of moves - return false; - } + // If we made it under the aim threshold, go ahead and fire. + // Also fire if we're at our best aim level already. + // If no critter is at dst then sight dispersion does not apply, + // so it would lock into an infinite loop. + bool done_aiming = pc.recoil <= aim_threshold || pc.recoil - sight_dispersion == min_recoil || + ( !g->critter_at( dst ) && pc.recoil == min_recoil ); + return done_aiming; } void target_ui::draw( player &pc ) @@ -2731,7 +2726,7 @@ void target_ui::panel_gun_info( int &text_y ) { gun_mode m = relevant->gun_current_mode(); std::string mode_name = m.tname(); - std::string gunmod_name = ""; + std::string gunmod_name; if( m.target != relevant ) { // Gun mode comes from a gunmod, not base gun. Add gunmod's name gunmod_name = m->tname() + " "; diff --git a/src/ranged.h b/src/ranged.h index 874ed34998acc..eae11bfc44485 100644 --- a/src/ranged.h +++ b/src/ranged.h @@ -94,7 +94,7 @@ trajectory mode_turrets( player &pc, vehicle &veh, const std::vector Date: Wed, 22 Apr 2020 15:50:00 +0300 Subject: [PATCH 33/43] Fix incorrect usage of ui_adaptor::disable_uis_below --- src/ranged.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index f6d0a08524f7b..29c656c952c51 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -1810,6 +1810,8 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) // Create window init_window_and_input( pc ); + // FIXME: temporarily disable redrawing of lower UIs before this UI is migrated to `ui_adaptor` + ui_adaptor ui( ui_adaptor::disable_uis_below {} ); // Handle multi-turn aiming std::string action; @@ -2031,9 +2033,6 @@ void target_ui::init_window_and_input( player &pc ) } aim_mode = aim_types.begin(); } - - // FIXME: temporarily disable redrawing of lower UIs before this UI is migrated to `ui_adaptor` - ui_adaptor ui( ui_adaptor::disable_uis_below {} ); } bool target_ui::handle_cursor_movement( player &pc, const std::string &action, bool &skip_redraw ) From 9acbb8b272cd495906a031b71384fafba8b7dfbc Mon Sep 17 00:00:00 2001 From: Oleg Antipin <60584843+olanti-p@users.noreply.github.com> Date: Wed, 22 Apr 2020 15:56:13 +0300 Subject: [PATCH 34/43] Update src/ranged.cpp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Jianxiang Wang (王健翔) --- src/ranged.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index 29c656c952c51..6033fad707eff 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -2878,7 +2878,7 @@ void target_ui::panel_fire_mode_aim( player &pc, int &text_y ) void target_ui::panel_turret_list( int &text_y ) { - mvwprintw( w_target, point( 1, text_y++ ), "Turrets in range: %d/%d", turrets_in_range.size(), + mvwprintw( w_target, point( 1, text_y++ ), _( "Turrets in range: %d/%d" ), turrets_in_range.size(), vturrets->size() ); for( vehicle_part *t : turrets_in_range ) { From 53a4cf4d802c8fb158a3b2b39a8693a816a6d7a2 Mon Sep 17 00:00:00 2001 From: Oleg Antipin Date: Wed, 22 Apr 2020 16:13:15 +0300 Subject: [PATCH 35/43] When extremely short on space, collapse blank lines reserved for target. Makes the aim bars jump around when moving the aim point on and off a target, but allows to see full list of controls when no target selected. --- src/ranged.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index 6033fad707eff..1083842fff0aa 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -183,6 +183,8 @@ class target_ui // Compact layout bool compact; + // Tiny layout - when extremely whort on space + bool tiny; // Window catacurses::window w_target; // Input context @@ -305,7 +307,7 @@ class target_ui void panel_gun_info( int &text_y ); void panel_recoil( player &pc, int &text_y ); void panel_spell_info( player &pc, int &text_y ); - void panel_target_info( int &text_y ); + void panel_target_info( int &text_y, bool fill_with_blank_if_no_target ); void panel_fire_mode_aim( player &pc, int &text_y ); void panel_turret_list( int &text_y ); @@ -1977,11 +1979,12 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) void target_ui::init_window_and_input( player &pc ) { compact = TERMY < 41; - bool tiny = TERMY < 32; + bool use_whole_sidebar = TERMY < 32; + tiny = TERMY < 28; int top = 0; int width = 55; int height; - if( tiny ) { + if( use_whole_sidebar ) { // If we're extremely short on space, use the whole sidebar. height = TERMY; } else if( compact ) { @@ -2593,7 +2596,8 @@ void target_ui::draw_ui_window( player &pc ) text_y += compact ? 0 : 1; } - panel_target_info( text_y ); + bool fill_with_blank_if_no_target = !tiny; + panel_target_info( text_y, fill_with_blank_if_no_target ); text_y += compact ? 0 : 1; if( mode == TargetMode::Turrets ) { @@ -2823,7 +2827,7 @@ void target_ui::panel_spell_info( player &pc, int &text_y ) casting->description() ); } -void target_ui::panel_target_info( int &text_y ) +void target_ui::panel_target_info( int &text_y, bool fill_with_blank_if_no_target ) { int max_lines = 4; if( dst_critter ) { @@ -2833,12 +2837,13 @@ void target_ui::panel_target_info( int &text_y ) // and somewhat reliably get 4. int fix_for_print_info = max_lines - 2; dst_critter->print_info( w_target, text_y, fix_for_print_info, 1 ); - } else { + text_y += max_lines; + } else if( fill_with_blank_if_no_target ) { // Fill with blank lines to prevent other panels from jumping around // when the cursor moves. + text_y += max_lines; // TODO: print info about tile? } - text_y += max_lines; } void target_ui::panel_fire_mode_aim( player &pc, int &text_y ) From 1197d2149fa8a8c9ef2ce0120e5c3c2c2402ac21 Mon Sep 17 00:00:00 2001 From: olanti-p Date: Fri, 1 May 2020 02:12:07 +0300 Subject: [PATCH 36/43] Migrate '[?] show help' from master --- src/ranged.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index 1083842fff0aa..3cc24416151b2 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -298,6 +298,7 @@ class target_ui std::string uitext_fire(); void draw_window_title(); + void draw_help_notice(); // Draw list of available controls at the bottom of the window. // Returns how much lines it took. @@ -2580,6 +2581,7 @@ void target_ui::draw_ui_window( player &pc ) draw_border( w_target ); draw_window_title(); + draw_help_notice(); draw_controls_list(); int text_y = 1; // Skip top border @@ -2650,6 +2652,18 @@ void target_ui::draw_window_title() wprintz( w_target, c_white, " >" ); } +void target_ui::draw_help_notice() +{ + int text_y = getmaxy( w_target ) - 1; + int width = getmaxx( w_target ); + const std::string label_help = _( "[?] show all controls" ); + int label_width = std::min( utf8_width( label_help ), width - 6 ); // 6 for borders and "< " + " >" + int text_x = width - label_width - 6; + mvwprintz( w_target, point( text_x + 1, text_y ), c_white, "< " ); + trim_and_print( w_target, point( text_x + 3, text_y ), label_width, c_white, label_help ); + wprintz( w_target, c_white, " >" ); +} + int target_ui::draw_controls_list() { // Get first key bound to given action OR ' ' if there are none. @@ -2664,7 +2678,7 @@ int target_ui::draw_controls_list() // Since this list is of variable length and positioned // at the bottom, we draw everything in reverse order - int text_y = height - ( compact ? 1 : 2 ); // If we're short on space, draw over bottom border + int text_y = height - 2; // Don't draw over bottom border if( is_mouse_enabled() ) { const char *label_mouse = "Mouse: LMB: Target, Wheel: Cycle,"; int text_x = utf8_width( label_mouse ) + 2; // '2' for border + space at the end From 584f05872cd245b1f87ac343ccfd51fd4cb3ddd3 Mon Sep 17 00:00:00 2001 From: olanti-p Date: Fri, 1 May 2020 04:49:05 +0300 Subject: [PATCH 37/43] Dynamically shrink list of controls Resolves the problem of text overlapping on small sizes and shouldn't confuse new players since '[?] show help' is always there now --- src/ranged.cpp | 115 +++++++++++++++++++++++++++++++------------------ 1 file changed, 74 insertions(+), 41 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index 3cc24416151b2..6705be85c3e45 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -301,8 +301,8 @@ class target_ui void draw_help_notice(); // Draw list of available controls at the bottom of the window. - // Returns how much lines it took. - int draw_controls_list(); + // text_y - first free line counting from the top + void draw_controls_list( int text_y ); void panel_cursor_info( int &text_y ); void panel_gun_info( int &text_y ); @@ -1351,9 +1351,9 @@ static int print_aim( const player &p, const catacurses::window &w, int line_num range, target_size, predicted_recoil ); } -static int draw_throw_aim( const player &p, const catacurses::window &w, int line_number, - input_context &ctxt, - const item &weapon, const tripoint &target_pos, bool is_blind_throw ) +static void draw_throw_aim( const player &p, const catacurses::window &w, int &text_y, + input_context &ctxt, + const item &weapon, const tripoint &target_pos, bool is_blind_throw ) { Creature *target = g->critter_at( target_pos, true ); if( target != nullptr && !p.sees( *target ) ) { @@ -1381,9 +1381,9 @@ static int draw_throw_aim( const player &p, const catacurses::window &w, int lin const target_ui::TargetMode throwing_target_mode = is_blind_throw ? target_ui::TargetMode::ThrowBlind : target_ui::TargetMode::Throw; - return print_ranged_chance( p, w, line_number, throwing_target_mode, ctxt, weapon, dispersion, - confidence_config, - range, target_size ); + text_y = print_ranged_chance( p, w, text_y, throwing_target_mode, ctxt, weapon, dispersion, + confidence_config, + range, target_size ); } std::vector Character::get_aim_types( const item &gun ) const @@ -2582,7 +2582,6 @@ void target_ui::draw_ui_window( player &pc ) draw_border( w_target ); draw_window_title(); draw_help_notice(); - draw_controls_list(); int text_y = 1; // Skip top border @@ -2614,6 +2613,8 @@ void target_ui::draw_ui_window( player &pc ) } } + draw_controls_list( text_y ); + wrefresh( w_target ); } @@ -2664,32 +2665,51 @@ void target_ui::draw_help_notice() wprintz( w_target, c_white, " >" ); } -int target_ui::draw_controls_list() +void target_ui::draw_controls_list( int text_y ) { + // Change UI colors for visual feedback + // TODO: Colorize keys inside brackets to be consistent with other UI windows + nc_color col_enabled = c_white; + nc_color col_disabled = c_light_gray; + nc_color col_move = ( status != Status::OutOfAmmo ? col_enabled : col_disabled ); + nc_color col_fire = ( status == Status::Good ? col_enabled : col_disabled ); + // Get first key bound to given action OR ' ' if there are none. const auto bound_key = [this]( const std::string & s ) { - const auto keys = ctxt.keys_bound_to( s ); + const std::vector keys = this->ctxt.keys_bound_to( s ); return keys.empty() ? ' ' : keys.front(); }; + const auto colored = [col_enabled]( nc_color color, const std::string & s ) { + if( color == col_enabled ) { + // col_enabled is the default one when printing + return s; + } else { + return colorize( s, color ); + } + }; - nc_color move_color = ( status != Status::OutOfAmmo ? c_white : c_light_gray ); - nc_color fire_color = ( status == Status::Good ? c_white : c_light_gray ); - int height = getmaxy( w_target ); + struct line { + size_t order; // Lines with highest 'order' are removed first + std::string str; + }; + std::vector lines; - // Since this list is of variable length and positioned - // at the bottom, we draw everything in reverse order - int text_y = height - 2; // Don't draw over bottom border + // Compile full list + lines.push_back( {8, colored( col_move, _( "Move cursor with directional keys" ) )} ); if( is_mouse_enabled() ) { - const char *label_mouse = "Mouse: LMB: Target, Wheel: Cycle,"; - int text_x = utf8_width( label_mouse ) + 2; // '2' for border + space at the end - mvwprintz( w_target, point( 1, text_y ), move_color, label_mouse ); - mvwprintz( w_target, point( text_x, text_y-- ), fire_color, _( "RMB: Fire" ) ); + std::string move = _( "Mouse: LMB: Target, Wheel: Cycle, " ); + std::string fire = _( "RMB: Fire" ); + lines.push_back( {7, colored( col_move, move ) + colored( col_fire, fire )} ); } - if( mode == TargetMode::Fire || mode == TargetMode::TurretManual ) { - mvwprintz( w_target, point( 1, text_y-- ), c_white, _( "[%c] to reload/switch ammo." ), - bound_key( "SWITCH_AMMO" ) ); - mvwprintz( w_target, point( 1, text_y-- ), c_white, _( "[%c] to switch firing modes." ), - bound_key( "SWITCH_MODE" ) ); + { + std::string cycle = string_format( _( "[%s] Cycle targets; " ), ctxt.get_desc( "NEXT_TARGET", 1 ) ); + std::string fire = string_format( _( "[%c] %s." ), bound_key( "FIRE" ), uitext_fire() ); + lines.push_back( {0, colored( col_move, cycle ) + colored( col_fire, fire )} ); + } + { + std::string text = string_format( _( "[%c] target self; [%c] toggle snap-to-target" ), + bound_key( "CENTER" ), bound_key( "TOGGLE_SNAP_TO_TARGET" ) ); + lines.push_back( {3, colored( col_enabled, text )} ); } if( mode == TargetMode::Fire ) { std::string aim_and_fire; @@ -2698,25 +2718,38 @@ int target_ui::draw_controls_list() aim_and_fire += string_format( "[%c] ", bound_key( e.action ) ); } } + aim_and_fire += _( "to aim and fire." ); - mvwprintz( w_target, point( 1, text_y-- ), fire_color, _( "[%c] to switch aiming modes." ), - bound_key( "SWITCH_AIM" ) ); - mvwprintz( w_target, point( 1, text_y-- ), fire_color, _( "%sto aim and fire" ), aim_and_fire ); - mvwprintz( w_target, point( 1, text_y-- ), fire_color, _( "[%c] to steady your aim. (10 moves)" ), - bound_key( "AIM" ) ); + std::string aim = string_format( _( "[%c] to steady your aim. (10 moves)" ), + bound_key( "AIM" ) ); + std::string sw_aim = string_format( _( "[%c] to switch aiming modes." ), + bound_key( "SWITCH_AIM" ) ); + + lines.push_back( {2, colored( col_fire, aim )} ); + lines.push_back( {1, colored( col_fire, sw_aim )} ); + lines.push_back( {4, colored( col_fire, aim_and_fire )} ); + } + if( mode == TargetMode::Fire || mode == TargetMode::TurretManual ) { + lines.push_back( {5, colored( col_enabled, string_format( _( "[%c] to switch firing modes." ), + bound_key( "SWITCH_MODE" ) ) )} ); + lines.push_back( {6, colored( col_enabled, string_format( _( "[%c] to reload/switch ammo." ), + bound_key( "SWITCH_AMMO" ) ) )} ); } - mvwprintz( w_target, point( 1, text_y-- ), c_white, - _( "[%c] target self; [%c] toggle snap-to-target" ), bound_key( "CENTER" ), - bound_key( "TOGGLE_SNAP_TO_TARGET" ) ); - auto label_cycle = string_format( _( "[%s] Cycle targets;" ), ctxt.get_desc( "NEXT_TARGET", 1 ) ); - int text_x = utf8_width( label_cycle ) + 2; // '2' for border + space at the end - mvwprintz( w_target, point( 1, text_y ), move_color, label_cycle ); - mvwprintz( w_target, point( text_x, text_y-- ), fire_color, "[%c] %s.", bound_key( "FIRE" ), - uitext_fire() ); + // Shrink the list until it fits + int height = getmaxy( w_target ); + size_t available_lines = static_cast( height - text_y - 1 ); // 1 for bottom border + while( lines.size() > available_lines ) { + lines.erase( std::max_element( lines.begin(), lines.end(), []( const line & l1, const line & l2 ) { + return l1.order < l2.order; + } ) ); + } - mvwprintz( w_target, point( 1, text_y-- ), move_color, _( "Move cursor with directional keys" ) ); - return height - text_y; + text_y = height - lines.size() - 1; + for( const line &l : lines ) { + nc_color col = col_enabled; + print_colored_text( w_target, point( 1, text_y++ ), col, col, l.str ); + } } void target_ui::panel_cursor_info( int &text_y ) From 7ec443b0eaa0e24879f42e91b5720239de0a088b Mon Sep 17 00:00:00 2001 From: olanti-p Date: Fri, 1 May 2020 19:04:28 +0300 Subject: [PATCH 38/43] Some positioning-related fixes --- src/ranged.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index 2d5c1596103ab..fad93969914ea 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -2029,6 +2029,7 @@ void target_ui::init_window_and_input( player &pc ) // we can have small window size and don't suffer from it. width = 34; height = 24; + compact = true; } else { width = 55; compact = TERMY < 41; @@ -2791,8 +2792,11 @@ void target_ui::draw_controls_list( int text_y ) // Shrink the list until it fits int height = getmaxy( w_target ); - size_t available_lines = static_cast( height - text_y - 1 ); // 1 for bottom border - while( lines.size() > available_lines ) { + int available_lines = height - text_y - 1; // 1 for bottom border + if( available_lines <= 0 ) { + return; + } + while( lines.size() > static_cast( available_lines ) ) { lines.erase( std::max_element( lines.begin(), lines.end(), []( const line & l1, const line & l2 ) { return l1.order < l2.order; } ) ); @@ -2839,6 +2843,7 @@ void target_ui::panel_cursor_info( int &text_y ) print_colored_text( w_target, point( text_x, text_y ), col, col, s ); text_x += len + 1; // 1 for space } + text_y++; } void target_ui::panel_gun_info( int &text_y ) From d452caca61363d0e9bc1edc9fdda25056513c196 Mon Sep 17 00:00:00 2001 From: olanti-p Date: Fri, 1 May 2020 21:27:24 +0300 Subject: [PATCH 39/43] Remove a duplicate check --- src/ranged.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index fad93969914ea..4cf769107f80a 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -2078,9 +2078,7 @@ void target_ui::init_window_and_input( player &pc ) if( mode == TargetMode::Fire ) { ctxt.register_action( "AIM" ); ctxt.register_action( "SWITCH_AIM" ); - } - if( mode == TargetMode::Fire ) { aim_types = pc.get_aim_types( *relevant ); for( aim_type &type : aim_types ) { if( type.has_threshold ) { From dc64250c4a6812ff2d79e1a52da5837d447a5a66 Mon Sep 17 00:00:00 2001 From: olanti-p Date: Fri, 1 May 2020 22:12:43 +0300 Subject: [PATCH 40/43] Better comments --- src/ranged.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index 4cf769107f80a..dc0cd883aa1c6 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -2025,8 +2025,8 @@ void target_ui::init_window_and_input( player &pc ) int width; int height; if( narrow ) { - // Narrow style excludes the list of controls; - // we can have small window size and don't suffer from it. + // Narrow layout removes the list of controls. This allows us + // to have small window size and not suffer from it. width = 34; height = 24; compact = true; From 5ec7c3784da69cb85a07301c241b77623099fe67 Mon Sep 17 00:00:00 2001 From: olanti-p Date: Fri, 1 May 2020 22:21:38 +0300 Subject: [PATCH 41/43] Don't re-confirm attack when aim-and-fire takes multiple turns --- src/ranged.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index dc0cd883aa1c6..2130ca463a2f8 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -1855,6 +1855,7 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) // Handle multi-turn aiming std::string action; + bool attack_was_confirmed = false; if( mode == TargetMode::Fire ) { if( pc.activity.id() == ACT_AIM ) { // We were in this UI during previous turn... @@ -1865,6 +1866,7 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) // ...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 of snap-to-target mode to keep it consistent between turns snap_to_target = pc.activity.str_values[1] == "snap"; @@ -1882,6 +1884,7 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) if( !choose_initial_target( pc, initial_dst ) ) { // We've lost our target from previous turn action.clear(); + attack_was_confirmed = false; } set_cursor_pos( pc, initial_dst ); @@ -1964,7 +1967,7 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) // This action basically means "Fire" as well; the actual firing may be delayed // through aiming, but there is usually no means to abort it. Therefore we query now - if( !confirm_non_enemy_target() ) { + if( !attack_was_confirmed && !confirm_non_enemy_target() ) { continue; } From a3073c6a9056b431286d9e2650550c314d72b42e Mon Sep 17 00:00:00 2001 From: olanti-p Date: Sat, 2 May 2020 00:44:05 +0300 Subject: [PATCH 42/43] Fix regression related to aim point not snapping to nearest target when entering the UI --- src/ranged.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index 2130ca463a2f8..0f3c07be403be 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -235,10 +235,11 @@ class target_ui void update_target_list( player &pc ); // Tries to find something to aim at. + // reentered - true if UI was re-entered (e.g. during multi-turn aiming) // Validates pc.last_target and pc.last_target_pos. // Sets 'new_dst' as the initial aiming point. // Returns 'true' if we can proceed with aim-and-shoot. - bool choose_initial_target( player &pc, tripoint &new_dst ); + bool choose_initial_target( player &pc, bool reentered, tripoint &new_dst ); // Update 'status' variable void update_status(); @@ -1856,9 +1857,11 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) // Handle multi-turn aiming std::string action; bool attack_was_confirmed = false; + bool reentered = false; 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. @@ -1881,7 +1884,7 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code ) src = pc.pos(); tripoint initial_dst = src; update_target_list( pc ); - if( !choose_initial_target( pc, initial_dst ) ) { + if( !choose_initial_target( pc, reentered, initial_dst ) ) { // We've lost our target from previous turn action.clear(); attack_was_confirmed = false; @@ -2279,7 +2282,7 @@ void target_ui::update_target_list( player &pc ) } ); } -bool target_ui::choose_initial_target( player &pc, tripoint &new_dst ) +bool target_ui::choose_initial_target( player &pc, bool reentered, tripoint &new_dst ) { // Determine if we had a target and it is still visible const auto old_target = std::find( targets.begin(), targets.end(), pc.last_target.lock().get() ); @@ -2301,7 +2304,7 @@ bool target_ui::choose_initial_target( player &pc, tripoint &new_dst ) if( dist_fn( local ) > range ) { // No luck pc.last_target_pos = cata::nullopt; - } else { + } else if( reentered ) { local_last_tgt_pos = local; } } From 42a145d3c19050e5382944229a9fb7a5c1dd13e9 Mon Sep 17 00:00:00 2001 From: olanti-p Date: Sat, 2 May 2020 01:15:42 +0300 Subject: [PATCH 43/43] Appease clang-tidy --- src/ranged.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index 0f3c07be403be..63b062405d78b 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -2755,14 +2755,14 @@ void target_ui::draw_controls_list( int text_y ) // Compile full list lines.push_back( {8, colored( col_move, _( "Move cursor with directional keys" ) )} ); if( is_mouse_enabled() ) { - std::string move = _( "Mouse: LMB: Target, Wheel: Cycle, " ); + std::string move = _( "Mouse: LMB: Target, Wheel: Cycle," ); std::string fire = _( "RMB: Fire" ); - lines.push_back( {7, colored( col_move, move ) + colored( col_fire, fire )} ); + lines.push_back( {7, colored( col_move, move ) + " " + colored( col_fire, fire )} ); } { - std::string cycle = string_format( _( "[%s] Cycle targets; " ), ctxt.get_desc( "NEXT_TARGET", 1 ) ); + std::string cycle = string_format( _( "[%s] Cycle targets;" ), ctxt.get_desc( "NEXT_TARGET", 1 ) ); std::string fire = string_format( _( "[%c] %s." ), bound_key( "FIRE" ), uitext_fire() ); - lines.push_back( {0, colored( col_move, cycle ) + colored( col_fire, fire )} ); + lines.push_back( {0, colored( col_move, cycle ) + " " + colored( col_fire, fire )} ); } { std::string text = string_format( _( "[%c] target self; [%c] toggle snap-to-target" ),