diff --git a/src/avatar.cpp b/src/avatar.cpp index 6d97230b62f5c..390b50bab3a9c 100644 --- a/src/avatar.cpp +++ b/src/avatar.cpp @@ -1530,3 +1530,56 @@ bool avatar::wield( item &target ) return true; } + +bool avatar::invoke_item( item *used, const tripoint &pt ) +{ + const std::map &use_methods = used->type->use_methods; + + if( use_methods.empty() ) { + return false; + } else if( use_methods.size() == 1 ) { + return invoke_item( used, use_methods.begin()->first, pt ); + } + + uilist umenu; + + umenu.text = string_format( _( "What to do with your %s?" ), used->tname() ); + umenu.hilight_disabled = true; + + for( const auto &e : use_methods ) { + const auto res = e.second.can_call( *this, *used, false, pt ); + umenu.addentry_desc( MENU_AUTOASSIGN, res.success(), MENU_AUTOASSIGN, e.second.get_name(), + res.str() ); + } + + umenu.desc_enabled = std::any_of( umenu.entries.begin(), + umenu.entries.end(), []( const uilist_entry & elem ) { + return !elem.desc.empty(); + } ); + + umenu.query(); + + int choice = umenu.ret; + if( choice < 0 || choice >= static_cast( use_methods.size() ) ) { + return false; + } + + const std::string &method = std::next( use_methods.begin(), choice )->first; + + return invoke_item( used, method, pt ); +} + +bool avatar::invoke_item( item *used ) +{ + return Character::invoke_item( used ); +} + +bool avatar::invoke_item( item *used, const std::string &method, const tripoint &pt ) +{ + return Character::invoke_item( used, method, pt ); +} + +bool avatar::invoke_item( item *used, const std::string &method ) +{ + return Character::invoke_item( used, method ); +} diff --git a/src/avatar.h b/src/avatar.h index 39d5bae9a6c56..72452f2962762 100644 --- a/src/avatar.h +++ b/src/avatar.h @@ -172,6 +172,12 @@ class avatar : public player bool wield( item &target ) override; + using Character::invoke_item; + bool invoke_item( item *, const tripoint &pt ) override; + bool invoke_item( item * ) override; + bool invoke_item( item *, const std::string &, const tripoint &pt ) override; + bool invoke_item( item *, const std::string & ) override; + private: map_memory player_map_memory; bool show_map_memory; diff --git a/src/character.cpp b/src/character.cpp index 60ade1cc56826..13b448500ae89 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -57,6 +57,7 @@ #include "lightmap.h" #include "rng.h" #include "stomach.h" +#include "text_snippets.h" #include "ui.h" #include "veh_type.h" #include "vehicle.h" @@ -173,6 +174,7 @@ static const trait_id trait_PYROMANIA( "PYROMANIA" ); static const trait_id trait_ROOTS2( "ROOTS2" ); static const trait_id trait_ROOTS3( "ROOTS3" ); static const trait_id trait_SEESLEEP( "SEESLEEP" ); +static const trait_id trait_SELFAWARE( "SELFAWARE" ); static const trait_id trait_SHELL2( "SHELL2" ); static const trait_id trait_SHELL( "SHELL" ); static const trait_id trait_SHOUT2( "SHOUT2" ); @@ -2906,6 +2908,38 @@ void Character::mod_int_bonus( int nint ) int_cur = std::max( 0, int_max + int_bonus ); } +void Character::print_health() const +{ + if( !is_player() ) { + return; + } + int current_health = get_healthy(); + if( has_trait( trait_SELFAWARE ) ) { + add_msg_if_player( _( "Your current health value is %d." ), current_health ); + } + + if( current_health > 0 && + ( has_effect( effect_common_cold ) || has_effect( effect_flu ) ) ) { + return; + } + + static const std::map msg_categories = { + { -100, "health_horrible" }, + { -50, "health_very_bad" }, + { -10, "health_bad" }, + { 10, "" }, + { 50, "health_good" }, + { 100, "health_very_good" }, + { INT_MAX, "health_great" } + }; + + auto iter = msg_categories.lower_bound( current_health ); + if( iter != msg_categories.end() && !iter->second.empty() ) { + const std::string &msg = SNIPPET.random_from_category( iter->second ); + add_msg_if_player( current_health > 0 ? m_good : m_bad, msg ); + } +} + namespace io { template<> @@ -5022,6 +5056,127 @@ void Character::update_stamina( int turns ) set_stamina( std::min( std::max( get_stamina(), 0 ), max_stam ) ); } +bool Character::invoke_item( item *used ) +{ + return invoke_item( used, pos() ); +} + +bool Character::invoke_item( item *, const tripoint & ) +{ + return false; +} + +bool Character::invoke_item( item *used, const std::string &method ) +{ + return invoke_item( used, method, pos() ); +} + +bool Character::invoke_item( item *used, const std::string &method, const tripoint &pt ) +{ + if( !has_enough_charges( *used, true ) ) { + return false; + } + + item *actually_used = used->get_usable_item( method ); + if( actually_used == nullptr ) { + debugmsg( "Tried to invoke a method %s on item %s, which doesn't have this method", + method.c_str(), used->tname() ); + return false; + } + + int charges_used = actually_used->type->invoke( *this->as_player(), *actually_used, pt, method ); + if( charges_used == 0 ) { + return false; + } + // Prevent accessing the item as it may have been deleted by the invoked iuse function. + + if( used->is_tool() || used->is_medication() || used->get_contained().is_medication() ) { + return consume_charges( *actually_used, charges_used ); + } else if( used->is_bionic() || used->is_deployable() || method == "place_trap" ) { + i_rem( used ); + return true; + } + + return false; +} + +bool Character::has_enough_charges( const item &it, bool show_msg ) const +{ + if( !it.is_tool() || !it.ammo_required() ) { + return true; + } + if( it.has_flag( "USE_UPS" ) ) { + if( has_charges( "UPS", it.ammo_required() ) || it.ammo_sufficient() ) { + return true; + } + if( show_msg ) { + add_msg_if_player( m_info, + ngettext( "Your %s needs %d charge from some UPS.", + "Your %s needs %d charges from some UPS.", + it.ammo_required() ), + it.tname(), it.ammo_required() ); + } + return false; + } else if( !it.ammo_sufficient() ) { + if( show_msg ) { + add_msg_if_player( m_info, + ngettext( "Your %s has %d charge but needs %d.", + "Your %s has %d charges but needs %d.", + it.ammo_remaining() ), + it.tname(), it.ammo_remaining(), it.ammo_required() ); + } + return false; + } + return true; +} + +bool Character::consume_charges( item &used, int qty ) +{ + if( qty < 0 ) { + debugmsg( "Tried to consume negative charges" ); + return false; + } + + if( qty == 0 ) { + return false; + } + + if( !used.is_tool() && !used.is_food() && !used.is_medication() ) { + debugmsg( "Tried to consume charges for non-tool, non-food, non-med item" ); + return false; + } + + // Consume comestibles destroying them if no charges remain + if( used.is_food() || used.is_medication() ) { + used.charges -= qty; + if( used.charges <= 0 ) { + i_rem( &used ); + return true; + } + return false; + } + + // Tools which don't require ammo are instead destroyed + if( used.is_tool() && !used.ammo_required() ) { + i_rem( &used ); + return true; + } + + // USE_UPS never occurs on base items but is instead added by the UPS tool mod + if( used.has_flag( "USE_UPS" ) ) { + // With the new UPS system, we'll want to use any charges built up in the tool before pulling from the UPS + // The usage of the item was already approved, so drain item if possible, otherwise use UPS + if( used.charges >= qty ) { + used.ammo_consume( qty, pos() ); + } else { + use_charges( "UPS", qty ); + } + } else { + used.ammo_consume( std::min( qty, used.ammo_remaining() ), pos() ); + } + return false; +} + int Character::item_handling_cost( const item &it, bool penalties, int base_cost ) const { int mv = base_cost; diff --git a/src/character.h b/src/character.h index f79a6db777ed7..e074812e34b3a 100644 --- a/src/character.h +++ b/src/character.h @@ -264,6 +264,9 @@ class Character : public Creature, public visitable virtual void mod_per_bonus( int nper ); virtual void mod_int_bonus( int nint ); + // Prints message(s) about current health + void print_health() const; + /** Getters for health values exclusive to characters */ virtual int get_healthy() const; virtual int get_healthy_mod() const; @@ -499,6 +502,10 @@ class Character : public Creature, public visitable void set_mutation( const trait_id &flag ); void unset_mutation( const trait_id &flag ); + // Trigger and disable mutations that can be so toggled. + void activate_mutation( const trait_id &mutation ); + void deactivate_mutation( const trait_id &mut ); + /** Converts a body_part to an hp_part */ static hp_part bp_to_hp( body_part bp ); /** Converts an hp_part to a body_part */ @@ -781,6 +788,30 @@ class Character : public Creature, public visitable return false; } + /** + * Asks how to use the item (if it has more than one use_method) and uses it. + * Returns true if it destroys the item. Consumes charges from the item. + * Multi-use items are ONLY supported when all use_methods are iuse_actor! + */ + virtual bool invoke_item( item *, const tripoint &pt ); + /** As above, but with a pre-selected method. Debugmsg if this item doesn't have this method. */ + virtual bool invoke_item( item *, const std::string &, const tripoint &pt ); + /** As above two, but with position equal to current position */ + virtual bool invoke_item( item * ); + virtual bool invoke_item( item *, const std::string & ); + + /** + * Has the item enough charges to invoke its use function? + * Also checks if UPS from this player is used instead of item charges. + */ + bool has_enough_charges( const item &it, bool show_msg ) const; + + /** Consume charges of a tool or comestible item, potentially destroying it in the process + * @param used item consuming the charges + * @param qty number of charges to consume which must be non-zero + * @return true if item was destroyed */ + bool consume_charges( item &used, int qty ); + /** * Calculate (but do not deduct) the number of moves required when handling (e.g. storing, drawing etc.) an item * @param it Item to calculate handling cost for diff --git a/src/mutation.cpp b/src/mutation.cpp index 07108c56cb6da..6108ab4c9f6a0 100644 --- a/src/mutation.cpp +++ b/src/mutation.cpp @@ -415,10 +415,10 @@ bool Character::can_install_cbm_on_bp( const std::vector &bps ) const return can_install; } -void player::activate_mutation( const trait_id &mut ) +void Character::activate_mutation( const trait_id &mut ) { const mutation_branch &mdata = mut.obj(); - auto &tdata = my_mutations[mut]; + trait_data &tdata = my_mutations[mut]; int cost = mdata.cost; // Preserve the fake weapon used to initiate ranged mutation firing static item mut_ranged( weapon ); @@ -568,7 +568,7 @@ void player::activate_mutation( const trait_id &mut ) } } -void player::deactivate_mutation( const trait_id &mut ) +void Character::deactivate_mutation( const trait_id &mut ) { my_mutations[mut].powered = false; diff --git a/src/npc.cpp b/src/npc.cpp index 705c063679753..f5b3bcaa33c33 100644 --- a/src/npc.cpp +++ b/src/npc.cpp @@ -2832,6 +2832,28 @@ void npc::process_turn() // TODO: Make NPCs leave the player if there's a path out of map and player is sleeping/unseen/etc. } +bool npc::invoke_item( item *used, const tripoint &pt ) +{ + const auto &use_methods = used->type->use_methods; + + if( use_methods.empty() ) { + return false; + } else if( use_methods.size() == 1 ) { + return Character::invoke_item( used, use_methods.begin()->first, pt ); + } + return false; +} + +bool npc::invoke_item( item *used, const std::string &method ) +{ + return Character::invoke_item( used, method ); +} + +bool npc::invoke_item( item *used ) +{ + return Character::invoke_item( used ); +} + std::array, npc_need::num_needs> npc::need_data = { { { "need_none", overmap_location_str_id( "source_of_anything" ) }, diff --git a/src/npc.h b/src/npc.h index ebf8efa69e713..a72c255c2dede 100644 --- a/src/npc.h +++ b/src/npc.h @@ -1028,6 +1028,11 @@ class npc : public player void execute_action( npc_action action ); // Performs action void process_turn() override; + using Character::invoke_item; + bool invoke_item( item *, const tripoint &pt ) override; + bool invoke_item( item *used, const std::string &method ) override; + bool invoke_item( item * ) override; + /** rates how dangerous a target is from 0 (harmless) to 1 (max danger) */ float evaluate_enemy( const Creature &target ) const; diff --git a/src/player.cpp b/src/player.cpp index 7526b47d0de95..7106394e23c96 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -3237,38 +3237,6 @@ void player::add_pain_msg( int val, body_part bp ) const } } -void player::print_health() const -{ - if( !is_player() ) { - return; - } - int current_health = get_healthy(); - if( has_trait( trait_SELFAWARE ) ) { - add_msg_if_player( _( "Your current health value is %d." ), current_health ); - } - - if( current_health > 0 && - ( has_effect( effect_common_cold ) || has_effect( effect_flu ) ) ) { - return; - } - - static const std::map msg_categories = { - { -100, "health_horrible" }, - { -50, "health_very_bad" }, - { -10, "health_bad" }, - { 10, "" }, - { 50, "health_good" }, - { 100, "health_very_good" }, - { INT_MAX, "health_great" } - }; - - auto iter = msg_categories.lower_bound( current_health ); - if( iter != msg_categories.end() && !iter->second.empty() ) { - const std::string &msg = SNIPPET.random_from_category( iter->second ); - add_msg_if_player( current_health > 0 ? m_good : m_bad, msg ); - } -} - void player::process_one_effect( effect &it, bool is_new ) { bool reduced = resists_effect( it ); @@ -7199,83 +7167,6 @@ hint_rating player::rate_action_use( const item &it ) const return HINT_CANT; } -bool player::has_enough_charges( const item &it, bool show_msg ) const -{ - if( !it.is_tool() || !it.ammo_required() ) { - return true; - } - if( it.has_flag( "USE_UPS" ) ) { - if( has_charges( "UPS", it.ammo_required() ) || it.ammo_sufficient() ) { - return true; - } - if( show_msg ) { - add_msg_if_player( m_info, - ngettext( "Your %s needs %d charge from some UPS.", - "Your %s needs %d charges from some UPS.", - it.ammo_required() ), - it.tname(), it.ammo_required() ); - } - return false; - } else if( !it.ammo_sufficient() ) { - if( show_msg ) { - add_msg_if_player( m_info, - ngettext( "Your %s has %d charge but needs %d.", - "Your %s has %d charges but needs %d.", - it.ammo_remaining() ), - it.tname(), it.ammo_remaining(), it.ammo_required() ); - } - return false; - } - return true; -} - -bool player::consume_charges( item &used, int qty ) -{ - if( qty < 0 ) { - debugmsg( "Tried to consume negative charges" ); - return false; - } - - if( qty == 0 ) { - return false; - } - - if( !used.is_tool() && !used.is_food() && !used.is_medication() ) { - debugmsg( "Tried to consume charges for non-tool, non-food, non-med item" ); - return false; - } - - // Consume comestibles destroying them if no charges remain - if( used.is_food() || used.is_medication() ) { - used.charges -= qty; - if( used.charges <= 0 ) { - i_rem( &used ); - return true; - } - return false; - } - - // Tools which don't require ammo are instead destroyed - if( used.is_tool() && !used.ammo_required() ) { - i_rem( &used ); - return true; - } - - // USE_UPS never occurs on base items but is instead added by the UPS tool mod - if( used.has_flag( "USE_UPS" ) ) { - // With the new UPS system, we'll want to use any charges built up in the tool before pulling from the UPS - // The usage of the item was already approved, so drain item if possible, otherwise use UPS - if( used.charges >= qty ) { - used.ammo_consume( qty, pos() ); - } else { - use_charges( "UPS", qty ); - } - } else { - used.ammo_consume( std::min( qty, used.ammo_remaining() ), pos() ); - } - return false; -} - void player::use( int inventory_position ) { item &used = i_at( inventory_position ); @@ -7330,83 +7221,6 @@ void player::use( item_location loc ) } } -bool player::invoke_item( item *used ) -{ - return invoke_item( used, pos() ); -} - -bool player::invoke_item( item *used, const tripoint &pt ) -{ - const auto &use_methods = used->type->use_methods; - - if( use_methods.empty() ) { - return false; - } else if( use_methods.size() == 1 ) { - return invoke_item( used, use_methods.begin()->first, pt ); - } - - uilist umenu; - - umenu.text = string_format( _( "What to do with your %s?" ), used->tname() ); - umenu.hilight_disabled = true; - - for( const auto &e : use_methods ) { - const auto res = e.second.can_call( *this, *used, false, pt ); - umenu.addentry_desc( MENU_AUTOASSIGN, res.success(), MENU_AUTOASSIGN, e.second.get_name(), - res.str() ); - } - - umenu.desc_enabled = std::any_of( umenu.entries.begin(), - umenu.entries.end(), []( const uilist_entry & elem ) { - return !elem.desc.empty(); - } ); - - umenu.query(); - - int choice = umenu.ret; - if( choice < 0 || choice >= static_cast( use_methods.size() ) ) { - return false; - } - - const std::string &method = std::next( use_methods.begin(), choice )->first; - - return invoke_item( used, method, pt ); -} - -bool player::invoke_item( item *used, const std::string &method ) -{ - return invoke_item( used, method, pos() ); -} - -bool player::invoke_item( item *used, const std::string &method, const tripoint &pt ) -{ - if( !has_enough_charges( *used, true ) ) { - return false; - } - - item *actually_used = used->get_usable_item( method ); - if( actually_used == nullptr ) { - debugmsg( "Tried to invoke a method %s on item %s, which doesn't have this method", - method.c_str(), used->tname() ); - return false; - } - - int charges_used = actually_used->type->invoke( *this, *actually_used, pt, method ); - if( charges_used == 0 ) { - return false; - } - // Prevent accessing the item as it may have been deleted by the invoked iuse function. - - if( used->is_tool() || used->is_medication() || used->get_contained().is_medication() ) { - return consume_charges( *actually_used, charges_used ); - } else if( used->is_bionic() || used->is_deployable() || method == "place_trap" ) { - i_rem( used ); - return true; - } - - return false; -} - void player::reassign_item( item &it, int invlet ) { bool remove_old = true; diff --git a/src/player.h b/src/player.h index e3b19749a52ef..82f5c7afc6118 100644 --- a/src/player.h +++ b/src/player.h @@ -901,26 +901,10 @@ class player : public Character void use( item_location loc ); /** Uses the current wielded weapon */ void use_wielded(); - /** - * Asks how to use the item (if it has more than one use_method) and uses it. - * Returns true if it destroys the item. Consumes charges from the item. - * Multi-use items are ONLY supported when all use_methods are iuse_actor! - */ - bool invoke_item( item *, const tripoint &pt ); - /** As above, but with a pre-selected method. Debugmsg if this item doesn't have this method. */ - bool invoke_item( item *, const std::string &, const tripoint &pt ); - /** As above two, but with position equal to current position */ - bool invoke_item( item * ); - bool invoke_item( item *, const std::string & ); + /** Reassign letter. */ void reassign_item( item &it, int invlet ); - /** Consume charges of a tool or comestible item, potentially destroying it in the process - * @param used item consuming the charges - * @param qty number of charges to consume which must be non-zero - * @return true if item was destroyed */ - bool consume_charges( item &used, int qty ); - /** Removes gunmod after first unloading any contained ammo and returns true on success */ bool gunmod_remove( item &gun, item &mod ); @@ -1367,18 +1351,9 @@ class player : public Character void print_encumbrance( const catacurses::window &win, int line = -1, const item *selected_clothing = nullptr ) const; - // Prints message(s) about current health - void print_health() const; - using Character::query_yn; bool query_yn( const std::string &mes ) const override; - /** - * Has the item enough charges to invoke its use function? - * Also checks if UPS from this player is used instead of item charges. - */ - bool has_enough_charges( const item &it, bool show_msg ) const; - const pathfinding_settings &get_pathfinding_settings() const override; std::set get_path_avoid() const override; @@ -1427,10 +1402,6 @@ class player : public Character */ bool is_visible_in_range( const Creature &critter, int range ) const; - // Trigger and disable mutations that can be so toggled. - void activate_mutation( const trait_id &mutation ); - void deactivate_mutation( const trait_id &mut ); - /** Determine player's capability of recharging their CBMs. */ bool can_feed_reactor_with( const item &it ) const; bool can_feed_furnace_with( const item &it ) const;