Permalink
Fris0uman
Make surgery a player_activity (#31763)
2120a0e
Jul 15, 2019
Join GitHub today
GitHub is home to over 36 million developers working together to host and review code, manage projects, and build software together.
Sign up| #include "iexamine.h" | |
| #include <climits> | |
| #include <cmath> | |
| #include <algorithm> | |
| #include <cstdlib> | |
| #include <sstream> | |
| #include <iterator> | |
| #include <map> | |
| #include <memory> | |
| #include <set> | |
| #include <type_traits> | |
| #include <utility> | |
| #include "ammo.h" | |
| #include "avatar.h" | |
| #include "basecamp.h" | |
| #include "bionics.h" | |
| #include "calendar.h" | |
| #include "cata_utility.h" | |
| #include "catacharset.h" | |
| #include "compatibility.h" // needed for the workaround for the std::to_string bug in some compilers | |
| #include "construction.h" | |
| #include "coordinate_conversions.h" | |
| #include "craft_command.h" | |
| #include "debug.h" | |
| #include "effect.h" | |
| #include "event.h" | |
| #include "field.h" | |
| #include "fungal_effects.h" | |
| #include "game.h" | |
| #include "game_inventory.h" | |
| #include "handle_liquid.h" | |
| #include "harvest.h" | |
| #include "input.h" | |
| #include "inventory.h" | |
| #include "line.h" | |
| #include "map.h" | |
| #include "map_iterator.h" | |
| #include "mapdata.h" | |
| #include "material.h" | |
| #include "messages.h" | |
| #include "mission_companion.h" | |
| #include "monster.h" | |
| #include "mtype.h" | |
| #include "iuse_actor.h" | |
| #include "npc.h" | |
| #include "options.h" | |
| #include "output.h" | |
| #include "overmapbuffer.h" | |
| #include "pickup.h" | |
| #include "player.h" | |
| #include "recipe.h" | |
| #include "requirements.h" | |
| #include "rng.h" | |
| #include "sounds.h" | |
| #include "string_formatter.h" | |
| #include "string_input_popup.h" | |
| #include "translations.h" | |
| #include "trap.h" | |
| #include "ui.h" | |
| #include "uistate.h" | |
| #include "units.h" | |
| #include "vehicle.h" | |
| #include "vehicle_selector.h" | |
| #include "vpart_position.h" | |
| #include "weather.h" | |
| #include "bodypart.h" | |
| #include "color.h" | |
| #include "creature.h" | |
| #include "cursesdef.h" | |
| #include "damage.h" | |
| #include "enums.h" | |
| #include "game_constants.h" | |
| #include "int_id.h" | |
| #include "item.h" | |
| #include "item_location.h" | |
| #include "item_stack.h" | |
| #include "iuse.h" | |
| #include "map_selector.h" | |
| #include "pimpl.h" | |
| #include "player_activity.h" | |
| #include "pldata.h" | |
| #include "string_id.h" | |
| #include "colony.h" | |
| #include "flat_set.h" | |
| #include "magic_teleporter_list.h" | |
| #include "point.h" | |
| const mtype_id mon_dark_wyrm( "mon_dark_wyrm" ); | |
| const mtype_id mon_fungal_blossom( "mon_fungal_blossom" ); | |
| const mtype_id mon_spider_web_s( "mon_spider_web_s" ); | |
| const mtype_id mon_spider_widow_giant_s( "mon_spider_widow_giant_s" ); | |
| const mtype_id mon_spider_cellar_giant_s( "mon_spider_cellar_giant_s" ); | |
| const mtype_id mon_turret( "mon_turret" ); | |
| const mtype_id mon_turret_rifle( "mon_turret_rifle" ); | |
| const skill_id skill_computer( "computer" ); | |
| const skill_id skill_fabrication( "fabrication" ); | |
| const skill_id skill_electronics( "electronics" ); | |
| const skill_id skill_firstaid( "firstaid" ); | |
| const skill_id skill_mechanics( "mechanics" ); | |
| const skill_id skill_cooking( "cooking" ); | |
| const skill_id skill_survival( "survival" ); | |
| const efftype_id effect_mending( "mending" ); | |
| const efftype_id effect_pkill2( "pkill2" ); | |
| const efftype_id effect_teleglow( "teleglow" ); | |
| const efftype_id effect_sleep( "sleep" ); | |
| const efftype_id effect_under_op( "under_operation" ); | |
| static const trait_id trait_AMORPHOUS( "AMORPHOUS" ); | |
| static const trait_id trait_ARACHNID_ARMS_OK( "ARACHNID_ARMS_OK" ); | |
| static const trait_id trait_BADKNEES( "BADKNEES" ); | |
| static const trait_id trait_BURROW( "BURROW" ); | |
| static const trait_id trait_ILLITERATE( "ILLITERATE" ); | |
| static const trait_id trait_INSECT_ARMS_OK( "INSECT_ARMS_OK" ); | |
| static const trait_id trait_M_DEFENDER( "M_DEFENDER" ); | |
| static const trait_id trait_M_DEPENDENT( "M_DEPENDENT" ); | |
| static const trait_id trait_M_FERTILE( "M_FERTILE" ); | |
| static const trait_id trait_M_SPORES( "M_SPORES" ); | |
| static const trait_id trait_NOPAIN( "NOPAIN" ); | |
| static const trait_id trait_PARKOUR( "PARKOUR" ); | |
| static const trait_id trait_SHELL2( "SHELL2" ); | |
| static const trait_id trait_THRESH_MARLOSS( "THRESH_MARLOSS" ); | |
| static const trait_id trait_THRESH_MYCUS( "THRESH_MYCUS" ); | |
| const trap_str_id tr_unfinished_construction( "tr_unfinished_construction" ); | |
| const zone_type_id z_loot_unsorted( "LOOT_UNSORTED" ); | |
| static void pick_plant( player &p, const tripoint &examp, const std::string &itemType, | |
| ter_id new_ter, | |
| bool seeds = false ); | |
| /** | |
| * Nothing player can interact with here. | |
| */ | |
| void iexamine::none( player &p, const tripoint &examp ) | |
| { | |
| ( void )p; //unused | |
| add_msg( _( "That is a %s." ), g->m.name( examp ) ); | |
| } | |
| /** | |
| * Pick an appropriate item and apply diamond coating if possible. | |
| */ | |
| void iexamine::cvdmachine( player &p, const tripoint & ) | |
| { | |
| // Select an item to which it is possible to apply a diamond coating | |
| auto loc = g->inv_map_splice( []( const item & e ) { | |
| return ( e.is_melee( DT_CUT ) || e.is_melee( DT_STAB ) ) && e.made_of( material_id( "steel" ) ) && | |
| !e.has_flag( "DIAMOND" ) && !e.has_flag( "NO_CVD" ); | |
| }, _( "Apply diamond coating" ), 1, _( "You don't have a suitable item to coat with diamond" ) ); | |
| if( !loc ) { | |
| return; | |
| } | |
| // Require materials proportional to selected item volume | |
| auto qty = loc->volume() / units::legacy_volume_factor; | |
| qty = std::max( 1, qty ); | |
| auto reqs = *requirement_id( "cvd_diamond" ) * qty; | |
| if( !reqs.can_make_with_inventory( p.crafting_inventory(), is_crafting_component ) ) { | |
| popup( "%s", reqs.list_missing() ); | |
| return; | |
| } | |
| // Consume materials | |
| for( const auto &e : reqs.get_components() ) { | |
| p.consume_items( e, 1, is_crafting_component ); | |
| } | |
| for( const auto &e : reqs.get_tools() ) { | |
| p.consume_tools( e ); | |
| } | |
| p.invalidate_crafting_inventory(); | |
| // Apply flag to item | |
| loc->item_tags.insert( "DIAMOND" ); | |
| add_msg( m_good, _( "You apply a diamond coating to your %s" ), loc->type_name() ); | |
| p.mod_moves( -to_turns<int>( 10_seconds ) ); | |
| } | |
| /** | |
| * UI FOR LAB_FINALE NANO FABRICATOR. | |
| */ | |
| void iexamine::nanofab( player &p, const tripoint &examp ) | |
| { | |
| bool table_exists = false; | |
| tripoint spawn_point; | |
| for( const auto &valid_location : g->m.points_in_radius( examp, 1, 0 ) ) { | |
| if( g->m.ter( valid_location ) == ter_str_id( "t_nanofab_body" ) ) { | |
| spawn_point = valid_location; | |
| table_exists = true; | |
| break; | |
| } | |
| } | |
| if( !table_exists ) { | |
| return; | |
| } | |
| auto nanofab_template = g->inv_map_splice( []( const item & e ) { | |
| return e.has_var( "NANOFAB_ITEM_ID" ); | |
| }, _( "Introduce Nanofabricator template" ), PICKUP_RANGE, | |
| _( "You don't have any usable templates." ) ); | |
| if( !nanofab_template ) { | |
| return; | |
| } | |
| item new_item( nanofab_template->get_var( "NANOFAB_ITEM_ID" ), calendar::turn ); | |
| auto qty = std::max( 1, new_item.volume() / 250_ml ); | |
| auto reqs = *requirement_id( "nanofabricator" ) * qty; | |
| if( !reqs.can_make_with_inventory( p.crafting_inventory(), is_crafting_component ) ) { | |
| popup( "%s", reqs.list_missing() ); | |
| return; | |
| } | |
| // Consume materials | |
| for( const auto &e : reqs.get_components() ) { | |
| p.consume_items( e, 1, is_crafting_component ); | |
| } | |
| for( const auto &e : reqs.get_tools() ) { | |
| p.consume_tools( e ); | |
| } | |
| p.invalidate_crafting_inventory(); | |
| if( new_item.is_armor() && new_item.has_flag( "VARSIZE" ) ) { | |
| new_item.item_tags.insert( "FIT" ); | |
| } | |
| g->m.add_item_or_charges( spawn_point, new_item ); | |
| } | |
| /** | |
| * Use "gas pump." Will pump any liquids on tile. | |
| */ | |
| void iexamine::gaspump( player &p, const tripoint &examp ) | |
| { | |
| if( !query_yn( _( "Use the %s?" ), g->m.tername( examp ) ) ) { | |
| none( p, examp ); | |
| return; | |
| } | |
| auto items = g->m.i_at( examp ); | |
| for( auto item_it = items.begin(); item_it != items.end(); ++item_it ) { | |
| if( item_it->made_of( LIQUID ) ) { | |
| ///\EFFECT_DEX decreases chance of spilling gas from a pump | |
| if( one_in( 10 + p.get_dex() ) ) { | |
| add_msg( m_bad, _( "You accidentally spill the %s." ), item_it->type_name() ); | |
| static const auto max_spill_volume = units::from_liter( 1 ); | |
| const int max_spill_charges = std::max( 1, item_it->charges_per_volume( max_spill_volume ) ); | |
| ///\EFFECT_DEX decreases amount of gas spilled from a pump | |
| const int qty = rng( 1, max_spill_charges * 8.0 / std::max( 1, p.get_dex() ) ); | |
| item spill = item_it->split( qty ); | |
| if( spill.is_null() ) { | |
| g->m.add_item_or_charges( p.pos(), *item_it ); | |
| items.erase( item_it ); | |
| } else { | |
| g->m.add_item_or_charges( p.pos(), spill ); | |
| } | |
| } else { | |
| liquid_handler::handle_liquid_from_ground( item_it, examp, 1 ); | |
| } | |
| return; | |
| } | |
| } | |
| add_msg( m_info, _( "Out of order." ) ); | |
| } | |
| void iexamine::translocator( player &, const tripoint &examp ) | |
| { | |
| const tripoint omt_loc = ms_to_omt_copy( g->m.getabs( examp ) ); | |
| const bool activated = g->u.translocators.knows_translocator( examp ); | |
| if( !activated ) { | |
| g->u.translocators.activate_teleporter( omt_loc, examp ); | |
| add_msg( m_info, _( "Translocator gate active." ) ); | |
| } else { | |
| if( query_yn( _( "Do you want to deactivate this active Translocator?" ) ) ) { | |
| g->u.translocators.deactivate_teleporter( omt_loc, examp ); | |
| } | |
| } | |
| } | |
| namespace | |
| { | |
| //-------------------------------------------------------------------------------------------------- | |
| //! Implements iexamine::atm(...) | |
| //-------------------------------------------------------------------------------------------------- | |
| class atm_menu | |
| { | |
| public: | |
| // menu choices | |
| enum options : int { | |
| cancel, purchase_card, deposit_money, withdraw_money, transfer_all_money | |
| }; | |
| atm_menu() = delete; | |
| atm_menu( atm_menu const & ) = delete; | |
| atm_menu( atm_menu && ) = delete; | |
| atm_menu &operator=( atm_menu const & ) = delete; | |
| atm_menu &operator=( atm_menu && ) = delete; | |
| explicit atm_menu( player &p ) : u( p ) { | |
| reset( false ); | |
| } | |
| void start() { | |
| for( bool result = false; !result; ) { | |
| switch( choose_option() ) { | |
| case purchase_card: | |
| result = do_purchase_card(); | |
| break; | |
| case deposit_money: | |
| result = do_deposit_money(); | |
| break; | |
| case withdraw_money: | |
| result = do_withdraw_money(); | |
| break; | |
| case transfer_all_money: | |
| result = do_transfer_all_money(); | |
| break; | |
| default: | |
| return; | |
| } | |
| if( !u.activity.is_null() ) { | |
| break; | |
| } | |
| g->draw(); | |
| } | |
| } | |
| private: | |
| void add_choice( const int i, const char *const title ) { | |
| amenu.addentry( i, true, -1, title ); | |
| } | |
| void add_info( const int i, const char *const title ) { | |
| amenu.addentry( i, false, -1, title ); | |
| } | |
| options choose_option() { | |
| if( u.activity.id() == activity_id( "ACT_ATM" ) ) { | |
| return static_cast<options>( u.activity.index ); | |
| } | |
| amenu.query(); | |
| uistate.iexamine_atm_selected = amenu.selected; | |
| return amenu.ret < 0 ? cancel : static_cast<options>( amenu.ret ); | |
| } | |
| //! Reset and repopulate the menu; with a fair bit of work this could be more efficient. | |
| void reset( const bool clear = true ) { | |
| const int card_count = u.amount_of( "cash_card" ); | |
| const int charge_count = card_count ? u.charges_of( "cash_card" ) : 0; | |
| if( clear ) { | |
| amenu.reset(); | |
| } | |
| amenu.selected = uistate.iexamine_atm_selected; | |
| amenu.text = string_format( _( "Welcome to the C.C.B.o.t.T. ATM. What would you like to do?\n" | |
| "Your current balance is: %s" ), | |
| format_money( u.cash ) ); | |
| if( u.cash >= 100 ) { | |
| add_choice( purchase_card, _( "Purchase cash card?" ) ); | |
| } else { | |
| add_info( purchase_card, _( "You need $1.00 in your account to purchase a card." ) ); | |
| } | |
| if( card_count && u.cash > 0 ) { | |
| add_choice( withdraw_money, _( "Withdraw Money" ) ); | |
| } else if( u.cash > 0 ) { | |
| add_info( withdraw_money, _( "You need a cash card before you can withdraw money!" ) ); | |
| } else if( u.cash < 0 ) { | |
| add_info( withdraw_money, | |
| _( "You need to pay down your debt first!" ) ); | |
| } else { | |
| add_info( withdraw_money, | |
| _( "You need money in your account before you can withdraw money!" ) ); | |
| } | |
| if( charge_count ) { | |
| add_choice( deposit_money, _( "Deposit Money" ) ); | |
| } else { | |
| add_info( deposit_money, | |
| _( "You need a charged cash card before you can deposit money!" ) ); | |
| } | |
| if( card_count >= 2 && charge_count ) { | |
| add_choice( transfer_all_money, _( "Transfer All Money" ) ); | |
| } | |
| } | |
| //! print a bank statement for @p print = true; | |
| void finish_interaction( const bool print = true ) { | |
| if( print ) { | |
| if( u.cash < 0 ) { | |
| add_msg( m_info, _( "Your debt is now %s." ), format_money( u.cash ) ); | |
| } else { | |
| add_msg( m_info, _( "Your account now holds %s." ), format_money( u.cash ) ); | |
| } | |
| } | |
| u.moves -= to_turns<int>( 5_seconds ); | |
| } | |
| //! Prompt for an integral value clamped to [0, max]. | |
| static int prompt_for_amount( const char *const msg, const int max ) { | |
| const std::string formatted = string_format( msg, max ); | |
| const int amount = string_input_popup() | |
| .title( formatted ) | |
| .width( 20 ) | |
| .text( to_string( max ) ) | |
| .only_digits( true ) | |
| .query_int(); | |
| return ( amount > max ) ? max : ( amount <= 0 ) ? 0 : amount; | |
| } | |
| //!Get a new cash card. $1.00 fine. | |
| bool do_purchase_card() { | |
| const char *prompt = _( "This will automatically deduct $1.00 from your bank account. Continue?" ); | |
| if( !query_yn( prompt ) ) { | |
| return false; | |
| } | |
| item card( "cash_card", calendar::turn ); | |
| card.charges = 0; | |
| u.i_add( card ); | |
| u.cash -= 100; | |
| u.moves -= to_turns<int>( 5_seconds ); | |
| finish_interaction(); | |
| return true; | |
| } | |
| //!Deposit money from cash card into bank account. | |
| bool do_deposit_money() { | |
| int money = u.charges_of( "cash_card" ); | |
| if( !money ) { | |
| popup( _( "You can only deposit money from charged cash cards!" ) ); | |
| return false; | |
| } | |
| const int amount = prompt_for_amount( ngettext( | |
| "Deposit how much? Max: %d cent. (0 to cancel) ", | |
| "Deposit how much? Max: %d cents. (0 to cancel) ", money ), money ); | |
| if( !amount ) { | |
| return false; | |
| } | |
| add_msg( m_info, "amount: %d", amount ); | |
| u.use_charges( "cash_card", amount ); | |
| u.cash += amount; | |
| u.moves -= to_turns<int>( 10_seconds ); | |
| finish_interaction(); | |
| return true; | |
| } | |
| //!Move money from bank account onto cash card. | |
| bool do_withdraw_money() { | |
| //We may want to use visit_items here but thats fairly heavy. | |
| //For now, just check weapon if we didnt find it in the inventory. | |
| int pos = u.inv.position_by_type( "cash_card" ); | |
| item *dst; | |
| if( pos == INT_MIN ) { | |
| dst = &u.weapon; | |
| } else { | |
| dst = &u.i_at( pos ); | |
| } | |
| if( dst->is_null() ) { | |
| //Just in case we run into an edge case | |
| popup( _( "You do not have a cash card to withdraw money!" ) ); | |
| return false; | |
| } | |
| const int amount = prompt_for_amount( ngettext( | |
| "Withdraw how much? Max: %d cent. (0 to cancel) ", | |
| "Withdraw how much? Max: %d cents. (0 to cancel) ", u.cash ), u.cash ); | |
| if( !amount ) { | |
| return false; | |
| } | |
| dst->charges += amount; | |
| u.cash -= amount; | |
| u.moves -= to_turns<int>( 10_seconds ); | |
| finish_interaction(); | |
| return true; | |
| } | |
| //!Move the money from all the cash cards in inventory to a single card. | |
| bool do_transfer_all_money() { | |
| item *dst; | |
| if( u.activity.id() == activity_id( "ACT_ATM" ) ) { | |
| u.activity.set_to_null(); // stop for now, if required, it will be created again. | |
| dst = &u.i_at( u.activity.position ); | |
| if( dst->is_null() || dst->typeId() != "cash_card" ) { | |
| return false; | |
| } | |
| } else { | |
| const int pos = u.inv.position_by_type( "cash_card" ); | |
| if( pos == INT_MIN ) { | |
| return false; | |
| } | |
| dst = &u.i_at( pos ); | |
| } | |
| for( auto &i : u.inv_dump() ) { | |
| if( i == dst || i->charges <= 0 || i->typeId() != "cash_card" ) { | |
| continue; | |
| } | |
| if( u.moves < 0 ) { | |
| // Money from `*i` could be transferred, but we're out of moves, schedule it for | |
| // the next turn. Putting this here makes sure there will be something to be | |
| // done next turn. | |
| u.assign_activity( activity_id( "ACT_ATM" ), 0, transfer_all_money, u.get_item_position( dst ) ); | |
| break; | |
| } | |
| dst->charges += i->charges; | |
| i->charges = 0; | |
| u.moves -= 10; | |
| } | |
| return true; | |
| } | |
| player &u; | |
| uilist amenu; | |
| }; | |
| } //namespace | |
| /** | |
| * launches the atm menu class which then handles all the atm interactions. | |
| */ | |
| void iexamine::atm( player &p, const tripoint & ) | |
| { | |
| atm_menu {p} .start(); | |
| } | |
| /** | |
| * Generates vending machine UI and allows players to purchase contained items with a cash card. | |
| */ | |
| void iexamine::vending( player &p, const tripoint &examp ) | |
| { | |
| constexpr int moves_cost = to_turns<int>( 5_seconds ); | |
| int money = p.charges_of( "cash_card" ); | |
| auto vend_items = g->m.i_at( examp ); | |
| if( vend_items.empty() ) { | |
| add_msg( m_info, _( "The vending machine is empty!" ) ); | |
| return; | |
| } else if( !money ) { | |
| popup( _( "You need a charged cash card to purchase things!" ) ); | |
| return; | |
| } | |
| const int padding_x = std::max( 0, TERMX - FULL_SCREEN_WIDTH ) / 4; | |
| const int padding_y = std::max( 0, TERMY - FULL_SCREEN_HEIGHT ) / 6; | |
| const int window_h = FULL_SCREEN_HEIGHT + std::max( 0, TERMY - FULL_SCREEN_HEIGHT ) * 2 / 3; | |
| const int window_w = FULL_SCREEN_WIDTH + std::max( 0, TERMX - FULL_SCREEN_WIDTH ) / 2; | |
| const int w_items_w = window_w / 2; | |
| const int w_info_w = window_w - w_items_w; | |
| const int list_lines = window_h - 4; // minus for header and footer | |
| constexpr int first_item_offset = 3; // header size | |
| catacurses::window const w = catacurses::newwin( window_h, w_items_w, padding_y, padding_x ); | |
| catacurses::window const w_item_info = catacurses::newwin( window_h, w_info_w, padding_y, | |
| padding_x + w_items_w ); | |
| bool used_machine = false; | |
| input_context ctxt( "VENDING_MACHINE" ); | |
| ctxt.register_updown(); | |
| ctxt.register_action( "CONFIRM" ); | |
| ctxt.register_action( "QUIT" ); | |
| ctxt.register_action( "HELP_KEYBINDINGS" ); | |
| // Collate identical items. | |
| // First, build a map {item::tname} => {item_it, item_it, item_it...} | |
| using iterator_t = decltype( std::begin( vend_items ) ); // map_stack::iterator doesn't exist. | |
| std::map<std::string, std::vector<iterator_t>> item_map; | |
| for( auto it = std::begin( vend_items ); it != std::end( vend_items ); ++it ) { | |
| // |# {name}| | |
| // 123 4 | |
| item_map[utf8_truncate( it->tname(), static_cast<size_t>( w_items_w - 4 ) )].push_back( it ); | |
| } | |
| // Next, put pointers to the pairs in the map in a vector to allow indexing. | |
| std::vector<std::map<std::string, std::vector<iterator_t>>::value_type *> item_list; | |
| item_list.reserve( item_map.size() ); | |
| for( auto &pair : item_map ) { | |
| item_list.emplace_back( &pair ); | |
| } | |
| const int lines_above = list_lines / 2; // lines above the selector | |
| const int lines_below = list_lines / 2 + list_lines % 2; // lines below the selector | |
| int cur_pos = 0; | |
| for( ;; ) { | |
| const int num_items = item_list.size(); | |
| const int page_size = std::min( num_items, list_lines ); | |
| werase( w ); | |
| wborder( w, LINE_XOXO, LINE_XOXO, LINE_OXOX, LINE_OXOX, | |
| LINE_OXXO, LINE_OOXX, LINE_XXOO, LINE_XOOX ); | |
| mvwhline( w, first_item_offset - 1, 1, LINE_OXOX, w_items_w - 2 ); | |
| mvwaddch( w, first_item_offset - 1, 0, LINE_XXXO ); // |- | |
| mvwaddch( w, first_item_offset - 1, w_items_w - 1, LINE_XOXX ); // -| | |
| trim_and_print( w, 1, 2, w_items_w - 3, c_light_gray, | |
| _( "Money left: %s" ), format_money( money ) ); | |
| // Keep the item selector centered in the page. | |
| int page_beg; | |
| if( cur_pos < num_items - cur_pos ) { | |
| page_beg = std::max( 0, cur_pos - lines_above ); | |
| } else { | |
| int page_end = std::min( num_items, cur_pos + lines_below ); | |
| page_beg = std::max( 0, page_end - list_lines ); | |
| } | |
| for( int line = 0; line < page_size; ++line ) { | |
| const int i = page_beg + line; | |
| const auto color = ( i == cur_pos ) ? h_light_gray : c_light_gray; | |
| const auto &elem = item_list[i]; | |
| const int count = elem->second.size(); | |
| const char c = ( count < 10 ) ? ( '0' + count ) : '*'; | |
| trim_and_print( w, first_item_offset + line, 1, w_items_w - 3, color, "%c %s", c, | |
| elem->first.c_str() ); | |
| } | |
| draw_scrollbar( w, cur_pos, list_lines, num_items, first_item_offset ); | |
| wrefresh( w ); | |
| // Item info | |
| auto &cur_items = item_list[static_cast<size_t>( cur_pos )]->second; | |
| auto &cur_item = cur_items.back(); | |
| werase( w_item_info ); | |
| // | {line}| | |
| // 12 3 | |
| fold_and_print( w_item_info, 1, 2, w_info_w - 3, c_light_gray, cur_item->info( true ) ); | |
| wborder( w_item_info, LINE_XOXO, LINE_XOXO, LINE_OXOX, LINE_OXOX, | |
| LINE_OXXO, LINE_OOXX, LINE_XXOO, LINE_XOOX ); | |
| //+<{name}>+ | |
| //12 34 | |
| const std::string name = utf8_truncate( cur_item->display_name(), | |
| static_cast<size_t>( w_info_w - 4 ) ); | |
| mvwprintw( w_item_info, 0, 1, "<%s>", name ); | |
| wrefresh( w_item_info ); | |
| const std::string &action = ctxt.handle_input(); | |
| if( action == "DOWN" ) { | |
| cur_pos = ( cur_pos + 1 ) % num_items; | |
| } else if( action == "UP" ) { | |
| cur_pos = ( cur_pos + num_items - 1 ) % num_items; | |
| } else if( action == "CONFIRM" ) { | |
| const int iprice = cur_item->price( false ); | |
| if( iprice > money ) { | |
| popup( _( "That item is too expensive!" ) ); | |
| continue; | |
| } | |
| if( !used_machine ) { | |
| used_machine = true; | |
| p.moves -= moves_cost; | |
| } | |
| money -= iprice; | |
| p.use_charges( "cash_card", iprice ); | |
| p.i_add_or_drop( *cur_item ); | |
| vend_items.erase( cur_item ); | |
| cur_items.pop_back(); | |
| if( !cur_items.empty() ) { | |
| continue; | |
| } | |
| item_list.erase( std::begin( item_list ) + cur_pos ); | |
| if( item_list.empty() ) { | |
| add_msg( _( "With a beep, the empty vending machine shuts down" ) ); | |
| return; | |
| } else if( cur_pos == num_items - 1 ) { | |
| cur_pos--; | |
| } | |
| } else if( action == "QUIT" ) { | |
| break; | |
| } | |
| } | |
| } | |
| /** | |
| * If there's water, allow its usage but add chance of poison. | |
| */ | |
| void iexamine::toilet( player &p, const tripoint &examp ) | |
| { | |
| auto items = g->m.i_at( examp ); | |
| auto water = items.begin(); | |
| for( ; water != items.end(); ++water ) { | |
| if( water->typeId() == "water" ) { | |
| break; | |
| } | |
| } | |
| if( water == items.end() ) { | |
| add_msg( m_info, _( "This toilet is empty." ) ); | |
| } else if( !water->made_of( LIQUID ) ) { | |
| add_msg( m_info, _( "The toilet water is frozen solid!" ) ); | |
| } else { | |
| // Use a different poison value each time water is drawn from the toilet. | |
| water->poison = one_in( 3 ) ? 0 : rng( 1, 3 ); | |
| ( void ) p; // TODO: use me | |
| liquid_handler::handle_liquid_from_ground( water, examp ); | |
| } | |
| } | |
| /** | |
| * If underground, move 2 levels up else move 2 levels down. Stable movement between levels 0 and -2. | |
| */ | |
| void iexamine::elevator( player &p, const tripoint &examp ) | |
| { | |
| ( void )p; //unused | |
| if( !query_yn( _( "Use the %s?" ), g->m.tername( examp ) ) ) { | |
| return; | |
| } | |
| int movez = ( examp.z < 0 ? 2 : -2 ); | |
| g->vertical_move( movez, false ); | |
| } | |
| /** | |
| * Open gate. | |
| */ | |
| void iexamine::controls_gate( player &p, const tripoint &examp ) | |
| { | |
| if( !query_yn( _( "Use the %s?" ), g->m.tername( examp ) ) ) { | |
| none( p, examp ); | |
| return; | |
| } | |
| g->open_gate( examp ); | |
| } | |
| /** | |
| * Use id/hack reader. Using an id despawns turrets. | |
| */ | |
| void iexamine::cardreader( player &p, const tripoint &examp ) | |
| { | |
| bool open = false; | |
| itype_id card_type = ( g->m.ter( examp ) == t_card_science ? "id_science" : | |
| g->m.ter( examp ) == t_card_military ? "id_military" : "id_industrial" ); | |
| if( p.has_amount( card_type, 1 ) && query_yn( _( "Swipe your ID card?" ) ) ) { | |
| p.mod_moves( -to_turns<int>( 1_seconds ) ); | |
| for( const tripoint &tmp : g->m.points_in_radius( examp, 3 ) ) { | |
| if( g->m.ter( tmp ) == t_door_metal_locked ) { | |
| g->m.ter_set( tmp, t_floor ); | |
| open = true; | |
| } | |
| } | |
| for( monster &critter : g->all_monsters() ) { | |
| // Check 1) same overmap coords, 2) turret, 3) hostile | |
| if( ms_to_omt_copy( g->m.getabs( critter.pos() ) ) == ms_to_omt_copy( g->m.getabs( examp ) ) && | |
| ( critter.type->id == mon_turret || | |
| critter.type->id == mon_turret_rifle ) && | |
| critter.attitude_to( p ) == Creature::Attitude::A_HOSTILE ) { | |
| g->remove_zombie( critter ); | |
| } | |
| } | |
| if( open ) { | |
| add_msg( _( "You insert your ID card." ) ); | |
| add_msg( m_good, _( "The nearby doors slide into the floor." ) ); | |
| p.use_amount( card_type, 1 ); | |
| } else { | |
| add_msg( _( "The nearby doors are already opened." ) ); | |
| } | |
| } else { | |
| p.assign_activity( activity_id( "ACT_HACK_DOOR" ), to_moves<int>( 5_minutes ) ); | |
| p.activity.placement = examp; | |
| } | |
| } | |
| void iexamine::cardreader_robofac( player &p, const tripoint &examp ) | |
| { | |
| itype_id card_type = "id_science"; | |
| if( p.has_amount( card_type, 1 ) && query_yn( _( "Swipe your ID card?" ) ) ) { | |
| p.mod_moves( -100 ); | |
| p.use_amount( card_type, 1 ); | |
| add_msg( m_bad, _( "The card reader short circuits!" ) ); | |
| g->m.ter_set( examp, t_card_reader_broken ); | |
| intercom( p, examp ); | |
| } else { | |
| switch( hack_attempt( p ) ) { | |
| case HACK_FAIL: | |
| case HACK_NOTHING: | |
| add_msg( _( "Nothing happens." ) ); | |
| break; | |
| case HACK_SUCCESS: { | |
| add_msg( _( "You activate the panel!" ) ); | |
| add_msg( m_bad, _( "The card reader short circuits!" ) ); | |
| g->m.ter_set( examp, t_card_reader_broken ); | |
| intercom( p, examp ); | |
| } | |
| break; | |
| case HACK_UNABLE: | |
| add_msg( | |
| m_info, | |
| p.get_skill_level( skill_computer ) > 0 ? | |
| _( "Looks like you need a %s, or a tool to hack it with." ) : | |
| _( "Looks like you need a %s." ), | |
| item::nname( card_type ) | |
| ); | |
| break; | |
| } | |
| } | |
| } | |
| void iexamine::cardreader_foodplace( player &p, const tripoint &examp ) | |
| { | |
| bool open = false; | |
| if( ( p.is_wearing( itype_id( "foodperson_mask" ) ) || | |
| p.is_wearing( itype_id( "foodperson_mask_on" ) ) ) && | |
| query_yn( _( "Press mask on the reader?" ) ) ) { | |
| p.mod_moves( -100 ); | |
| for( const tripoint &tmp : g->m.points_in_radius( examp, 3 ) ) { | |
| if( g->m.ter( tmp ) == t_door_metal_locked ) { | |
| g->m.ter_set( tmp, t_door_metal_c ); | |
| open = true; | |
| } | |
| } | |
| if( open ) { | |
| add_msg( _( "You press your face on the reader." ) ); | |
| add_msg( m_good, _( "The nearby doors are unlocked." ) ); | |
| sounds::sound( examp, 6, sounds::sound_t::speech, _( "\"Hello Foodperson. Welcome home.\"" ), true, | |
| "speech", "welcome" ); | |
| } else { | |
| add_msg( _( "The nearby doors are already unlocked." ) ); | |
| if( query_yn( _( "Lock doors?" ) ) ) { | |
| for( const tripoint &tmp : g->m.points_in_radius( examp, 3 ) ) { | |
| if( g->m.ter( tmp ) == t_door_metal_o || g->m.ter( tmp ) == t_door_metal_c ) { | |
| if( p.pos() == tmp ) { | |
| p.add_msg_if_player( m_bad, _( "You are in the way of the door, move before trying again." ) ); | |
| } else { | |
| g->m.ter_set( tmp, t_door_metal_locked ); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } else if( p.has_amount( itype_id( "foodperson_mask" ), 1 ) || | |
| p.has_amount( itype_id( "foodperson_mask_on" ), 1 ) ) { | |
| sounds::sound( examp, 6, sounds::sound_t::speech, | |
| _( "\"FOODPERSON DETECTED. Please make yourself presentable.\"" ), true, | |
| "speech", "welcome" ); | |
| } else { | |
| sounds::sound( examp, 6, sounds::sound_t::speech, | |
| _( "\"Your face is inadequate. Please go away.\"" ), true, | |
| "speech", "welcome" ); | |
| p.assign_activity( activity_id( "ACT_HACK_DOOR" ), to_moves<int>( 5_minutes ) ); | |
| p.activity.placement = examp; | |
| } | |
| } | |
| void iexamine::intercom( player &p, const tripoint &examp ) | |
| { | |
| const std::vector<npc *> intercom_npcs = g->get_npcs_if( [examp]( const npc & guy ) { | |
| return guy.name == "the intercom" && rl_dist( guy.pos(), examp ) < 10; | |
| } ); | |
| if( intercom_npcs.empty() ) { | |
| p.add_msg_if_player( m_info, _( "No one responds." ) ); | |
| } else { | |
| intercom_npcs.front()->talk_to_u( false, false ); | |
| } | |
| } | |
| /** | |
| * Prompt removal of rubble. Select best shovel and invoke "CLEAR_RUBBLE" on tile. | |
| */ | |
| void iexamine::rubble( player &p, const tripoint &examp ) | |
| { | |
| int moves; | |
| if( p.has_quality( quality_id( "DIG" ), 3 ) || p.has_trait( trait_BURROW ) ) { | |
| moves = to_turns<int>( 1_minutes ); | |
| } else if( p.has_quality( quality_id( "DIG" ), 2 ) ) { | |
| moves = to_turns<int>( 2_minutes ); | |
| } else { | |
| add_msg( m_info, _( "If only you had a shovel..." ) ); | |
| return; | |
| } | |
| if( ( g->m.veh_at( examp ) || !g->m.tr_at( examp ).is_null() || | |
| g->critter_at( examp ) != nullptr ) && | |
| !query_yn( _( "Clear up that %s?" ), g->m.furnname( examp ) ) ) { | |
| return; | |
| } | |
| p.assign_activity( activity_id( "ACT_CLEAR_RUBBLE" ), moves, -1, 0 ); | |
| p.activity.placement = examp; | |
| return; | |
| } | |
| /** | |
| * Prompt climbing over fence. Calculates move cost, applies it to player and, moves them. | |
| */ | |
| void iexamine::chainfence( player &p, const tripoint &examp ) | |
| { | |
| // Skip prompt if easy to climb. | |
| if( !g->m.has_flag( "CLIMB_SIMPLE", examp ) ) { | |
| if( !query_yn( _( "Climb %s?" ), g->m.tername( examp ) ) ) { | |
| none( p, examp ); | |
| return; | |
| } | |
| } | |
| if( g->m.has_flag( "CLIMB_SIMPLE", examp ) && p.has_trait( trait_PARKOUR ) ) { | |
| add_msg( _( "You vault over the obstacle with ease." ) ); | |
| p.moves -= 100; // Not tall enough to warrant spider-climbing, so only relevant trait. | |
| } else if( g->m.has_flag( "CLIMB_SIMPLE", examp ) ) { | |
| add_msg( _( "You vault over the obstacle." ) ); | |
| p.moves -= 300; // Most common move cost for barricades pre-change. | |
| } else if( p.has_trait( trait_ARACHNID_ARMS_OK ) && !p.wearing_something_on( bp_torso ) ) { | |
| add_msg( _( "Climbing this obstacle is trivial for one such as you." ) ); | |
| p.moves -= 75; // Yes, faster than walking. 6-8 limbs are impressive. | |
| } else if( p.has_trait( trait_INSECT_ARMS_OK ) && !p.wearing_something_on( bp_torso ) ) { | |
| add_msg( _( "You quickly scale the fence." ) ); | |
| p.moves -= 90; | |
| } else if( p.has_trait( trait_PARKOUR ) ) { | |
| add_msg( _( "This obstacle is no match for your freerunning abilities." ) ); | |
| p.moves -= 100; | |
| } else { | |
| p.moves -= 400; | |
| ///\EFFECT_DEX decreases chances of slipping while climbing | |
| int climb = p.dex_cur; | |
| if( p.has_trait( trait_BADKNEES ) ) { | |
| climb = climb / 2; | |
| } | |
| if( one_in( climb ) ) { | |
| add_msg( m_bad, _( "You slip while climbing and fall down again." ) ); | |
| if( climb <= 1 ) { | |
| add_msg( m_bad, _( "Climbing this obstacle is impossible in your current state." ) ); | |
| } | |
| return; | |
| } | |
| p.moves += climb * 10; | |
| sfx::play_variant_sound( "plmove", "clear_obstacle", sfx::get_heard_volume( g->u.pos() ) ); | |
| } | |
| if( p.in_vehicle ) { | |
| g->m.unboard_vehicle( p.pos() ); | |
| } | |
| p.setpos( examp ); | |
| if( examp.x < HALF_MAPSIZE_X || examp.y < HALF_MAPSIZE_Y || | |
| examp.x >= HALF_MAPSIZE_X + SEEX || examp.y >= HALF_MAPSIZE_Y + SEEY ) { | |
| if( p.is_player() ) { | |
| g->update_map( p ); | |
| } | |
| } | |
| } | |
| /** | |
| * If player has amorphous trait, slip through the bars. | |
| */ | |
| void iexamine::bars( player &p, const tripoint &examp ) | |
| { | |
| if( !( p.has_trait( trait_AMORPHOUS ) ) ) { | |
| none( p, examp ); | |
| return; | |
| } | |
| if( ( ( p.encumb( bp_torso ) ) >= 10 ) && ( ( p.encumb( bp_head ) ) >= 10 ) && | |
| ( p.encumb( bp_foot_l ) >= 10 || | |
| p.encumb( bp_foot_r ) >= 10 ) ) { // Most likely places for rigid gear that would catch on the bars. | |
| add_msg( m_info, | |
| _( "Your amorphous body could slip though the %s, but your cumbersome gear can't." ), | |
| g->m.tername( examp ) ); | |
| return; | |
| } | |
| if( !query_yn( _( "Slip through the %s?" ), g->m.tername( examp ) ) ) { | |
| none( p, examp ); | |
| return; | |
| } | |
| p.moves -= to_turns<int>( 2_seconds ); | |
| add_msg( _( "You slide right between the bars." ) ); | |
| p.setpos( examp ); | |
| } | |
| void iexamine::deployed_furniture( player &p, const tripoint &pos ) | |
| { | |
| if( !query_yn( _( "Take down the %s?" ), g->m.furn( pos ).obj().name() ) ) { | |
| return; | |
| } | |
| p.add_msg_if_player( m_info, _( "You take down the %s." ), | |
| g->m.furn( pos ).obj().name() ); | |
| const auto furn_item = g->m.furn( pos ).obj().deployed_item; | |
| g->m.add_item_or_charges( pos, item( furn_item, calendar::turn ) ); | |
| g->m.furn_set( pos, f_null ); | |
| } | |
| static std::pair<itype_id, const deploy_tent_actor *> find_tent_itype( const furn_str_id &id ) | |
| { | |
| const itype_id &iid = id->deployed_item; | |
| if( item::type_is_defined( iid ) ) { | |
| const itype &type = *item::find_type( iid ); | |
| for( const auto &pair : type.use_methods ) { | |
| const auto actor = dynamic_cast<const deploy_tent_actor *>( pair.second.get_actor_ptr() ); | |
| if( !actor ) { | |
| continue; | |
| } | |
| if( ( actor->floor_center && *actor->floor_center == id ) || ( !actor->floor_center && | |
| actor->floor == id ) ) { | |
| return std::make_pair( iid, actor ); | |
| } | |
| } | |
| } | |
| return std::make_pair( iid, nullptr ); | |
| } | |
| /** | |
| * Determine structure's type and prompts its removal. | |
| */ | |
| void iexamine::portable_structure( player &p, const tripoint &examp ) | |
| { | |
| const furn_str_id fid = g->m.furn( examp ).id(); | |
| const std::pair<itype_id, const deploy_tent_actor *> tent_item_type = find_tent_itype( fid ); | |
| if( tent_item_type.first == "null" ) { | |
| debugmsg( "unknown furniture %s: don't know how to transform it into an item", fid.str() ); | |
| return; | |
| } | |
| itype_id dropped = tent_item_type.first; | |
| std::string name = item::nname( dropped ); | |
| int radius; | |
| if( tent_item_type.second ) { | |
| const deploy_tent_actor &actor = *tent_item_type.second; | |
| if( !actor.check_intact( examp ) ) { | |
| if( !actor.broken_type ) { | |
| add_msg( _( "The %s is broken and can not be picked up." ), name ); | |
| none( p, examp ); | |
| return; | |
| } | |
| dropped = *actor.broken_type; | |
| name = string_format( _( "damaged %s" ), name ); | |
| } | |
| radius = actor.radius; | |
| } else { | |
| radius = std::max( 1, fid->bash.collapse_radius ); | |
| } | |
| if( !query_yn( _( "Take down the %s?" ), name ) ) { | |
| none( p, examp ); | |
| return; | |
| } | |
| p.moves -= to_turns<int>( 2_seconds ); | |
| for( const tripoint &pt : g->m.points_in_radius( examp, radius ) ) { | |
| g->m.furn_set( pt, f_null ); | |
| } | |
| g->m.add_item_or_charges( examp, item( dropped, calendar::turn ) ); | |
| } | |
| /** | |
| * If there is a 2x4 around, prompt placing it across pit. | |
| */ | |
| void iexamine::pit( player &p, const tripoint &examp ) | |
| { | |
| const inventory &crafting_inv = p.crafting_inventory(); | |
| if( !crafting_inv.has_amount( "2x4", 1 ) ) { | |
| none( p, examp ); | |
| return; | |
| } | |
| std::vector<item_comp> planks; | |
| planks.push_back( item_comp( "2x4", 1 ) ); | |
| if( query_yn( _( "Place a plank over the pit?" ) ) ) { | |
| p.consume_items( planks, 1, is_crafting_component ); | |
| if( g->m.ter( examp ) == t_pit ) { | |
| g->m.ter_set( examp, t_pit_covered ); | |
| } else if( g->m.ter( examp ) == t_pit_spiked ) { | |
| g->m.ter_set( examp, t_pit_spiked_covered ); | |
| } else if( g->m.ter( examp ) == t_pit_glass ) { | |
| g->m.ter_set( examp, t_pit_glass_covered ); | |
| } | |
| add_msg( _( "You place a plank of wood over the pit." ) ); | |
| p.mod_moves( -to_turns<int>( 1_seconds ) ); | |
| } | |
| } | |
| /** | |
| * Prompt removing the 2x4 placed across the pit | |
| */ | |
| void iexamine::pit_covered( player &p, const tripoint &examp ) | |
| { | |
| if( !query_yn( _( "Remove cover?" ) ) ) { | |
| none( p, examp ); | |
| return; | |
| } | |
| item plank( "2x4", calendar::turn ); | |
| add_msg( _( "You remove the plank." ) ); | |
| g->m.add_item_or_charges( p.pos(), plank ); | |
| if( g->m.ter( examp ) == t_pit_covered ) { | |
| g->m.ter_set( examp, t_pit ); | |
| } else if( g->m.ter( examp ) == t_pit_spiked_covered ) { | |
| g->m.ter_set( examp, t_pit_spiked ); | |
| } else if( g->m.ter( examp ) == t_pit_glass_covered ) { | |
| g->m.ter_set( examp, t_pit_glass ); | |
| } | |
| p.mod_moves( -to_turns<int>( 1_seconds ) ); | |
| } | |
| /** | |
| * Loop prompt to bet $10. | |
| */ | |
| void iexamine::slot_machine( player &p, const tripoint & ) | |
| { | |
| if( p.cash < 1000 ) { | |
| add_msg( m_info, _( "You need $10 to play." ) ); | |
| } else if( query_yn( _( "Insert $10?" ) ) ) { | |
| do { | |
| if( one_in( 5 ) ) { | |
| popup( _( "Three cherries... you get your money back!" ) ); | |
| } else if( one_in( 20 ) ) { | |
| popup( _( "Three bells... you win $50!" ) ); | |
| p.cash += 4000; // Minus the $10 we wagered | |
| } else if( one_in( 50 ) ) { | |
| popup( _( "Three stars... you win $200!" ) ); | |
| p.cash += 19000; | |
| } else if( one_in( 1000 ) ) { | |
| popup( _( "JACKPOT! You win $3000!" ) ); | |
| p.cash += 299000; | |
| } else { | |
| popup( _( "No win." ) ); | |
| p.cash -= 1000; | |
| } | |
| } while( p.cash >= 1000 && query_yn( _( "Play again?" ) ) ); | |
| } | |
| } | |
| /** | |
| * Attempt to crack safe through audio-feedback manual lock manipulation. | |
| * | |
| * Try to unlock the safe by moving the dial and listening for the mechanism to "click into place." | |
| * Time per attempt affected by perception and mechanics. 30 minutes per attempt minimum. | |
| * Small chance of just guessing the combo without listening device. | |
| */ | |
| void iexamine::safe( player &p, const tripoint &examp ) | |
| { | |
| auto cracking_tool = p.crafting_inventory().items_with( []( const item & it ) -> bool { | |
| item temporary_item( it.type ); | |
| return temporary_item.has_flag( "SAFECRACK" ); | |
| } ); | |
| if( !( !cracking_tool.empty() || p.has_bionic( bionic_id( "bio_ears" ) ) ) ) { | |
| p.moves -= to_turns<int>( 10_seconds ); | |
| // one_in(30^3) chance of guessing | |
| if( one_in( 27000 ) ) { | |
| p.add_msg_if_player( m_good, _( "You mess with the dial for a little bit... and it opens!" ) ); | |
| g->m.furn_set( examp, f_safe_o ); | |
| return; | |
| } else { | |
| p.add_msg_if_player( m_info, _( "You mess with the dial for a little bit." ) ); | |
| return; | |
| } | |
| } | |
| if( p.is_deaf() ) { | |
| add_msg( m_info, _( "You can't crack a safe while deaf!" ) ); | |
| return; | |
| } | |
| if( query_yn( _( "Attempt to crack the safe?" ) ) ) { | |
| add_msg( m_info, _( "You start cracking the safe." ) ); | |
| // 150 minutes +/- 20 minutes per mechanics point away from 3 +/- 10 minutes per | |
| // perception point away from 8; capped at 30 minutes minimum. *100 to convert to moves | |
| ///\EFFECT_PER speeds up safe cracking | |
| ///\EFFECT_MECHANICS speeds up safe cracking | |
| const time_duration time = std::max( 150_minutes - 20_minutes * ( p.get_skill_level( | |
| skill_mechanics ) - 3 ) - 10_minutes * ( p.get_per() - 8 ), 30_minutes ); | |
| p.assign_activity( activity_id( "ACT_CRACKING" ), to_moves<int>( time ) ); | |
| p.activity.placement = examp; | |
| } | |
| } | |
| /** | |
| * Attempt to pick the gunsafe's lock and open it. | |
| */ | |
| void iexamine::gunsafe_ml( player &p, const tripoint &examp ) | |
| { | |
| if( !( p.has_amount( "crude_picklock", 1 ) || p.has_amount( "hairpin", 1 ) || | |
| p.has_amount( "fc_hairpin", 1 ) || | |
| p.has_amount( "picklocks", 1 ) || p.has_bionic( bionic_id( "bio_lockpick" ) ) ) ) { | |
| add_msg( m_info, _( "You need a lockpick to open this gun safe." ) ); | |
| return; | |
| } else if( !query_yn( _( "Pick the gun safe?" ) ) ) { | |
| return; | |
| } | |
| int pick_quality = 1; | |
| if( p.has_amount( "picklocks", 1 ) || p.has_bionic( bionic_id( "bio_lockpick" ) ) ) { | |
| pick_quality = 5; | |
| } else if( p.has_amount( "fc_hairpin", 1 ) ) { | |
| pick_quality = 1; | |
| } else { | |
| pick_quality = 3; | |
| } | |
| p.practice( skill_mechanics, 1 ); | |
| ///\EFFECT_DEX speeds up lock picking gun safe | |
| ///\EFFECT_MECHANICS speeds up lock picking gun safe | |
| p.moves -= std::max( 0, to_turns<int>( 10_minutes - time_duration::from_minutes( pick_quality ) ) | |
| - ( p.dex_cur + p.get_skill_level( skill_mechanics ) ) * 5 ); | |
| ///\EFFECT_DEX increases chance of lock picking gun safe | |
| ///\EFFECT_MECHANICS increases chance of lock picking gun safe | |
| int pick_roll = ( dice( 2, p.get_skill_level( skill_mechanics ) ) + dice( 2, | |
| p.dex_cur ) ) * pick_quality; | |
| int door_roll = dice( 4, 30 ); | |
| if( pick_roll >= door_roll ) { | |
| p.practice( skill_mechanics, 1 ); | |
| add_msg( _( "You successfully unlock the gun safe." ) ); | |
| g->m.furn_set( examp, furn_str_id( "f_safe_o" ) ); | |
| } else if( door_roll > ( 3 * pick_roll ) ) { | |
| add_msg( _( "Your clumsy attempt jams the lock!" ) ); | |
| g->m.furn_set( examp, furn_str_id( "f_gunsafe_mj" ) ); | |
| } else { | |
| add_msg( _( "The gun safe stumps your efforts to pick it." ) ); | |
| } | |
| } | |
| /** | |
| * Attempt to "hack" the gunsafe's electronic lock and open it. | |
| */ | |
| void iexamine::gunsafe_el( player &p, const tripoint &examp ) | |
| { | |
| p.assign_activity( activity_id( "ACT_HACK_SAFE" ), to_moves<int>( 5_minutes ) ); | |
| p.activity.placement = examp; | |
| } | |
| /** | |
| * Checks PC has a crowbar then calls iuse.crowbar. | |
| */ | |
| void iexamine::locked_object( player &p, const tripoint &examp ) | |
| { | |
| auto prying_items = p.crafting_inventory().items_with( []( const item & it ) -> bool { | |
| item temporary_item( it.type ); | |
| return temporary_item.has_quality( quality_id( "PRY" ), 1 ); | |
| } ); | |
| if( prying_items.empty() ) { | |
| add_msg( m_info, _( "If only you had something to pry with..." ) ); | |
| return; | |
| } | |
| // Sort by their quality level. | |
| std::sort( prying_items.begin(), prying_items.end(), []( const item * a, const item * b ) -> bool { | |
| return a->get_quality( quality_id( "PRY" ) ) > b->get_quality( quality_id( "PRY" ) ); | |
| } ); | |
| p.add_msg_if_player( string_format( _( "You attempt to pry open the %s using your %s..." ), | |
| g->m.has_furn( examp ) ? g->m.furnname( examp ) : g->m.tername( examp ), | |
| prying_items[0]->tname() ) ); | |
| // if crowbar() ever eats charges or otherwise alters the passed item, rewrite this to reflect | |
| // changes to the original item. | |
| iuse dummy; | |
| item temporary_item( prying_items[0]->type ); | |
| dummy.crowbar( &p, &temporary_item, false, examp ); | |
| } | |
| void iexamine::bulletin_board( player &p, const tripoint &examp ) | |
| { | |
| g->validate_camps(); | |
| point omt = ms_to_omt_copy( g->m.getabs( examp.x, examp.y ) ); | |
| cata::optional<basecamp *> bcp = overmap_buffer.find_camp( omt ); | |
| if( bcp ) { | |
| basecamp *temp_camp = *bcp; | |
| temp_camp->validate_assignees(); | |
| temp_camp->validate_sort_points(); | |
| const std::string title = ( "Base Missions" ); | |
| mission_data mission_key; | |
| temp_camp->get_available_missions( mission_key, false ); | |
| if( talk_function::display_and_choose_opts( mission_key, temp_camp->camp_omt_pos(), | |
| "FACTION_CAMP", title ) ) { | |
| temp_camp->handle_mission( mission_key.cur_key.id, mission_key.cur_key.dir, false ); | |
| } | |
| } else { | |
| p.add_msg_if_player( _( "This bulletin board is not inside a camp" ) ); | |
| } | |
| } | |
| /** | |
| * Display popup with reference to "The Enigma of Amigara Fault." | |
| */ | |
| void iexamine::fault( player &, const tripoint & ) | |
| { | |
| popup( _( "\ | |
| This wall is perfectly vertical. Odd, twisted holes are set in it, leading\n\ | |
| as far back into the solid rock as you can see. The holes are humanoid in\n\ | |
| shape, but with long, twisted, distended limbs." ) ); | |
| } | |
| /** | |
| * Spawn 1d4 wyrms and sink pedestal into ground. | |
| */ | |
| void iexamine::pedestal_wyrm( player &p, const tripoint &examp ) | |
| { | |
| if( !g->m.i_at( examp ).empty() ) { | |
| none( p, examp ); | |
| return; | |
| } | |
| // Send in a few wyrms to start things off. | |
| g->u.add_memorial_log( pgettext( "memorial_male", "Awoke a group of dark wyrms!" ), | |
| pgettext( "memorial_female", "Awoke a group of dark wyrms!" ) ); | |
| int num_wyrms = rng( 1, 4 ); | |
| for( int i = 0; i < num_wyrms; i++ ) { | |
| int tries = 0; | |
| tripoint monp = examp; | |
| do { | |
| monp.x = rng( 0, MAPSIZE_X ); | |
| monp.y = rng( 0, MAPSIZE_Y ); | |
| tries++; | |
| } while( tries < 10 && !g->is_empty( monp ) && | |
| rl_dist( p.pos(), monp ) <= 2 ); | |
| if( tries < 10 ) { | |
| g->m.ter_set( monp, t_rock_floor ); | |
| g->summon_mon( mon_dark_wyrm, monp ); | |
| } | |
| } | |
| add_msg( _( "The pedestal sinks into the ground..." ) ); | |
| sounds::sound( examp, 80, sounds::sound_t::combat, _( "an ominous grinding noise..." ), true, | |
| "misc", "stones_grinding" ); | |
| g->m.ter_set( examp, t_rock_floor ); | |
| g->events.add( EVENT_SPAWN_WYRMS, calendar::turn + rng( 30_seconds, 60_seconds ) ); | |
| } | |
| /** | |
| * Put petrified eye on pedestal causing it to sink into ground and open temple. | |
| */ | |
| void iexamine::pedestal_temple( player &p, const tripoint &examp ) | |
| { | |
| map_stack items = g->m.i_at( examp ); | |
| if( !items.empty() && items.only_item().typeId() == "petrified_eye" ) { | |
| add_msg( _( "The pedestal sinks into the ground..." ) ); | |
| g->m.ter_set( examp, t_dirt ); | |
| g->m.i_clear( examp ); | |
| g->events.add( EVENT_TEMPLE_OPEN, calendar::turn + 10_seconds ); | |
| } else if( p.has_amount( "petrified_eye", 1 ) && | |
| query_yn( _( "Place your petrified eye on the pedestal?" ) ) ) { | |
| p.use_amount( "petrified_eye", 1 ); | |
| add_msg( _( "The pedestal sinks into the ground..." ) ); | |
| g->m.ter_set( examp, t_dirt ); | |
| g->events.add( EVENT_TEMPLE_OPEN, calendar::turn + 10_seconds ); | |
| } else { | |
| add_msg( _( "This pedestal is engraved in eye-shaped diagrams, and has a \ | |
| large semi-spherical indentation at the top." ) ); | |
| } | |
| } | |
| /** | |
| * Unlock/open door or attempt to peek through peephole. | |
| */ | |
| void iexamine::door_peephole( player &p, const tripoint &examp ) | |
| { | |
| if( g->m.is_outside( p.pos() ) ) { | |
| // if door is a locked type attempt to open | |
| if( g->m.has_flag( "OPENCLOSE_INSIDE", examp ) ) { | |
| locked_object( p, examp ); | |
| } else { | |
| p.add_msg_if_player( _( "You cannot look through the peephole from the outside." ) ); | |
| } | |
| return; | |
| } | |
| // Peek through the peephole, or open the door. | |
| const int choice = uilist( _( "Do what with the door?" ), { | |
| _( "Peek through peephole." ), | |
| _( "Open door." ) | |
| } ); | |
| if( choice == 0 ) { | |
| // Peek | |
| g->peek( examp ); | |
| p.add_msg_if_player( _( "You peek through the peephole." ) ); | |
| } else if( choice == 1 ) { | |
| g->m.open_door( examp, true, false ); | |
| p.add_msg_if_player( _( "You open the door." ) ); | |
| } else { | |
| p.add_msg_if_player( _( "Never mind." ) ); | |
| } | |
| } | |
| void iexamine::fswitch( player &p, const tripoint &examp ) | |
| { | |
| if( !query_yn( _( "Flip the %s?" ), g->m.tername( examp ) ) ) { | |
| none( p, examp ); | |
| return; | |
| } | |
| ter_id terid = g->m.ter( examp ); | |
| p.moves -= to_moves<int>( 1_seconds ); | |
| tripoint tmp; | |
| tmp.z = examp.z; | |
| for( tmp.y = examp.y; tmp.y <= examp.y + 5; tmp.y++ ) { | |
| for( tmp.x = 0; tmp.x < MAPSIZE_X; tmp.x++ ) { | |
| if( terid == t_switch_rg ) { | |
| if( g->m.ter( tmp ) == t_rock_red ) { | |
| g->m.ter_set( tmp, t_floor_red ); | |
| } else if( g->m.ter( tmp ) == t_floor_red ) { | |
| g->m.ter_set( tmp, t_rock_red ); | |
| } else if( g->m.ter( tmp ) == t_rock_green ) { | |
| g->m.ter_set( tmp, t_floor_green ); | |
| } else if( g->m.ter( tmp ) == t_floor_green ) { | |
| g->m.ter_set( tmp, t_rock_green ); | |
| } | |
| } else if( terid == t_switch_gb ) { | |
| if( g->m.ter( tmp ) == t_rock_blue ) { | |
| g->m.ter_set( tmp, t_floor_blue ); | |
| } else if( g->m.ter( tmp ) == t_floor_blue ) { | |
| g->m.ter_set( tmp, t_rock_blue ); | |
| } else if( g->m.ter( tmp ) == t_rock_green ) { | |
| g->m.ter_set( tmp, t_floor_green ); | |
| } else if( g->m.ter( tmp ) == t_floor_green ) { | |
| g->m.ter_set( tmp, t_rock_green ); | |
| } | |
| } else if( terid == t_switch_rb ) { | |
| if( g->m.ter( tmp ) == t_rock_blue ) { | |
| g->m.ter_set( tmp, t_floor_blue ); | |
| } else if( g->m.ter( tmp ) == t_floor_blue ) { | |
| g->m.ter_set( tmp, t_rock_blue ); | |
| } else if( g->m.ter( tmp ) == t_rock_red ) { | |
| g->m.ter_set( tmp, t_floor_red ); | |
| } else if( g->m.ter( tmp ) == t_floor_red ) { | |
| g->m.ter_set( tmp, t_rock_red ); | |
| } | |
| } else if( terid == t_switch_even ) { | |
| if( ( tmp.y - examp.y ) % 2 == 1 ) { | |
| if( g->m.ter( tmp ) == t_rock_red ) { | |
| g->m.ter_set( tmp, t_floor_red ); | |
| } else if( g->m.ter( tmp ) == t_floor_red ) { | |
| g->m.ter_set( tmp, t_rock_red ); | |
| } else if( g->m.ter( tmp ) == t_rock_green ) { | |
| g->m.ter_set( tmp, t_floor_green ); | |
| } else if( g->m.ter( tmp ) == t_floor_green ) { | |
| g->m.ter_set( tmp, t_rock_green ); | |
| } else if( g->m.ter( tmp ) == t_rock_blue ) { | |
| g->m.ter_set( tmp, t_floor_blue ); | |
| } else if( g->m.ter( tmp ) == t_floor_blue ) { | |
| g->m.ter_set( tmp, t_rock_blue ); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| add_msg( m_warning, _( "You hear the rumble of rock shifting." ) ); | |
| g->events.add( EVENT_TEMPLE_SPAWN, calendar::turn + 3_turns ); | |
| } | |
| /** | |
| * If it's winter: show msg and return true. Otherwise return false | |
| */ | |
| static bool dead_plant( bool flower, player &p, const tripoint &examp ) | |
| { | |
| if( season_of_year( calendar::turn ) == WINTER ) { | |
| if( flower ) { | |
| add_msg( m_info, _( "This flower is dead. You can't get it." ) ); | |
| } else { | |
| add_msg( m_info, _( "This plant is dead. You can't get it." ) ); | |
| } | |
| iexamine::none( p, examp ); | |
| return true; | |
| } | |
| return false; | |
| } | |
| /** | |
| * Helper method to see if player has traits, hunger and mouthwear for drinking nectar. | |
| */ | |
| static bool can_drink_nectar( const player &p ) | |
| { | |
| return ( p.has_active_mutation( trait_id( "PROBOSCIS" ) ) || | |
| p.has_active_mutation( trait_id( "BEAK_HUM" ) ) ) && | |
| ( ( p.get_hunger() ) > 0 ) && ( !( p.wearing_something_on( bp_mouth ) ) ); | |
| } | |
| /** | |
| * Consume Nectar. -15 hunger. | |
| */ | |
| static bool drink_nectar( player &p ) | |
| { | |
| if( can_drink_nectar( p ) ) { | |
| p.moves -= to_moves<int>( 30_seconds ); | |
| add_msg( _( "You drink some nectar." ) ); | |
| item nectar( "nectar", calendar::turn, 1 ); | |
| p.eat( nectar ); | |
| return true; | |
| } | |
| return false; | |
| } | |
| /** | |
| * Spawn an item after harvesting the plant | |
| */ | |
| static void handle_harvest( player &p, const std::string &itemid, bool force_drop ) | |
| { | |
| item harvest = item( itemid ); | |
| if( !force_drop && p.can_pickVolume( harvest, true ) && | |
| p.can_pickWeight( harvest, true ) ) { | |
| p.i_add( harvest ); | |
| p.add_msg_if_player( _( "You harvest: %s." ), harvest.tname() ); | |
| } else { | |
| g->m.add_item_or_charges( p.pos(), harvest ); | |
| p.add_msg_if_player( _( "You harvest and drop: %s." ), harvest.tname() ); | |
| } | |
| } | |
| /** | |
| * Prompt pick (or drink nectar if able) poppy bud. Not safe for player. | |
| * | |
| * Drinking causes: -25 hunger, +20 fatigue, pkill2-70 effect and, 1 in 20 pkiller-1 addiction. | |
| * Picking w/ env_resist < 5 causes 1 in 3 sleep for 12 min and 4 dmg to each leg | |
| */ | |
| void iexamine::flower_poppy( player &p, const tripoint &examp ) | |
| { | |
| if( dead_plant( true, p, examp ) ) { | |
| return; | |
| } | |
| // TODO: Get rid of this section and move it to eating | |
| // Two y/n prompts is just too much | |
| if( can_drink_nectar( p ) ) { | |
| if( !query_yn( _( "You feel woozy as you explore the %s. Drink?" ), | |
| g->m.furnname( examp ) ) ) { | |
| return; | |
| } | |
| p.moves -= to_moves<int>( 30_seconds ); // You take your time... | |
| add_msg( _( "You slowly suck up the nectar." ) ); | |
| item poppy( "poppy_nectar", calendar::turn, 1 ); | |
| p.eat( poppy ); | |
| p.mod_fatigue( 20 ); | |
| p.add_effect( effect_pkill2, 7_minutes ); | |
| // Please drink poppy nectar responsibly. | |
| if( one_in( 20 ) ) { | |
| p.add_addiction( ADD_PKILLER, 1 ); | |
| } | |
| } | |
| if( !query_yn( _( "Pick %s?" ), g->m.furnname( examp ) ) ) { | |
| none( p, examp ); | |
| return; | |
| } | |
| int resist = p.get_env_resist( bp_mouth ); | |
| if( resist < 10 ) { | |
| // Can't smell the flowers with a gas mask on! | |
| add_msg( m_warning, _( "This flower has a heady aroma." ) ); | |
| } | |
| auto recentWeather = sum_conditions( calendar::turn - 10_minutes, calendar::turn, p.pos() ); | |
| // If it has been raining recently, then this event is twice less likely. | |
| if( ( ( recentWeather.rain_amount > 1 ) ? one_in( 6 ) : one_in( 3 ) ) && resist < 5 ) { | |
| // Should user player::infect, but can't! | |
| // player::infect needs to be restructured to return a bool indicating success. | |
| add_msg( m_bad, _( "You fall asleep..." ) ); | |
| p.fall_asleep( 2_hours ); | |
| add_msg( m_bad, _( "Your legs are covered in the poppy's roots!" ) ); | |
| p.apply_damage( nullptr, bp_leg_l, 4 ); | |
| p.apply_damage( nullptr, bp_leg_r, 4 ); | |
| p.moves -= 50; | |
| } | |
| g->m.furn_set( examp, f_null ); | |
| handle_harvest( p, "poppy_bud", false ); | |
| handle_harvest( p, "withered", false ); | |
| } | |
| /** | |
| * Prompt pick cactus pad. Not safe for player. | |
| */ | |
| void iexamine::flower_cactus( player &p, const tripoint &examp ) | |
| { | |
| if( dead_plant( true, p, examp ) ) { | |
| return; | |
| } | |
| if( !query_yn( _( "Pick %s?" ), g->m.furnname( examp ) ) ) { | |
| none( p, examp ); | |
| return; | |
| } | |
| if( one_in( 6 ) ) { | |
| add_msg( m_bad, _( "The cactus' nettles sting you!" ) ); | |
| p.apply_damage( nullptr, bp_arm_l, 4 ); | |
| p.apply_damage( nullptr, bp_arm_r, 4 ); | |
| } | |
| g->m.furn_set( examp, f_null ); | |
| handle_harvest( p, "cactus_pad", false ); | |
| handle_harvest( p, "seed_cactus", false ); | |
| } | |
| /** | |
| * Dig up its roots or drink its nectar if you can. | |
| */ | |
| void iexamine::flower_dahlia( player &p, const tripoint &examp ) | |
| { | |
| if( dead_plant( true, p, examp ) ) { | |
| return; | |
| } | |
| if( drink_nectar( p ) ) { | |
| return; | |
| } | |
| bool can_get_root = p.has_quality( quality_id( "DIG" ) ) || p.has_trait( trait_BURROW ); | |
| if( can_get_root ) { | |
| if( !query_yn( _( "Pick %s?" ), g->m.furnname( examp ) ) ) { | |
| none( p, examp ); | |
| return; | |
| } | |
| } else { | |
| if( !query_yn( _( "You don't have a digging tool to dig up roots. Pick %s anyway?" ), | |
| g->m.furnname( examp ) ) ) { | |
| none( p, examp ); | |
| return; | |
| } | |
| } | |
| g->m.furn_set( examp, f_null ); | |
| if( can_get_root ) { | |
| handle_harvest( p, "dahlia_root", false ); | |
| } | |
| handle_harvest( p, "seed_dahlia", false ); | |
| handle_harvest( p, "withered", false ); | |
| // There was a bud and flower spawn here | |
| // But those were useless, don't re-add until they get useful | |
| } | |
| static bool harvest_common( player &p, const tripoint &examp, bool furn, bool nectar, | |
| bool auto_forage = false ) | |
| { | |
| const auto hid = g->m.get_harvest( examp ); | |
| if( hid.is_null() || hid->empty() ) { | |
| if( !auto_forage ) { | |
| p.add_msg_if_player( m_info, _( "Nothing can be harvested from this plant in current season." ) ); | |
| } | |
| if( p.manual_examine ) { | |
| iexamine::none( p, examp ); | |
| } | |
| return false; | |
| } | |
| const auto &harvest = hid.obj(); | |
| // If nothing can be harvested, neither can nectar | |
| // Incredibly low priority TODO: Allow separating nectar seasons | |
| if( nectar && drink_nectar( p ) ) { | |
| return false; | |
| } | |
| if( p.is_player() && !auto_forage && | |
| !query_yn( _( "Pick %s?" ), furn ? g->m.furnname( examp ) : g->m.tername( | |
| examp ) ) ) { | |
| iexamine::none( p, examp ); | |
| return false; | |
| } | |
| int lev = p.get_skill_level( skill_survival ); | |
| bool got_anything = false; | |
| for( const auto &entry : harvest ) { | |
| float min_num = entry.base_num.first + lev * entry.scale_num.first; | |
| float max_num = entry.base_num.second + lev * entry.scale_num.second; | |
| int roll = std::min<int>( entry.max, round( rng_float( min_num, max_num ) ) ); | |
| if( roll >= 1 ) { | |
| got_anything = true; | |
| for( int i = 0; i < roll; i++ ) { | |
| handle_harvest( p, entry.drop, false ); | |
| } | |
| } | |
| } | |
| if( !got_anything ) { | |
| p.add_msg_if_player( m_bad, _( "You couldn't harvest anything." ) ); | |
| } | |
| p.mod_moves( -to_moves<int>( rng( 5_seconds, 15_seconds ) ) ); | |
| return true; | |
| } | |
| void iexamine::harvest_furn_nectar( player &p, const tripoint &examp ) | |
| { | |
| bool auto_forage = get_option<bool>( "AUTO_FEATURES" ) && | |
| get_option<std::string>( "AUTO_FORAGING" ) == "both"; | |
| if( harvest_common( p, examp, true, true, auto_forage ) ) { | |
| g->m.furn_set( examp, f_null ); | |
| } | |
| } | |
| void iexamine::harvest_furn( player &p, const tripoint &examp ) | |
| { | |
| bool auto_forage = get_option<bool>( "AUTO_FEATURES" ) && | |
| get_option<std::string>( "AUTO_FORAGING" ) == "both"; | |
| if( harvest_common( p, examp, true, false, auto_forage ) ) { | |
| g->m.furn_set( examp, f_null ); | |
| } | |
| } | |
| void iexamine::harvest_ter_nectar( player &p, const tripoint &examp ) | |
| { | |
| bool auto_forage = get_option<bool>( "AUTO_FEATURES" ) && | |
| ( get_option<std::string>( "AUTO_FORAGING" ) == "both" || | |
| get_option<std::string>( "AUTO_FORAGING" ) == "bushes" || | |
| get_option<std::string>( "AUTO_FORAGING" ) == "trees" ); | |
| if( harvest_common( p, examp, false, true, auto_forage ) ) { | |
| g->m.ter_set( examp, g->m.get_ter_transforms_into( examp ) ); | |
| } | |
| } | |
| void iexamine::harvest_ter( player &p, const tripoint &examp ) | |
| { | |
| bool auto_forage = get_option<bool>( "AUTO_FEATURES" ) && | |
| ( get_option<std::string>( "AUTO_FORAGING" ) == "both" || | |
| get_option<std::string>( "AUTO_FORAGING" ) == "trees" ); | |
| if( harvest_common( p, examp, false, false, auto_forage ) ) { | |
| g->m.ter_set( examp, g->m.get_ter_transforms_into( examp ) ); | |
| } | |
| } | |
| /** | |
| * Only harvest a plant once per season. Display message and call iexamine::none. | |
| */ | |
| void iexamine::harvested_plant( player &p, const tripoint &examp ) | |
| { | |
| p.add_msg_if_player( m_info, _( "Nothing can be harvested from this plant in current season" ) ); | |
| iexamine::none( p, examp ); | |
| } | |
| void iexamine::flower_marloss( player &p, const tripoint &examp ) | |
| { | |
| if( season_of_year( calendar::turn ) == WINTER ) { | |
| add_msg( m_info, _( "This flower is still alive, despite the harsh conditions..." ) ); | |
| } | |
| if( can_drink_nectar( p ) ) { | |
| if( !query_yn( _( "You feel out of place as you explore the %s. Drink?" ), | |
| g->m.furnname( examp ) ) ) { | |
| return; | |
| } | |
| p.moves -= to_moves<int>( 30_seconds ); // Takes 30 seconds | |
| add_msg( m_bad, _( "This flower tastes very wrong..." ) ); | |
| // If you can drink flowers, you're post-thresh and the Mycus does not want you. | |
| p.add_effect( effect_teleglow, 10_minutes ); | |
| } | |
| if( !query_yn( _( "Pick %s?" ), g->m.furnname( examp ) ) ) { | |
| none( p, examp ); | |
| return; | |
| } | |
| g->m.furn_set( examp, f_null ); | |
| g->m.spawn_item( p.pos(), "marloss_seed", 1, 3, calendar::turn ); | |
| handle_harvest( p, "withered", false ); | |
| } | |
| /** | |
| * Spawn spiders from a spider egg sack in radius 1 around the egg sack. | |
| * Transforms the egg sack furniture into a ruptured egg sack (f_egg_sacke). | |
| * Also spawns eggs. | |
| * @param p The player | |
| * @param examp Location of egg sack | |
| * @param montype The monster type of the created spiders. | |
| */ | |
| void iexamine::egg_sack_generic( player &p, const tripoint &examp, | |
| const mtype_id &montype ) | |
| { | |
| const std::string old_furn_name = g->m.furnname( examp ); | |
| if( !query_yn( _( "Harvest the %s?" ), old_furn_name ) ) { | |
| none( p, examp ); | |
| return; | |
| } | |
| g->m.furn_set( examp, f_egg_sacke ); | |
| int monster_count = 0; | |
| if( one_in( 2 ) ) { | |
| const std::vector<tripoint> pts = closest_tripoints_first( 1, examp ); | |
| for( const auto &pt : pts ) { | |
| if( g->is_empty( pt ) && one_in( 3 ) ) { | |
| g->summon_mon( montype, pt ); | |
| monster_count++; | |
| } | |
| } | |
| } | |
| int roll = rng( 1, 5 ); | |
| bool drop_eggs = ( monster_count >= 1 ? true : false ); | |
| for( int i = 0; i < roll; i++ ) { | |
| handle_harvest( p, "spider_egg", drop_eggs ); | |
| } | |
| if( monster_count == 1 ) { | |
| add_msg( m_warning, _( "A spiderling bursts from the %s!" ), old_furn_name ); | |
| } else if( monster_count >= 1 ) { | |
| add_msg( m_warning, _( "Spiderlings burst from the %s!" ), old_furn_name ); | |
| } | |
| } | |
| void iexamine::egg_sackbw( player &p, const tripoint &examp ) | |
| { | |
| egg_sack_generic( p, examp, mon_spider_widow_giant_s ); | |
| } | |
| void iexamine::egg_sackcs( player &p, const tripoint &examp ) | |
| { | |
| egg_sack_generic( p, examp, mon_spider_cellar_giant_s ); | |
| } | |
| void iexamine::egg_sackws( player &p, const tripoint &examp ) | |
| { | |
| egg_sack_generic( p, examp, mon_spider_web_s ); | |
| } | |
| /** | |
| * Remove furniture. Add spore effect. | |
| */ | |
| void iexamine::fungus( player &p, const tripoint &examp ) | |
| { | |
| add_msg( _( "The %s crumbles into spores!" ), g->m.furnname( examp ) ); | |
| fungal_effects( *g, g->m ).create_spores( examp, &p ); | |
| g->m.furn_set( examp, f_null ); | |
| p.moves -= 50; | |
| } | |
| /** | |
| * Make lists of unique seed types and names for the menu(no multiple hemp seeds etc) | |
| */ | |
| std::vector<seed_tuple> iexamine::get_seed_entries( const std::vector<item *> &seed_inv ) | |
| { | |
| std::map<itype_id, int> seed_map; | |
| for( auto &seed : seed_inv ) { | |
| seed_map[seed->typeId()] += ( seed->charges > 0 ? seed->charges : 1 ); | |
| } | |
| std::vector<seed_tuple> seed_entries; | |
| seed_entries.reserve( seed_map.size() ); | |
| for( const auto &pr : seed_map ) { | |
| seed_entries.emplace_back( | |
| pr.first, item::nname( pr.first, pr.second ), pr.second ); | |
| } | |
| // Sort by name | |
| std::sort( seed_entries.begin(), seed_entries.end(), | |
| []( const seed_tuple & l, const seed_tuple & r ) { | |
| return std::get<1>( l ).compare( std::get<1>( r ) ) < 0; | |
| } ); | |
| return seed_entries; | |
| } | |
| /** | |
| * Choose seed for planting | |
| */ | |
| int iexamine::query_seed( const std::vector<seed_tuple> &seed_entries ) | |
| { | |
| uilist smenu; | |
| smenu.text = _( "Use which seed?" ); | |
| int count = 0; | |
| for( const auto &entry : seed_entries ) { | |
| const std::string &seed_name = std::get<1>( entry ); | |
| int seed_count = std::get<2>( entry ); | |
| std::string format = seed_count > 0 ? "%s (%d)" : "%s"; | |
| smenu.addentry( count++, true, MENU_AUTOASSIGN, format.c_str(), | |
| seed_name, seed_count ); | |
| } | |
| smenu.query(); | |
| if( smenu.ret >= 0 ) { | |
| return smenu.ret; | |
| } else { | |
| return seed_entries.size(); | |
| } | |
| } | |
| /** | |
| * Actual planting of selected seed | |
| */ | |
| void iexamine::plant_seed( player &p, const tripoint &examp, const itype_id &seed_id ) | |
| { | |
| std::list<item> used_seed; | |
| if( item::count_by_charges( seed_id ) ) { | |
| used_seed = p.use_charges( seed_id, 1 ); | |
| } else { | |
| used_seed = p.use_amount( seed_id, 1 ); | |
| } | |
| used_seed.front().set_age( 0_turns ); | |
| g->m.add_item_or_charges( examp, used_seed.front() ); | |
| if( g->m.has_flag_furn( "PLANTABLE", examp ) ) { | |
| g->m.furn_set( examp, furn_str_id( g->m.furn( examp )->plant->transform ) ); | |
| } else { | |
| g->m.set( examp, t_dirt, f_plant_seed ); | |
| } | |
| p.moves -= to_moves<int>( 30_seconds ); | |
| add_msg( _( "Planted %s." ), item::nname( seed_id ) ); | |
| } | |
| /** | |
| * If it's warm enough, pick one of the player's seeds and plant it. | |
| */ | |
| void iexamine::dirtmound( player &p, const tripoint &examp ) | |
| { | |
| if( !warm_enough_to_plant( g->u.pos() ) ) { | |
| add_msg( m_info, _( "It is too cold to plant anything now." ) ); | |
| return; | |
| } | |
| /* ambient_light_at() not working? | |
| if (g->m.ambient_light_at(examp) < LIGHT_AMBIENT_LOW) { | |
| add_msg(m_info, _("It is too dark to plant anything now.")); | |
| return; | |
| }*/ | |
| std::vector<item *> seed_inv = p.items_with( []( const item & itm ) { | |
| return itm.is_seed(); | |
| } ); | |
| if( seed_inv.empty() ) { | |
| add_msg( m_info, _( "You have no seeds to plant." ) ); | |
| return; | |
| } | |
| if( !g->m.i_at( examp ).empty() ) { | |
| add_msg( _( "Something's lying there..." ) ); | |
| return; | |
| } | |
| auto seed_entries = get_seed_entries( seed_inv ); | |
| int seed_index = query_seed( seed_entries ); | |
| // Did we cancel? | |
| if( seed_index < 0 || seed_index >= static_cast<int>( seed_entries.size() ) ) { | |
| add_msg( _( "You saved your seeds for later." ) ); | |
| return; | |
| } | |
| const auto &seed_id = std::get<0>( seed_entries[seed_index] ); | |
| plant_seed( p, examp, seed_id ); | |
| } | |
| /** | |
| * Items that appear when a generic plant is harvested. Seed @ref islot_seed. | |
| * @param type The seed type, must have a @ref itype::seed slot. | |
| * @param plant_count Number of fruits to generate. For charge-based items, this | |
| * specifies multiples of the default charge. | |
| * @param seed_count Number of seeds to generate. | |
| * @param byproducts If true, byproducts (like straw, withered plants, see | |
| * @ref islot_seed::byproducts) are included. | |
| */ | |
| std::list<item> iexamine::get_harvest_items( const itype &type, const int plant_count, | |
| const int seed_count, const bool byproducts ) | |
| { | |
| std::list<item> result; | |
| if( !type.seed ) { | |
| return result; | |
| } | |
| const islot_seed &seed_data = *type.seed; | |
| // This is a temporary measure, itype should instead provide appropriate accessors | |
| // to expose data about the seed item to allow harvesting to function. | |
| const itype_id &seed_type = type.get_id(); | |
| const auto add = [&]( const itype_id & id, const int count ) { | |
| item new_item( id, calendar::turn ); | |
| if( new_item.count_by_charges() && count > 0 ) { | |
| new_item.charges *= count; | |
| new_item.charges /= seed_data.fruit_div; | |
| if( new_item.charges <= 0 ) { | |
| new_item.charges = 1; | |
| } | |
| result.push_back( new_item ); | |
| } else if( count > 0 ) { | |
| result.insert( result.begin(), count, new_item ); | |
| } | |
| }; | |
| if( seed_data.spawn_seeds ) { | |
| add( seed_type, seed_count ); | |
| } | |
| add( seed_data.fruit_id, plant_count ); | |
| if( byproducts ) { | |
| for( auto &b : seed_data.byproducts ) { | |
| add( b, 1 ); | |
| } | |
| } | |
| return result; | |
| } | |
| /** | |
| * Actual harvesting of selected plant | |
| */ | |
| void iexamine::harvest_plant( player &p, const tripoint &examp ) | |
| { | |
| // Can't use item_stack::only_item() since there might be fertilizer | |
| map_stack items = g->m.i_at( examp ); | |
| const map_stack::iterator seed = std::find_if( items.begin(), items.end(), []( const item & it ) { | |
| return it.is_seed(); | |
| } ); | |
| if( seed == items.end() ) { | |
| debugmsg( "Missing seed for plant at (%d, %d, %d)", examp.x, examp.y, examp.z ); | |
| g->m.i_clear( examp ); | |
| g->m.furn_set( examp, f_null ); | |
| return; | |
| } | |
| const std::string &seedType = seed->typeId(); | |
| if( seedType == "fungal_seeds" ) { | |
| fungus( p, examp ); | |
| g->m.i_clear( examp ); | |
| } else if( seedType == "marloss_seed" ) { | |
| fungus( p, examp ); | |
| g->m.i_clear( examp ); | |
| if( p.has_trait( trait_M_DEPENDENT ) && ( p.get_kcal_percent() < 0.8f || p.get_thirst() > 300 ) ) { | |
| g->m.ter_set( examp, t_marloss ); | |
| add_msg( m_info, | |
| _( "We have altered this unit's configuration to extract and provide local nutriment. The Mycus provides." ) ); | |
| } else if( ( p.has_trait( trait_M_DEFENDER ) ) || ( ( p.has_trait( trait_M_SPORES ) || | |
| p.has_trait( trait_M_FERTILE ) ) && | |
| one_in( 2 ) ) ) { | |
| g->summon_mon( mon_fungal_blossom, examp ); | |
| add_msg( m_info, _( "The seed blooms forth! We have brought true beauty to this world." ) ); | |
| } else if( ( p.has_trait( trait_THRESH_MYCUS ) ) || one_in( 4 ) ) { | |
| g->m.furn_set( examp, f_flower_marloss ); | |
| add_msg( m_info, _( "The seed blossoms rather rapidly..." ) ); | |
| } else { | |
| g->m.furn_set( examp, f_flower_fungal ); | |
| add_msg( m_info, _( "The seed blossoms into a flower-looking fungus." ) ); | |
| } | |
| } else { // Generic seed, use the seed item data | |
| const itype &type = *seed->type; | |
| g->m.i_clear( examp ); | |
| int skillLevel = p.get_skill_level( skill_survival ); | |
| ///\EFFECT_SURVIVAL increases number of plants harvested from a seed | |
| int plant_count = rng( skillLevel / 2, skillLevel ); | |
| plant_count *= g->m.furn( examp )->plant->harvest_multiplier; | |
| const int max_harvest_count = get_option<int>( "MAX_HARVEST_COUNT" ); | |
| if( plant_count >= max_harvest_count ) { | |
| plant_count = max_harvest_count; | |
| } else if( plant_count <= 0 ) { | |
| plant_count = 1; | |
| } | |
| const int seedCount = std::max( 1, rng( plant_count / 4, plant_count / 2 ) ); | |
| for( auto &i : get_harvest_items( type, plant_count, seedCount, true ) ) { | |
| g->m.add_item_or_charges( examp, i ); | |
| } | |
| g->m.furn_set( examp, furn_str_id( g->m.furn( examp )->plant->transform ) ); | |
| p.moves -= to_moves<int>( 10_seconds ); | |
| } | |
| } | |
| ret_val<bool> iexamine::can_fertilize( player &p, const tripoint &tile, | |
| const itype_id &fertilizer ) | |
| { | |
| if( !g->m.has_flag_furn( "PLANT", tile ) ) { | |
| return ret_val<bool>::make_failure( _( "Tile isn't a plant" ) ); | |
| } | |
| if( g->m.i_at( tile ).size() > 1 ) { | |
| return ret_val<bool>::make_failure( _( "Tile is already fertilized" ) ); | |
| } | |
| if( !p.has_charges( fertilizer, 1 ) ) { | |
| return ret_val<bool>::make_failure( | |
| _( "Tried to fertilize with %s, but player doesn't have any." ), | |
| fertilizer.c_str() ); | |
| } | |
| return ret_val<bool>::make_success(); | |
| } | |
| void iexamine::fertilize_plant( player &p, const tripoint &tile, const itype_id &fertilizer ) | |
| { | |
| ret_val<bool> can_fert = can_fertilize( p, tile, fertilizer ); | |
| if( !can_fert.success() ) { | |
| debugmsg( can_fert.str() ); | |
| return; | |
| } | |
| std::list<item> planted = p.use_charges( fertilizer, 1 ); | |
| // Reduce the amount of time it takes until the next stage of the plant by | |
| // 20% of a seasons length. (default 2.8 days). | |
| const time_duration fertilizerEpoch = calendar::season_length() * 0.2; | |
| // Can't use item_stack::only_item() since there might be fertilizer | |
| map_stack items = g->m.i_at( tile ); | |
| const map_stack::iterator seed = std::find_if( items.begin(), items.end(), []( const item & it ) { | |
| return it.is_seed(); | |
| } ); | |
| if( seed == items.end() ) { | |
| debugmsg( "Missing seed for plant at (%d, %d, %d)", tile.x, tile.y, tile.z ); | |
| g->m.i_clear( tile ); | |
| g->m.furn_set( tile, f_null ); | |
| return; | |
| } | |
| // TODO: item should probably clamp the value on its own | |
| seed->set_birthday( std::max( calendar::time_of_cataclysm, seed->birthday() - fertilizerEpoch ) ); | |
| // The plant furniture has the NOITEM token which prevents adding items on that square, | |
| // spawned items are moved to an adjacent field instead, but the fertilizer token | |
| // must be on the square of the plant, therefore this hack: | |
| const auto old_furn = g->m.furn( tile ); | |
| g->m.furn_set( tile, f_null ); | |
| g->m.spawn_item( tile, "fertilizer", 1, 1, calendar::turn ); | |
| g->m.furn_set( tile, old_furn ); | |
| p.mod_moves( -to_moves<int>( 10_seconds ) ); | |
| add_msg( m_info, _( "You fertilize the %s with the %s." ), seed->get_plant_name(), | |
| planted.front().tname() ); | |
| } | |
| itype_id iexamine::choose_fertilizer( player &p, const std::string &pname, bool ask_player ) | |
| { | |
| std::vector<const item *> f_inv = p.all_items_with_flag( "FERTILIZER" ); | |
| if( f_inv.empty() ) { | |
| add_msg( m_info, _( "You have no fertilizer for the %s." ), pname ); | |
| return itype_id(); | |
| } | |
| std::vector<itype_id> f_types; | |
| std::vector<std::string> f_names; | |
| for( auto &f : f_inv ) { | |
| if( std::find( f_types.begin(), f_types.end(), f->typeId() ) == f_types.end() ) { | |
| f_types.push_back( f->typeId() ); | |
| f_names.push_back( f->tname() ); | |
| } | |
| } | |
| if( ask_player && !query_yn( _( "Fertilize the %s" ), pname ) ) { | |
| return itype_id(); | |
| } | |
| // Choose fertilizer from list | |
| int f_index = 0; | |
| if( f_types.size() > 1 ) { | |
| f_index = uilist( _( "Use which fertilizer?" ), f_names ); | |
| } | |
| if( f_index < 0 ) { | |
| return itype_id(); | |
| } | |
| return f_types[f_index]; | |
| } | |
| void iexamine::aggie_plant( player &p, const tripoint &examp ) | |
| { | |
| // Can't use item_stack::only_item() since there might be fertilizer | |
| map_stack items = g->m.i_at( examp ); | |
| const map_stack::iterator seed = std::find_if( items.begin(), items.end(), []( const item & it ) { | |
| return it.is_seed(); | |
| } ); | |
| if( seed == items.end() ) { | |
| debugmsg( "Missing seed for plant at (%d, %d, %d)", examp.x, examp.y, examp.z ); | |
| g->m.i_clear( examp ); | |
| g->m.furn_set( examp, f_null ); | |
| return; | |
| } | |
| const std::string pname = seed->get_plant_name(); | |
| if( g->m.has_flag_furn( "GROWTH_HARVEST", examp ) && query_yn( _( "Harvest the %s?" ), pname ) ) { | |
| harvest_plant( p, examp ); | |
| } else if( !g->m.has_flag_furn( "GROWTH_HARVEST", examp ) ) { | |
| if( g->m.i_at( examp ).size() > 1 ) { | |
| add_msg( m_info, _( "This %s has already been fertilized." ), pname ); | |
| return; | |
| } | |
| itype_id fertilizer = choose_fertilizer( p, pname, true /*ask player for confirmation */ ); | |
| if( !fertilizer.empty() ) { | |
| fertilize_plant( p, examp, fertilizer ); | |
| } | |
| } | |
| } | |
| // Highly modified fermenting vat functions | |
| void iexamine::kiln_empty( player &p, const tripoint &examp ) | |
| { | |
| furn_id cur_kiln_type = g->m.furn( examp ); | |
| furn_id next_kiln_type = f_null; | |
| if( cur_kiln_type == f_kiln_empty ) { | |
| next_kiln_type = f_kiln_full; | |
| } else if( cur_kiln_type == f_kiln_metal_empty ) { | |
| next_kiln_type = f_kiln_metal_full; | |
| } else { | |
| debugmsg( "Examined furniture has action kiln_empty, but is of type %s", | |
| g->m.furn( examp ).id().c_str() ); | |
| return; | |
| } | |
| static const std::set<material_id> kilnable{ material_id( "wood" ), material_id( "bone" ) }; | |
| bool fuel_present = false; | |
| auto items = g->m.i_at( examp ); | |
| for( const item &i : items ) { | |
| if( i.typeId() == "charcoal" ) { | |
| add_msg( _( "This kiln already contains charcoal." ) ); | |
| add_msg( _( "Remove it before firing the kiln again." ) ); | |
| return; | |
| } else if( i.made_of_any( kilnable ) ) { | |
| fuel_present = true; | |
| } else { | |
| add_msg( m_bad, _( "This kiln contains %s, which can't be made into charcoal!" ), i.tname( 1, | |
| false ) ); | |
| return; | |
| } | |
| } | |
| if( !fuel_present ) { | |
| add_msg( _( "This kiln is empty. Fill it with wood or bone and try again." ) ); | |
| return; | |
| } | |
| ///\EFFECT_FABRICATION decreases loss when firing a kiln | |
| const int skill = p.get_skill_level( skill_fabrication ); | |
| int loss = 60 - 2 * | |
| skill; // We can afford to be inefficient - logs and skeletons are cheap, charcoal isn't | |
| // Burn stuff that should get charred, leave out the rest | |
| units::volume total_volume = 0_ml; | |
| for( const item &i : items ) { | |
| total_volume += i.volume(); | |
| } | |
| auto char_type = item::find_type( "unfinished_charcoal" ); | |
| int char_charges = char_type->charges_per_volume( ( 100 - loss ) * total_volume / 100 ); | |
| if( char_charges < 1 ) { | |
| add_msg( _( "The batch in this kiln is too small to yield any charcoal." ) ); | |
| return; | |
| } | |
| if( !p.has_charges( "fire", 1 ) ) { | |
| add_msg( _( "This kiln is ready to be fired, but you have no fire source." ) ); | |
| return; | |
| } else { | |
| add_msg( _( "This kiln contains %s %s of material, and is ready to be fired." ), | |
| format_volume( total_volume ), volume_units_abbr() ); | |
| if( !query_yn( _( "Fire the kiln?" ) ) ) { | |
| return; | |
| } | |
| } | |
| p.use_charges( "fire", 1 ); | |
| g->m.i_clear( examp ); | |
| g->m.furn_set( examp, next_kiln_type ); | |
| item result( "unfinished_charcoal", calendar::turn ); | |
| result.charges = char_charges; | |
| g->m.add_item( examp, result ); | |
| add_msg( _( "You fire the charcoal kiln." ) ); | |
| } | |
| void iexamine::kiln_full( player &, const tripoint &examp ) | |
| { | |
| furn_id cur_kiln_type = g->m.furn( examp ); | |
| furn_id next_kiln_type = f_null; | |
| if( cur_kiln_type == f_kiln_full ) { | |
| next_kiln_type = f_kiln_empty; | |
| } else if( cur_kiln_type == f_kiln_metal_full ) { | |
| next_kiln_type = f_kiln_metal_empty; | |
| } else { | |
| debugmsg( "Examined furniture has action kiln_full, but is of type %s", | |
| g->m.furn( examp ).id().c_str() ); | |
| return; | |
| } | |
| map_stack items = g->m.i_at( examp ); | |
| if( items.empty() ) { | |
| add_msg( _( "This kiln is empty..." ) ); | |
| g->m.furn_set( examp, next_kiln_type ); | |
| return; | |
| } | |
| auto char_type = item::find_type( "charcoal" ); | |
| add_msg( _( "There's a charcoal kiln there." ) ); | |
| const time_duration firing_time = 6_hours; // 5 days in real life | |
| const time_duration time_left = firing_time - items.only_item().age(); | |
| if( time_left > 0_turns ) { | |
| int hours = to_hours<int>( time_left ); | |
| int minutes = to_minutes<int>( time_left ) + 1; | |
| if( minutes > 60 ) { | |
| add_msg( ngettext( "It will finish burning in about %d hour.", | |
| "It will finish burning in about %d hours.", | |
| hours ), hours ); | |
| } else if( minutes > 30 ) { | |
| add_msg( _( "It will finish burning in less than an hour." ) ); | |
| } else { | |
| add_msg( _( "It should take about %d minutes to finish burning." ), minutes ); | |
| } | |
| return; | |
| } | |
| units::volume total_volume = 0_ml; | |
| // Burn stuff that should get charred, leave out the rest | |
| for( auto item_it = items.begin(); item_it != items.end(); ) { | |
| if( item_it->typeId() == "unfinished_charcoal" || item_it->typeId() == "charcoal" ) { | |
| total_volume += item_it->volume(); | |
| item_it = items.erase( item_it ); | |
| } else { | |
| item_it++; | |
| } | |
| } | |
| item result( "charcoal", calendar::turn ); | |
| result.charges = char_type->charges_per_volume( total_volume ); | |
| g->m.add_item( examp, result ); | |
| g->m.furn_set( examp, next_kiln_type ); | |
| add_msg( _( "It has finished burning, yielding %d charcoal." ), result.charges ); | |
| } | |
| void iexamine::fireplace( player &p, const tripoint &examp ) | |
| { | |
| const bool already_on_fire = g->m.has_nearby_fire( examp, 0 ); | |
| const bool furn_is_deployed = !g->m.furn( examp ).obj().deployed_item.empty(); | |
| if( already_on_fire && !furn_is_deployed ) { | |
| none( p, examp ); | |
| Pickup::pick_up( examp, 0 ); | |
| return; | |
| } | |
| std::multimap<int, item *> firestarters; | |
| for( item *it : p.items_with( []( const item & it ) { | |
| return it.has_flag( "FIRESTARTER" ) || it.has_flag( "FIRE" ); | |
| } ) ) { | |
| const auto usef = it->type->get_use( "firestarter" ); | |
| if( usef != nullptr && usef->get_actor_ptr() != nullptr ) { | |
| const auto actor = dynamic_cast<const firestarter_actor *>( usef->get_actor_ptr() ); | |
| if( actor->can_use( p, *it, false, examp ).success() ) { | |
| firestarters.insert( std::pair<int, item *>( actor->moves_cost_fast, it ) ); | |
| } | |
| } | |
| } | |
| const bool has_firestarter = !firestarters.empty(); | |
| const bool has_bionic_firestarter = p.has_bionic( bionic_id( "bio_lighter" ) ) && | |
| p.power_level >= bionic_id( "bio_lighter" )->power_activate; | |
| uilist selection_menu; | |
| selection_menu.text = _( "Select an action" ); | |
| selection_menu.addentry( 0, true, 'e', _( "Examine" ) ); | |
| if( !already_on_fire ) { | |
| selection_menu.addentry( 1, has_firestarter, 'f', | |
| has_firestarter ? _( "Start a fire" ) : _( "Start a fire... you'll need a fire source." ) ); | |
| if( has_bionic_firestarter ) { | |
| selection_menu.addentry( 2, true, 'b', _( "Use a CBM to start a fire" ) ); | |
| } | |
| } | |
| if( furn_is_deployed ) { | |
| selection_menu.addentry( 3, true, 't', string_format( _( "Take down the %s" ), | |
| g->m.furnname( examp ) ) ); | |
| } | |
| selection_menu.query(); | |
| switch( selection_menu.ret ) { | |
| case 0: | |
| none( p, examp ); | |
| Pickup::pick_up( examp, 0 ); | |
| return; | |
| case 1: { | |
| for( auto &firestarter : firestarters ) { | |
| item *it = firestarter.second; | |
| const auto usef = it->type->get_use( "firestarter" ); | |
| const auto actor = dynamic_cast<const firestarter_actor *>( usef->get_actor_ptr() ); | |
| p.add_msg_if_player( string_format( _( "You attempt to start a fire with your %s..." ), | |
| it->tname() ) ); | |
| const ret_val<bool> can_use = actor->can_use( p, *it, false, examp ); | |
| if( can_use.success() ) { | |
| const int charges = actor->use( p, *it, false, examp ); | |
| p.use_charges( it->typeId(), charges ); | |
| return; | |
| } else { | |
| p.add_msg_if_player( m_bad, can_use.str() ); | |
| } | |
| } | |
| p.add_msg_if_player( _( "You weren't able to start a fire." ) ); | |
| return; | |
| } | |
| case 2: { | |
| if( g->m.add_field( examp, fd_fire, 1 ) ) { | |
| p.charge_power( -bionic_id( "bio_lighter" )->power_activate ); | |
| p.mod_moves( -to_moves<int>( 1_seconds ) ); | |
| } else { | |
| p.add_msg_if_player( m_info, _( "You can't light a fire there." ) ); | |
| } | |
| return; | |
| } | |
| case 3: { | |
| if( already_on_fire ) { | |
| if( !query_yn( _( "Really take down the %s while it's on fire?" ), g->m.furnname( examp ) ) ) { | |
| return; | |
| } | |
| } | |
| p.add_msg_if_player( m_info, _( "You take down the %s." ), | |
| g->m.furnname( examp ) ); | |
| const auto furn_item = g->m.furn( examp ).obj().deployed_item; | |
| g->m.add_item_or_charges( examp, item( furn_item, calendar::turn ) ); | |
| g->m.furn_set( examp, f_null ); | |
| return; | |
| } | |
| default: | |
| none( p, examp ); | |
| return; | |
| } | |
| } | |
| void iexamine::fvat_empty( player &p, const tripoint &examp ) | |
| { | |
| itype_id brew_type; | |
| std::string brew_nname; | |
| bool to_deposit = false; | |
| static const auto vat_volume = units::from_liter( 50 ); | |
| bool vat_full = false; | |
| bool ferment = false; | |
| bool brew_present = false; | |
| int charges_on_ground = 0; | |
| auto items = g->m.i_at( examp ); | |
| for( auto item_it = items.begin(); item_it != items.end(); ) { | |
| if( !item_it->is_brewable() || brew_present ) { | |
| // This isn't a brew or there was already another kind of brew inside, | |
| // so this has to be moved. | |
| items.insert( *item_it ); | |
| // This will add items to a space near the vat, because it's flagged as NOITEM. | |
| item_it = items.erase( item_it ); | |
| } else { | |
| item_it++; | |
| brew_present = true; | |
| } | |
| } | |
| if( !brew_present ) { | |
| add_msg( _( "This keg is empty." ) ); | |
| // TODO: Allow using brews from crafting inventory | |
| const auto b_inv = p.items_with( []( const item & it ) { | |
| return it.is_brewable(); | |
| } ); | |
| if( b_inv.empty() ) { | |
| add_msg( m_info, _( "You have no brew to ferment." ) ); | |
| return; | |
| } | |
| // Make lists of unique typeids and names for the menu | |
| // Code shamelessly stolen from the crop planting function! | |
| std::vector<itype_id> b_types; | |
| std::vector<std::string> b_names; | |
| for( auto &b : b_inv ) { | |
| if( std::find( b_types.begin(), b_types.end(), b->typeId() ) == b_types.end() ) { | |
| b_types.push_back( b->typeId() ); | |
| b_names.push_back( item::nname( b->typeId() ) ); | |
| } | |
| } | |
| // Choose brew from list | |
| int b_index = 0; | |
| if( b_types.size() > 1 ) { | |
| b_index = uilist( _( "Use which brew?" ), b_names ); | |
| } else { //Only one brew type was in inventory, so it's automatically used | |
| if( !query_yn( _( "Set %s in the vat?" ), b_names[0] ) ) { | |
| b_index = -1; | |
| } | |
| } | |
| if( b_index < 0 ) { | |
| return; | |
| } | |
| to_deposit = true; | |
| brew_type = b_types[b_index]; | |
| brew_nname = item::nname( brew_type ); | |
| } else { | |
| item &brew = g->m.i_at( examp ).only_item(); | |
| brew_type = brew.typeId(); | |
| brew_nname = item::nname( brew_type ); | |
| charges_on_ground = brew.charges; | |
| add_msg( _( "This keg contains %s (%d), %0.f%% full." ), | |
| brew.tname(), brew.charges, brew.volume() * 100.0 / vat_volume ); | |
| enum options { ADD_BREW, REMOVE_BREW, START_FERMENT }; | |
| uilist selectmenu; | |
| selectmenu.text = _( "Select an action" ); | |
| selectmenu.addentry( ADD_BREW, ( p.charges_of( brew_type ) > 0 ), MENU_AUTOASSIGN, | |
| string_format( _( "Add more %s to the vat" ), brew_nname ) ); | |
| selectmenu.addentry( REMOVE_BREW, brew.made_of( LIQUID ), MENU_AUTOASSIGN, | |
| string_format( _( "Remove %s from the vat" ), brew.tname() ) ); | |
| selectmenu.addentry( START_FERMENT, true, MENU_AUTOASSIGN, _( "Start fermenting cycle" ) ); | |
| selectmenu.query(); | |
| switch( selectmenu.ret ) { | |
| case ADD_BREW: { | |
| to_deposit = true; | |
| break; | |
| } | |
| case REMOVE_BREW: { | |
| liquid_handler::handle_liquid_from_ground( g->m.i_at( examp ).begin(), examp ); | |
| return; | |
| } | |
| case START_FERMENT: { | |
| ferment = true; | |
| break; | |
| } | |
| default: | |
| add_msg( _( "Never mind." ) ); | |
| return; | |
| } | |
| } | |
| if( to_deposit ) { | |
| item brew( brew_type, 0 ); | |
| int charges_held = p.charges_of( brew_type ); | |
| brew.charges = charges_on_ground; | |
| for( int i = 0; i < charges_held && !vat_full; i++ ) { | |
| p.use_charges( brew_type, 1 ); | |
| brew.charges++; | |
| if( brew.volume() >= vat_volume ) { | |
| vat_full = true; | |
| } | |
| } | |
| add_msg( _( "Set %s in the vat." ), brew_nname ); | |
| add_msg( _( "The keg now contains %s (%d), %0.f%% full." ), | |
| brew.tname(), brew.charges, brew.volume() * 100.0 / vat_volume ); | |
| g->m.i_clear( examp ); | |
| //This is needed to bypass NOITEM | |
| g->m.add_item( examp, brew ); | |
| p.moves -= to_moves<int>( 20_seconds ); | |
| if( !vat_full ) { | |
| ferment = query_yn( _( "Start fermenting cycle?" ) ); | |
| } | |
| } | |
| if( vat_full || ferment ) { | |
| g->m.i_at( examp ).only_item().set_age( 0_turns ); | |
| g->m.furn_set( examp, f_fvat_full ); | |
| if( vat_full ) { | |
| add_msg( _( "The vat is full, so you close the lid and start the fermenting cycle." ) ); | |
| } else { | |
| add_msg( _( "You close the lid and start the fermenting cycle." ) ); | |
| } | |
| } | |
| } | |
| void iexamine::fvat_full( player &p, const tripoint &examp ) | |
| { | |
| map_stack items_here = g->m.i_at( examp ); | |
| if( items_here.empty() ) { | |
| debugmsg( "fvat_full was empty!" ); | |
| g->m.furn_set( examp, f_fvat_empty ); | |
| return; | |
| } | |
| for( item &it : items_here ) { | |
| if( !it.made_of_from_type( LIQUID ) ) { | |
| add_msg( _( "You remove %s from the vat." ), it.tname() ); | |
| g->m.add_item_or_charges( p.pos(), it ); | |
| g->m.i_rem( examp, &it ); | |
| } | |
| } | |
| if( items_here.empty() ) { | |
| g->m.furn_set( examp, f_fvat_empty ); | |
| return; | |
| } | |
| item &brew_i = *items_here.begin(); | |
| // Does the vat contain unfermented brew, or already fermented booze? | |
| // TODO: Allow "recursive brewing" to continue without player having to check on it | |
| if( brew_i.is_brewable() ) { | |
| add_msg( _( "There's a vat of %s set to ferment there." ), brew_i.tname() ); | |
| // TODO: change brew_time to return time_duration | |
| const time_duration brew_time = brew_i.brewing_time(); | |
| const time_duration progress = brew_i.age(); | |
| if( progress < brew_time ) { | |
| int hours = to_hours<int>( brew_time - progress ); | |
| if( hours < 1 ) { | |
| add_msg( _( "It will finish brewing in less than an hour." ) ); | |
| } else { | |
| add_msg( ngettext( "It will finish brewing in about %d hour.", | |
| "It will finish brewing in about %d hours.", | |
| hours ), hours ); | |
| } | |
| return; | |
| } | |
| if( query_yn( _( "Finish brewing?" ) ) ) { | |
| const auto results = brew_i.brewing_results(); | |
| g->m.i_clear( examp ); | |
| for( const auto &result : results ) { | |
| // TODO: Different age based on settings | |
| item booze( result, brew_i.birthday(), brew_i.charges ); | |
| g->m.add_item( examp, booze ); | |
| if( booze.made_of_from_type( LIQUID ) ) { | |
| add_msg( _( "The %s is now ready for bottling." ), booze.tname() ); | |
| } | |
| } | |
| p.moves -= to_moves<int>( 5_seconds ); | |
| p.practice( skill_cooking, std::min( to_minutes<int>( brew_time ) / 10, 100 ) ); | |
| } | |
| return; | |
| } else { | |
| add_msg( _( "There's a vat of fermented %s there." ), brew_i.tname() ); | |
| } | |
| const std::string booze_name = brew_i.tname(); | |
| if( liquid_handler::handle_liquid_from_ground( items_here.begin(), examp ) ) { | |
| g->m.furn_set( examp, f_fvat_empty ); | |
| add_msg( _( "You squeeze the last drops of %s from the vat." ), booze_name ); | |
| } | |
| } | |
| static units::volume get_keg_capacity( const tripoint &pos ) | |
| { | |
| const furn_t &furn = g->m.furn( pos ).obj(); | |
| return furn.keg_capacity; | |
| } | |
| /** | |
| * Check whether there is a keg on the map that can be filled via @ref pour_into_keg. | |
| */ | |
| bool iexamine::has_keg( const tripoint &pos ) | |
| { | |
| return get_keg_capacity( pos ) > 0_ml; | |
| } | |
| static void displace_items_except_one_liquid( const tripoint &examp ) | |
| { | |
| // Temporarily replace the real furniture with a fake furniture with NOITEM | |
| const furn_id previous_furn = g->m.furn( examp ); | |
| g->m.furn_set( examp, furn_id( "f_no_item" ) ); | |
| bool liquid_present = false; | |
| map_stack items = g->m.i_at( examp ); | |
| for( map_stack::iterator it = items.begin(); it != items.end(); ) { | |
| if( !it->made_of_from_type( LIQUID ) || liquid_present ) { | |
| // This isn't a liquid or there was already another kind of liquid inside, | |
| // so this has to be moved. | |
| // This will add items to a space near the vat, because it's flagged as NOITEM. | |
| items.insert( *it ); | |
| it = items.erase( it ); | |
| } else { | |
| it++; | |
| liquid_present = true; | |
| } | |
| } | |
| // Replace the real furniture | |
| g->m.furn_set( examp, previous_furn ); | |
| } | |
| void iexamine::keg( player &p, const tripoint &examp ) | |
| { | |
| none( p, examp ); | |
| const auto keg_name = g->m.name( examp ); | |
| units::volume keg_cap = get_keg_capacity( examp ); | |
| const bool liquid_present = map_cursor( examp ).has_item_with( []( const item & it ) { | |
| return it.made_of_from_type( LIQUID ); | |
| } ); | |
| if( !liquid_present ) { | |
| add_msg( m_info, _( "It is empty." ) ); | |
| // Get list of all drinks | |
| auto drinks_inv = p.items_with( []( const item & it ) { | |
| return it.made_of( LIQUID ); | |
| } ); | |
| if( drinks_inv.empty() ) { | |
| add_msg( m_info, _( "You don't have any drinks to fill the %s with." ), keg_name ); | |
| return; | |
| } | |
| // Make lists of unique drinks... about third time we do this, maybe we ought to make a function next time | |
| std::vector<itype_id> drink_types; | |
| std::vector<std::string> drink_names; | |
| std::vector<double> drink_rot; | |
| for( auto &drink : drinks_inv ) { | |
| auto found_drink = std::find( drink_types.begin(), drink_types.end(), drink->typeId() ); | |
| if( found_drink == drink_types.end() ) { | |
| drink_types.push_back( drink->typeId() ); | |
| drink_names.push_back( item::nname( drink->typeId() ) ); | |
| drink_rot.push_back( drink->get_relative_rot() ); | |
| } else { | |
| auto rot_iter = std::next( drink_rot.begin(), std::distance( drink_types.begin(), found_drink ) ); | |
| // Yep, worst rot wins. | |
| *rot_iter = std::max( *rot_iter, drink->get_relative_rot() ); | |
| } | |
| } | |
| // Choose drink to store in keg from list | |
| int drink_index = 0; | |
| if( drink_types.size() > 1 ) { | |
| drink_index = uilist( _( "Store which drink?" ), drink_names ); | |
| if( drink_index < 0 || static_cast<size_t>( drink_index ) >= drink_types.size() ) { | |
| drink_index = -1; | |
| } | |
| } else { //Only one drink type was in inventory, so it's automatically used | |
| if( !query_yn( _( "Fill the %1$s with %2$s?" ), | |
| keg_name, drink_names[0].c_str() ) ) { | |
| drink_index = -1; | |
| } | |
| } | |
| if( drink_index < 0 ) { | |
| return; | |
| } | |
| // First empty the keg of foreign objects | |
| displace_items_except_one_liquid( examp ); | |
| //Store liquid chosen in the keg | |
| itype_id drink_type = drink_types[ drink_index ]; | |
| int charges_held = p.charges_of( drink_type ); | |
| item drink( drink_type, 0 ); | |
| drink.set_relative_rot( drink_rot[ drink_index ] ); | |
| drink.charges = 0; | |
| bool keg_full = false; | |
| for( int i = 0; i < charges_held && !keg_full; i++ ) { | |
| g->u.use_charges( drink.typeId(), 1 ); | |
| drink.charges++; | |
| keg_full = drink.volume() >= keg_cap; | |
| } | |
| if( keg_full ) { | |
| add_msg( _( "You completely fill the %1$s with %2$s." ), | |
| keg_name, item::nname( drink_type ) ); | |
| } else { | |
| add_msg( _( "You fill the %1$s with %2$s." ), | |
| keg_name, item::nname( drink_type ) ); | |
| } | |
| p.moves -= to_moves<int>( 10_seconds ); | |
| g->m.i_clear( examp ); | |
| g->m.add_item( examp, drink ); | |
| return; | |
| } else { | |
| // First empty the keg of foreign objects | |
| displace_items_except_one_liquid( examp ); | |
| map_stack items = g->m.i_at( examp ); | |
| item &drink = items.only_item(); | |
| const std::string drink_tname = drink.tname(); | |
| const std::string drink_nname = item::nname( drink.typeId() ); | |
| enum options { | |
| DISPENSE, | |
| HAVE_A_DRINK, | |
| REFILL, | |
| EXAMINE, | |
| }; | |
| uilist selectmenu; | |
| selectmenu.addentry( DISPENSE, drink.made_of( LIQUID ), MENU_AUTOASSIGN, | |
| _( "Dispense or dump %s" ), drink_tname ); | |
| selectmenu.addentry( HAVE_A_DRINK, drink.is_food() && drink.made_of( LIQUID ), | |
| MENU_AUTOASSIGN, _( "Have a drink" ) ); | |
| selectmenu.addentry( REFILL, true, MENU_AUTOASSIGN, _( "Refill" ) ); | |
| selectmenu.addentry( EXAMINE, true, MENU_AUTOASSIGN, _( "Examine" ) ); | |
| selectmenu.text = _( "Select an action" ); | |
| selectmenu.query(); | |
| switch( selectmenu.ret ) { | |
| case DISPENSE: | |
| if( liquid_handler::handle_liquid_from_ground( items.begin(), examp ) ) { | |
| add_msg( _( "You squeeze the last drops of %1$s from the %2$s." ), | |
| drink_tname, keg_name ); | |
| } | |
| return; | |
| case HAVE_A_DRINK: | |
| if( !p.eat( drink ) ) { | |
| return; // They didn't actually drink | |
| } | |
| if( drink.charges == 0 ) { | |
| add_msg( _( "You squeeze the last drops of %1$s from the %2$s." ), | |
| drink_tname, keg_name ); | |
| g->m.i_clear( examp ); | |
| } | |
| p.moves -= to_moves<int>( 5_seconds ); | |
| return; | |
| case REFILL: { | |
| if( drink.volume() >= keg_cap ) { | |
| add_msg( _( "The %s is completely full." ), keg_name ); | |
| return; | |
| } | |
| int charges_held = p.charges_of( drink.typeId() ); | |
| if( charges_held < 1 ) { | |
| add_msg( m_info, _( "You don't have any %1$s to fill the %2$s with." ), | |
| drink_nname, keg_name ); | |
| return; | |
| } | |
| item tmp( drink.typeId(), calendar::turn, charges_held ); | |
| pour_into_keg( examp, tmp ); | |
| p.use_charges( drink.typeId(), charges_held - tmp.charges ); | |
| add_msg( _( "You fill the %1$s with %2$s." ), keg_name, drink_nname ); | |
| p.moves -= to_moves<int>( 10_seconds ); | |
| return; | |
| } | |
| case EXAMINE: { | |
| add_msg( m_info, _( "It contains %s (%d), %0.f%% full." ), | |
| drink_tname, drink.charges, drink.volume() * 100.0 / keg_cap ); | |
| return; | |
| } | |
| default: | |
| return; | |
| } | |
| } | |
| } | |
| /** | |
| * Pour liquid into a keg (furniture) on the map. The transferred charges (if any) | |
| * will be removed from the liquid item. | |
| * @return Whether any charges have been transferred at all. | |
| */ | |
| bool iexamine::pour_into_keg( const tripoint &pos, item &liquid ) | |
| { | |
| const units::volume keg_cap = get_keg_capacity( pos ); | |
| if( keg_cap <= 0_ml ) { | |
| return false; | |
| } | |
| const auto keg_name = g->m.name( pos ); | |
| map_stack stack = g->m.i_at( pos ); | |
| if( stack.empty() ) { | |
| g->m.add_item( pos, liquid ); | |
| g->m.i_at( pos ).only_item().charges = 0; // Will be set later | |
| } else if( stack.only_item().typeId() != liquid.typeId() ) { | |
| add_msg( _( "The %s already contains some %s, you can't add a different liquid to it." ), | |
| keg_name, item::nname( stack.only_item().typeId() ) ); | |
| return false; | |
| } | |
| item &drink = stack.only_item(); | |
| if( drink.volume() >= keg_cap ) { | |
| add_msg( _( "The %s is full." ), keg_name ); | |
| return false; | |
| } | |
| add_msg( _( "You pour %1$s into the %2$s." ), liquid.tname(), keg_name ); | |
| while( liquid.charges > 0 && drink.volume() < keg_cap ) { | |
| drink.charges++; | |
| liquid.charges--; | |
| } | |
| return true; | |
| } | |
| void pick_plant( player &p, const tripoint &examp, | |
| const std::string &itemType, ter_id new_ter, bool seeds ) | |
| { | |
| bool auto_forage = get_option<bool>( "AUTO_FEATURES" ) && | |
| get_option<std::string>( "AUTO_FORAGING" ) != "off"; | |
| if( p.is_player() && !auto_forage && | |
| !query_yn( _( "Harvest the %s?" ), g->m.tername( examp ) ) ) { | |
| iexamine::none( p, examp ); | |
| return; | |
| } | |
| const int survival = p.get_skill_level( skill_survival ); | |
| p.practice( skill_survival, 6 ); | |
| int plantBase = rng( 2, 5 ); | |
| ///\EFFECT_SURVIVAL increases number of plants harvested | |
| int plantCount = rng( plantBase, plantBase + survival / 2 ); | |
| plantCount = std::min( plantCount, 12 ); | |
| g->m.spawn_item( p.pos(), itemType, plantCount, 0, calendar::turn ); | |
| if( seeds ) { | |
| g->m.spawn_item( p.pos(), "seed_" + itemType, 1, | |
| rng( plantCount / 4, plantCount / 2 ), calendar::turn ); | |
| } | |
| g->m.ter_set( examp, new_ter ); | |
| } | |
| void iexamine::tree_hickory( player &p, const tripoint &examp ) | |
| { | |
| if( harvest_common( p, examp, false, false ) ) { | |
| g->m.ter_set( examp, g->m.get_ter_transforms_into( examp ) ); | |
| } | |
| if( !p.has_quality( quality_id( "DIG" ) ) ) { | |
| p.add_msg_if_player( m_info, _( "You have no tool to dig with..." ) ); | |
| return; | |
| } | |
| if( p.is_player() && | |
| !query_yn( _( "Dig up %s? This kills the tree!" ), g->m.tername( examp ) ) ) { | |
| return; | |
| } | |
| ///\EFFECT_SURVIVAL increases hickory root number per tree | |
| g->m.spawn_item( p.pos(), "hickory_root", rng( 1, 3 + p.get_skill_level( skill_survival ) ), 0, | |
| calendar::turn ); | |
| g->m.ter_set( examp, t_tree_hickory_dead ); | |
| ///\EFFECT_SURVIVAL speeds up hickory root digging | |
| p.moves -= to_moves<int>( 20_seconds ) / ( p.get_skill_level( skill_survival ) + 1 ) + 100; | |
| } | |
| static item_location maple_tree_sap_container() | |
| { | |
| const item maple_sap = item( "maple_sap", 0 ); | |
| return g->inv_map_splice( [&]( const item & it ) { | |
| return it.get_remaining_capacity_for_liquid( maple_sap, true ) > 0; | |
| }, _( "Which container?" ), PICKUP_RANGE ); | |
| } | |
| void iexamine::tree_maple( player &p, const tripoint &examp ) | |
| { | |
| if( !p.has_quality( quality_id( "DRILL" ) ) ) { | |
| add_msg( m_info, _( "You need a tool to drill the crust to tap this maple tree." ) ); | |
| return; | |
| } | |
| if( !p.has_quality( quality_id( "HAMMER" ) ) ) { | |
| add_msg( m_info, | |
| _( "You need a tool to hammer the spile into the crust to tap this maple tree." ) ); | |
| return; | |
| } | |
| const inventory &crafting_inv = p.crafting_inventory(); | |
| if( !crafting_inv.has_amount( "tree_spile", 1 ) ) { | |
| add_msg( m_info, _( "You need a %s to tap this maple tree." ), | |
| item::nname( "tree_spile" ) ); | |
| return; | |
| } | |
| std::vector<item_comp> comps; | |
| comps.push_back( item_comp( "tree_spile", 1 ) ); | |
| p.consume_items( comps, 1, is_crafting_component ); | |
| p.mod_moves( -to_moves<int>( 20_seconds ) ); | |
| g->m.ter_set( examp, t_tree_maple_tapped ); | |
| auto cont_loc = maple_tree_sap_container(); | |
| item *container = cont_loc.get_item(); | |
| if( container ) { | |
| g->m.add_item_or_charges( examp, *container, false ); | |
| cont_loc.remove_item(); | |
| } else { | |
| add_msg( m_info, _( "No container added. The sap will just spill on the ground." ) ); | |
| } | |
| } | |
| void iexamine::tree_maple_tapped( player &p, const tripoint &examp ) | |
| { | |
| bool has_sap = false; | |
| item *container = nullptr; | |
| int charges = 0; | |
| const std::string maple_sap_name = item::nname( "maple_sap" ); | |
| map_stack items = g->m.i_at( examp ); | |
| if( !items.empty() ) { | |
| item &it = items.only_item(); | |
| if( it.is_bucket() || it.is_watertight_container() ) { | |
| container = ⁢ | |
| if( !it.is_container_empty() && it.contents.front().typeId() == "maple_sap" ) { | |
| has_sap = true; | |
| charges = it.contents.front().charges; | |
| } | |
| } | |
| } | |
| enum options { | |
| REMOVE_TAP, | |
| ADD_CONTAINER, | |
| HARVEST_SAP, | |
| REMOVE_CONTAINER, | |
| }; | |
| uilist selectmenu; | |
| selectmenu.addentry( REMOVE_TAP, true, MENU_AUTOASSIGN, _( "Remove tap" ) ); | |
| selectmenu.addentry( ADD_CONTAINER, !container, MENU_AUTOASSIGN, | |
| _( "Add a container to receive the %s" ), maple_sap_name ); | |
| selectmenu.addentry( HARVEST_SAP, has_sap, MENU_AUTOASSIGN, _( "Harvest current %s (%d)" ), | |
| maple_sap_name, charges ); | |
| selectmenu.addentry( REMOVE_CONTAINER, container, MENU_AUTOASSIGN, _( "Remove container" ) ); | |
| selectmenu.text = _( "Select an action" ); | |
| selectmenu.query(); | |
| switch( selectmenu.ret ) { | |
| case REMOVE_TAP: { | |
| if( !p.has_quality( quality_id( "HAMMER" ) ) ) { | |
| add_msg( m_info, _( "You need a hammering tool to remove the spile from the crust." ) ); | |
| return; | |
| } | |
| item tree_spile( "tree_spile" ); | |
| add_msg( _( "You remove the %s." ), tree_spile.tname( 1 ) ); | |
| g->m.add_item_or_charges( p.pos(), tree_spile ); | |
| if( container ) { | |
| g->m.add_item_or_charges( p.pos(), *container ); | |
| } | |
| g->m.i_clear( examp ); | |
| p.mod_moves( -to_moves<int>( 20_seconds ) ); | |
| g->m.ter_set( examp, t_tree_maple ); | |
| return; | |
| } | |
| case ADD_CONTAINER: { | |
| auto cont_loc = maple_tree_sap_container(); | |
| container = cont_loc.get_item(); | |
| if( container ) { | |
| g->m.add_item_or_charges( examp, *container, false ); | |
| cont_loc.remove_item(); | |
| } else { | |
| add_msg( m_info, _( "No container added. The sap will just spill on the ground." ) ); | |
| } | |
| return; | |
| } | |
| case HARVEST_SAP: { | |
| liquid_handler::handle_liquid_from_container( *container, PICKUP_RANGE ); | |
| return; | |
| } | |
| case REMOVE_CONTAINER: { | |
| g->u.assign_activity( activity_id( "ACT_PICKUP" ) ); | |
| g->u.activity.targets.emplace_back( map_cursor( examp ), container ); | |
| g->u.activity.values.push_back( 0 ); | |
| return; | |
| } | |
| default: | |
| return; | |
| } | |
| } | |
| void iexamine::shrub_marloss( player &p, const tripoint &examp ) | |
| { | |
| if( p.has_trait( trait_THRESH_MYCUS ) ) { | |
| pick_plant( p, examp, "mycus_fruit", t_shrub_fungal ); | |
| } else if( p.has_trait( trait_THRESH_MARLOSS ) ) { | |
| g->m.spawn_item( p.pos(), "mycus_fruit", 1, 0, calendar::turn ); | |
| g->m.ter_set( examp, t_fungus ); | |
| add_msg( m_info, _( "The shrub offers up a fruit, then crumbles into a fungal bed." ) ); | |
| } else { | |
| pick_plant( p, examp, "marloss_berry", t_shrub_fungal ); | |
| } | |
| } | |
| void iexamine::tree_marloss( player &p, const tripoint &examp ) | |
| { | |
| if( p.has_trait( trait_THRESH_MYCUS ) ) { | |
| pick_plant( p, examp, "mycus_fruit", t_tree_fungal ); | |
| if( p.has_trait( trait_M_DEPENDENT ) && one_in( 3 ) ) { | |
| // Folks have a better shot at keeping fed. | |
| add_msg( m_info, | |
| _( "We have located a particularly vital nutrient deposit underneath this location." ) ); | |
| add_msg( m_good, _( "Additional nourishment is available." ) ); | |
| g->m.ter_set( examp, t_marloss_tree ); | |
| } | |
| } else if( p.has_trait( trait_THRESH_MARLOSS ) ) { | |
| g->m.spawn_item( p.pos(), "mycus_fruit", 1, 0, calendar::turn ); | |
| g->m.ter_set( examp, t_tree_fungal ); | |
| add_msg( m_info, _( "The tree offers up a fruit, then shrivels into a fungal tree." ) ); | |
| } else { | |
| pick_plant( p, examp, "marloss_berry", t_tree_fungal ); | |
| } | |
| } | |
| void iexamine::shrub_wildveggies( player &p, const tripoint &examp ) | |
| { | |
| // Ask if there's something possibly more interesting than this shrub here | |
| if( ( !g->m.i_at( examp ).empty() || | |
| g->m.veh_at( examp ) || | |
| !g->m.tr_at( examp ).is_null() || | |
| g->critter_at( examp ) != nullptr ) && | |
| !query_yn( _( "Forage through %s?" ), g->m.tername( examp ) ) ) { | |
| none( p, examp ); | |
| return; | |
| } | |
| add_msg( _( "You forage through the %s." ), g->m.tername( examp ) ); | |
| ///\EFFECT_SURVIVAL speeds up foraging | |
| int move_cost = 100000 / ( 2 * p.get_skill_level( skill_survival ) + 5 ); | |
| ///\EFFECT_PER randomly speeds up foraging | |
| move_cost /= rng( std::max( 4, p.per_cur ), 4 + p.per_cur * 2 ); | |
| p.assign_activity( activity_id( "ACT_FORAGE" ), move_cost, 0 ); | |
| p.activity.placement = examp; | |
| p.activity.auto_resume = true; | |
| return; | |
| } | |
| void iexamine::recycle_compactor( player &, const tripoint &examp ) | |
| { | |
| // choose what metal to recycle | |
| auto metals = materials::get_compactable(); | |
| uilist choose_metal; | |
| choose_metal.text = _( "Recycle what metal?" ); | |
| for( auto &m : metals ) { | |
| choose_metal.addentry( m.name() ); | |
| } | |
| choose_metal.query(); | |
| int m_idx = choose_metal.ret; | |
| if( m_idx < 0 || m_idx >= static_cast<int>( metals.size() ) ) { | |
| add_msg( _( "Never mind." ) ); | |
| return; | |
| } | |
| material_type m = metals.at( m_idx ); | |
| // check inputs and tally total mass | |
| auto inputs = g->m.i_at( examp ); | |
| units::mass sum_weight = 0_gram; | |
| auto ca = m.compact_accepts(); | |
| std::set<material_id> accepts( ca.begin(), ca.end() ); | |
| accepts.insert( m.id ); | |
| for( auto &input : inputs ) { | |
| if( !input.only_made_of( accepts ) ) { | |
| //~ %1$s: an item in the compactor , %2$s: desired compactor output material | |
| add_msg( _( "You realize this isn't going to work because %1$s is not made purely of %2$s." ), | |
| input.tname(), m.name() ); | |
| return; | |
| } | |
| if( input.is_container() && !input.is_container_empty() ) { | |
| //~ %1$s: an item in the compactor | |
| add_msg( _( "You realize this isn't going to work because %1$s has not been emptied of its contents." ), | |
| input.tname() ); | |
| return; | |
| } | |
| sum_weight += input.weight(); | |
| } | |
| if( sum_weight <= 0_gram ) { | |
| //~ %1$s: desired compactor output material | |
| add_msg( _( "There is no %1$s in the compactor. Drop some metal items onto it and try again." ), | |
| m.name() ); | |
| return; | |
| } | |
| // See below for recover_factor (rng(6,9)/10), this | |
| // is the normal value of that recover factor. | |
| static const double norm_recover_factor = 8.0 / 10.0; | |
| const units::mass norm_recover_weight = sum_weight * norm_recover_factor; | |
| // choose output | |
| uilist choose_output; | |
| //~ %1$.3f: total mass of material in compactor, %2$s: weight units , %3$s: compactor output material | |
| choose_output.text = string_format( _( "Compact %1$.3f %2$s of %3$s into:" ), | |
| convert_weight( sum_weight ), weight_units(), m.name() ); | |
| for( auto &ci : m.compacts_into() ) { | |
| auto it = item( ci, 0, item::solitary_tag{} ); | |
| const int amount = norm_recover_weight / it.weight(); | |
| //~ %1$d: number of, %2$s: output item | |
| choose_output.addentry( string_format( _( "about %1$d %2$s" ), amount, | |
| it.tname( amount ) ) ); | |
| } | |
| choose_output.query(); | |
| int o_idx = choose_output.ret; | |
| if( o_idx < 0 || o_idx >= static_cast<int>( m.compacts_into().size() ) ) { | |
| add_msg( _( "Never mind." ) ); | |
| return; | |
| } | |
| // remove items | |
| for( auto it = inputs.begin(); it != inputs.end(); ) { | |
| it = inputs.erase( it ); | |
| } | |
| // produce outputs | |
| double recover_factor = rng( 6, 9 ) / 10.0; | |
| sum_weight = sum_weight * recover_factor; | |
| sounds::sound( examp, 80, sounds::sound_t::combat, _( "Ka-klunk!" ), true, "tool", "compactor" ); | |
| bool out_desired = false; | |
| bool out_any = false; | |
| for( auto it = m.compacts_into().begin() + o_idx; it != m.compacts_into().end(); ++it ) { | |
| const units::mass ow = item( *it, 0, item::solitary_tag{} ).weight(); | |
| int count = sum_weight / ow; | |
| sum_weight -= count * ow; | |
| if( count > 0 ) { | |
| g->m.spawn_item( examp, *it, count, 1, calendar::turn ); | |
| if( !out_any ) { | |
| out_any = true; | |
| if( it == m.compacts_into().begin() + o_idx ) { | |
| out_desired = true; | |
| } | |
| } | |
| } | |
| } | |
| // feedback to user | |
| if( !out_any ) { | |
| add_msg( _( "The compactor chews up all the items in its hopper." ) ); | |
| //~ %1$s: compactor output material | |
| add_msg( _( "The compactor beeps: \"No %1$s to process!\"" ), m.name() ); | |
| return; | |
| } | |
| if( !out_desired ) { | |
| //~ %1$s: compactor output material | |
| add_msg( _( "The compactor beeps: \"Insufficient %1$s!\"" ), m.name() ); | |
| add_msg( _( "It spits out an assortment of smaller pieces instead." ) ); | |
| } | |
| } | |
| void iexamine::trap( player &p, const tripoint &examp ) | |
| { | |
| const auto &tr = g->m.tr_at( examp ); | |
| if( !p.is_player() || tr.is_null() ) { | |
| return; | |
| } | |
| const int possible = tr.get_difficulty(); | |
| bool seen = tr.can_see( examp, p ); | |
| if( tr.loadid == tr_unfinished_construction || g->m.partial_con_at( examp ) ) { | |
| partial_con *pc = g->m.partial_con_at( examp ); | |
| if( pc ) { | |
| const std::vector<construction> &list_constructions = get_constructions(); | |
| const construction &built = list_constructions[pc->id]; | |
| if( !query_yn( _( "Unfinished task: %s, %d%% complete here, continue construction?" ), | |
| built.description, pc->counter / 100000 ) ) { | |
| if( query_yn( _( "Cancel construction?" ) ) ) { | |
| g->m.disarm_trap( examp ); | |
| for( const item &it : pc->components ) { | |
| g->m.add_item_or_charges( g->u.pos(), it ); | |
| } | |
| g->m.partial_con_remove( examp ); | |
| return; | |
| } else { | |
| return; | |
| } | |
| } else { | |
| g->u.assign_activity( activity_id( "ACT_BUILD" ) ); | |
| g->u.activity.placement = g->m.getabs( examp ); | |
| return; | |
| } | |
| } else { | |
| return; | |
| } | |
| } | |
| if( seen && possible >= 99 ) { | |
| add_msg( m_info, _( "That %s looks too dangerous to mess with. Best leave it alone." ), | |
| tr.name() ); | |
| return; | |
| } | |
| // Some traps are not actual traps. Those should get a different query. | |
| if( seen && possible == 0 && | |
| tr.get_avoidance() == 0 ) { // Separated so saying no doesn't trigger the other query. | |
| if( query_yn( _( "There is a %s there. Take down?" ), tr.name() ) ) { | |
| g->m.disarm_trap( examp ); | |
| } | |
| } else if( seen && query_yn( _( "There is a %s there. Disarm?" ), tr.name() ) ) { | |
| g->m.disarm_trap( examp ); | |
| } | |
| } | |
| void iexamine::water_source( player &p, const tripoint &examp ) | |
| { | |
| item water = g->m.water_from( examp ); | |
| ( void ) p; // TODO: use me | |
| liquid_handler::handle_liquid( water, nullptr, 0, &examp ); | |
| } | |
| void iexamine::clean_water_source( player &, const tripoint &examp ) | |
| { | |
| item water = item( "water_clean", 0, item::INFINITE_CHARGES ); | |
| liquid_handler::handle_liquid( water, nullptr, 0, &examp ); | |
| } | |
| const itype *furn_t::crafting_pseudo_item_type() const | |
| { | |
| if( crafting_pseudo_item.empty() ) { | |
| return nullptr; | |
| } | |
| return item::find_type( crafting_pseudo_item ); | |
| } | |
| const itype *furn_t::crafting_ammo_item_type() const | |
| { | |
| const itype *pseudo = crafting_pseudo_item_type(); | |
| if( pseudo->tool && !pseudo->tool->ammo_id.empty() ) { | |
| return item::find_type( ammotype( *pseudo->tool->ammo_id.begin() )->default_ammotype() ); | |
| } | |
| return nullptr; | |
| } | |
| static int count_charges_in_list( const itype *type, const map_stack &items ) | |
| { | |
| for( const auto &candidate : items ) { | |
| if( candidate.type == type ) { | |
| return candidate.charges; | |
| } | |
| } | |
| return 0; | |
| } | |
| void iexamine::reload_furniture( player &p, const tripoint &examp ) | |
| { | |
| const furn_t &f = g->m.furn( examp ).obj(); | |
| const itype *type = f.crafting_pseudo_item_type(); | |
| const itype *ammo = f.crafting_ammo_item_type(); | |
| if( type == nullptr || ammo == nullptr ) { | |
| add_msg( m_info, _( "This %s can not be reloaded!" ), f.name() ); | |
| return; | |
| } | |
| map_stack items_here = g->m.i_at( examp ); | |
| const int amount_in_furn = count_charges_in_list( ammo, items_here ); | |
| const int amount_in_inv = p.charges_of( ammo->get_id() ); | |
| if( amount_in_furn > 0 ) { | |
| if( p.query_yn( _( "The %1$s contains %2$d %3$s. Unload?" ), f.name(), amount_in_furn, | |
| ammo->nname( amount_in_furn ) ) ) { | |
| p.assign_activity( activity_id( "ACT_PICKUP" ) ); | |
| p.activity.targets.emplace_back( map_cursor( examp ), &g->m.i_at( examp ).only_item() ); | |
| p.activity.values.push_back( 0 ); | |
| return; | |
| } | |
| } | |
| //~ %1$s - furniture, %2$d - number, %3$s items. | |
| add_msg( _( "The %1$s contains %2$d %3$s." ), f.name(), amount_in_furn, | |
| ammo->nname( amount_in_furn ) ); | |
| const int max_amount_in_furn = ammo->charges_per_volume( f.max_volume ); | |
| const int max_reload_amount = max_amount_in_furn - amount_in_furn; | |
| if( max_reload_amount <= 0 ) { | |
| return; | |
| } | |
| if( amount_in_inv == 0 ) { | |
| //~ Reloading or restocking a piece of furniture, for example a forge. | |
| add_msg( m_info, _( "You need some %1$s to reload this %2$s." ), ammo->nname( 2 ), | |
| f.name() ); | |
| return; | |
| } | |
| const int max_amount = std::min( amount_in_inv, max_reload_amount ); | |
| //~ Loading fuel or other items into a piece of furniture. | |
| const std::string popupmsg = string_format( _( "Put how many of the %1$s into the %2$s?" ), | |
| ammo->nname( max_amount ), f.name() ); | |
| int amount = string_input_popup() | |
| .title( popupmsg ) | |
| .width( 20 ) | |
| .text( to_string( max_amount ) ) | |
| .only_digits( true ) | |
| .query_int(); | |
| if( amount <= 0 || amount > max_amount ) { | |
| return; | |
| } | |
| p.use_charges( ammo->get_id(), amount ); | |
| auto items = g->m.i_at( examp ); | |
| for( auto &itm : items ) { | |
| if( itm.type == ammo ) { | |
| itm.charges += amount; | |
| amount = 0; | |
| break; | |
| } | |
| } | |
| if( amount != 0 ) { | |
| item it( ammo, calendar::turn, amount ); | |
| g->m.add_item( examp, it ); | |
| } | |
| add_msg( _( "You reload the %s." ), g->m.furnname( examp ) ); | |
| p.moves -= to_moves<int>( 5_seconds ); | |
| } | |
| void iexamine::curtains( player &p, const tripoint &examp ) | |
| { | |
| const bool closed_window_with_curtains = g->m.has_flag( "BARRICADABLE_WINDOW_CURTAINS", examp ) ; | |
| if( g->m.is_outside( p.pos() ) && ( g->m.has_flag( "WALL", examp ) || | |
| closed_window_with_curtains ) ) { | |
| locked_object( p, examp ); | |
| return; | |
| } | |
| const ter_id ter = g->m.ter( examp ); | |
| // Peek through the curtains, or tear them down. | |
| uilist window_menu; | |
| window_menu.text = _( "Do what with the curtains?" ); | |
| window_menu.addentry( 0, ( !ter.obj().close && | |
| closed_window_with_curtains ), 'p', _( "Peek through the closed curtains." ) ); | |
| window_menu.addentry( 1, true, 't', _( "Tear down the curtains." ) ); | |
| window_menu.query(); | |
| const int choice = window_menu.ret; | |
| if( choice == 0 ) { | |
| // Peek | |
| g->peek( examp ); | |
| p.add_msg_if_player( _( "You carefully peek through the curtains." ) ); | |
| } else if( choice == 1 ) { | |
| // Mr. Gorbachev, tear down those curtains! | |
| if( ter == t_window_domestic || ter == t_curtains ) { | |
| g->m.ter_set( examp, t_window_no_curtains ); | |
| } else if( ter == t_window_open ) { | |
| g->m.ter_set( examp, t_window_no_curtains_open ); | |
| } else if( ter == t_window_domestic_taped ) { | |
| g->m.ter_set( examp, t_window_no_curtains_taped ); | |
| } | |
| g->m.spawn_item( p.pos(), "nail", 1, 4, calendar::turn ); | |
| g->m.spawn_item( p.pos(), "sheet", 2, 0, calendar::turn ); | |
| g->m.spawn_item( p.pos(), "stick", 1, 0, calendar::turn ); | |
| g->m.spawn_item( p.pos(), "string_36", 1, 0, calendar::turn ); | |
| p.moves -= to_moves<int>( 10_seconds ); | |
| p.add_msg_if_player( _( "You tear the curtains and curtain rod off the windowframe." ) ); | |
| } else { | |
| p.add_msg_if_player( _( "Never mind." ) ); | |
| } | |
| } | |
| void iexamine::sign( player &p, const tripoint &examp ) | |
| { | |
| std::string existing_signage = g->m.get_signage( examp ); | |
| bool previous_signage_exists = !existing_signage.empty(); | |
| // Display existing message, or lack thereof. | |
| if( p.has_trait( trait_ILLITERATE ) ) { | |
| popup( _( "You're illiterate, and can't read the message on the sign." ) ); | |
| } else if( previous_signage_exists ) { | |
| popup( existing_signage.c_str() ); | |
| } else { | |
| p.add_msg_if_player( m_neutral, _( "Nothing legible on the sign." ) ); | |
| } | |
| // Allow chance to modify message. | |
| std::vector<tool_comp> tools; | |
| std::vector<const item *> filter = p.crafting_inventory().items_with( []( const item & it ) { | |
| return it.has_flag( "WRITE_MESSAGE" ) && it.charges > 0; | |
| } ); | |
| tools.reserve( filter.size() ); | |
| for( const item *writing_item : filter ) { | |
| tools.push_back( tool_comp( writing_item->typeId(), 1 ) ); | |
| } | |
| if( !tools.empty() ) { | |
| // Different messages if the sign already has writing associated with it. | |
| std::string query_message = previous_signage_exists ? | |
| _( "Overwrite the existing message on the sign?" ) : | |
| _( "Add a message to the sign?" ); | |
| std::string ignore_message = _( "You leave the sign alone." ); | |
| if( query_yn( query_message ) ) { | |
| std::string signage = string_input_popup() | |
| .title( _( "Write what?" ) ) | |
| .identifier( "signage" ) | |
| .query_string(); | |
| if( signage.empty() ) { | |
| p.add_msg_if_player( m_neutral, ignore_message ); | |
| } else { | |
| std::string spray_painted_message = previous_signage_exists ? | |
| _( "You overwrite the previous message on the sign with your graffiti." ) : | |
| _( "You graffiti a message onto the sign." ); | |
| g->m.set_signage( examp, signage ); | |
| p.add_msg_if_player( m_info, spray_painted_message ); | |
| p.mod_moves( - 20 * signage.length() ); | |
| p.consume_tools( tools, 1 ); | |
| } | |
| } else { | |
| p.add_msg_if_player( m_neutral, ignore_message ); | |
| } | |
| } | |
| } | |
| static int getNearPumpCount( const tripoint &p ) | |
| { | |
| int result = 0; | |
| for( const tripoint &tmp : g->m.points_in_radius( p, 12 ) ) { | |
| const auto t = g->m.ter( tmp ); | |
| if( t == ter_str_id( "t_gas_pump" ) || t == ter_str_id( "t_gas_pump_a" ) ) { | |
| result++; | |
| } | |
| } | |
| return result; | |
| } | |
| static cata::optional<tripoint> getNearFilledGasTank( const tripoint ¢er, int &gas_units ) | |
| { | |
| cata::optional<tripoint> tank_loc; | |
| int distance = INT_MAX; | |
| gas_units = 0; | |
| for( const tripoint &tmp : g->m.points_in_radius( center, SEEX * 2 ) ) { | |
| if( g->m.ter( tmp ) != ter_str_id( "t_gas_tank" ) ) { | |
| continue; | |
| } | |
| const int new_distance = rl_dist( center, tmp ); | |
| if( new_distance >= distance ) { | |
| continue; | |
| } | |
| if( !tank_loc ) { | |
| // Return a potentially empty tank, but only if we don't find a closer full one. | |
| tank_loc.emplace( tmp ); | |
| } | |
| for( auto &k : g->m.i_at( tmp ) ) { | |
| if( k.made_of( LIQUID ) ) { | |
| distance = new_distance; | |
| tank_loc.emplace( tmp ); | |
| gas_units = k.charges; | |
| break; | |
| } | |
| } | |
| } | |
| return tank_loc; | |
| } | |
| static int getGasDiscountCardQuality( const item &it ) | |
| { | |
| std::set<std::string> tags = it.type->item_tags; | |
| for( const std::string &tag : tags ) { | |
| if( tag.size() > 15 && tag.substr( 0, 15 ) == "DISCOUNT_VALUE_" ) { | |
| return atoi( tag.substr( 15 ).c_str() ); | |
| } | |
| } | |
| return 0; | |
| } | |
| static int findBestGasDiscount( player &p ) | |
| { | |
| int discount = 0; | |
| for( size_t i = 0; i < p.inv.size(); i++ ) { | |
| item &it = p.inv.find_item( i ); | |
| if( it.has_flag( "GAS_DISCOUNT" ) ) { | |
| int q = getGasDiscountCardQuality( it ); | |
| if( q > discount ) { | |
| discount = q; | |
| } | |
| } | |
| } | |
| return discount; | |
| } | |
| static std::string str_to_illiterate_str( std::string s ) | |
| { | |
| if( !g->u.has_trait( trait_ILLITERATE ) ) { | |
| return s; | |
| } else { | |
| for( auto &i : s ) { | |
| i = i + rng( 0, 5 ) - rng( 0, 5 ); | |
| if( i < ' ' ) { | |
| // some control character, most likely not handled correctly be the print functions | |
| i = ' '; | |
| } else if( i == '%' ) { | |
| // avoid characters that trigger formatting in the various print functions | |
| i++; | |
| } | |
| } | |
| return s; | |
| } | |
| } | |
| static std::string getGasDiscountName( int discount ) | |
| { | |
| if( discount == 3 ) { | |
| return str_to_illiterate_str( _( "Platinum member" ) ); | |
| } else if( discount == 2 ) { | |
| return str_to_illiterate_str( _( "Gold member" ) ); | |
| } else if( discount == 1 ) { | |
| return str_to_illiterate_str( _( "Silver member" ) ); | |
| } else { | |
| return str_to_illiterate_str( _( "Beloved customer" ) ); | |
| } | |
| } | |
| static int getGasPricePerLiter( int discount ) | |
| { | |
| // Those prices are in cents | |
| static const int prices[4] = { 1400, 1320, 1200, 1000 }; | |
| if( discount < 0 || discount > 3 ) { | |
| return prices[0]; | |
| } else { | |
| return prices[discount]; | |
| } | |
| } | |
| static cata::optional<tripoint> getGasPumpByNumber( const tripoint &p, int number ) | |
| { | |
| int k = 0; | |
| for( const tripoint &tmp : g->m.points_in_radius( p, 12 ) ) { | |
| const auto t = g->m.ter( tmp ); | |
| if( ( t == ter_str_id( "t_gas_pump" ) || t == ter_str_id( "t_gas_pump_a" ) ) && number == k++ ) { | |
| return tmp; | |
| } | |
| } | |
| return cata::nullopt; | |
| } | |
| static bool toPumpFuel( const tripoint &src, const tripoint &dst, int units ) | |
| { | |
| auto items = g->m.i_at( src ); | |
| for( auto item_it = items.begin(); item_it != items.end(); ++item_it ) { | |
| if( item_it->made_of( LIQUID ) ) { | |
| if( item_it->charges < units ) { | |
| return false; | |
| } | |
| item_it->charges -= units; | |
| item liq_d( item_it->type, calendar::turn, units ); | |
| const auto backup_pump = g->m.ter( dst ); | |
| g->m.ter_set( dst, ter_str_id::NULL_ID() ); | |
| g->m.add_item_or_charges( dst, liq_d ); | |
| g->m.ter_set( dst, backup_pump ); | |
| if( item_it->charges < 1 ) { | |
| items.erase( item_it ); | |
| } | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| static int fromPumpFuel( const tripoint &dst, const tripoint &src ) | |
| { | |
| auto items = g->m.i_at( src ); | |
| for( auto item_it = items.begin(); item_it != items.end(); ++item_it ) { | |
| if( item_it->made_of( LIQUID ) ) { | |
| // how much do we have in the pump? | |
| item liq_d( item_it->type, calendar::turn, item_it->charges ); | |
| // add the charges to the destination | |
| const auto backup_tank = g->m.ter( dst ); | |
| g->m.ter_set( dst, ter_str_id::NULL_ID() ); | |
| g->m.add_item_or_charges( dst, liq_d ); | |
| g->m.ter_set( dst, backup_tank ); | |
| // remove the liquid from the pump | |
| int amount = item_it->charges; | |
| items.erase( item_it ); | |
| return amount; | |
| } | |
| } | |
| return -1; | |
| } | |
| static void turnOnSelectedPump( const tripoint &p, int number ) | |
| { | |
| int k = 0; | |
| for( const tripoint &tmp : g->m.points_in_radius( p, 12 ) ) { | |
| const auto t = g->m.ter( tmp ); | |
| if( t == ter_str_id( "t_gas_pump" ) || t == ter_str_id( "t_gas_pump_a" ) ) { | |
| if( number == k++ ) { | |
| g->m.ter_set( tmp, ter_str_id( "t_gas_pump_a" ) ); | |
| } else { | |
| g->m.ter_set( tmp, ter_str_id( "t_gas_pump" ) ); | |
| } | |
| } | |
| } | |
| } | |
| void iexamine::pay_gas( player &p, const tripoint &examp ) | |
| { | |
| int choice = -1; | |
| const int buy_gas = 1; | |
| const int choose_pump = 2; | |
| const int hack = 3; | |
| const int refund = 4; | |
| if( p.has_trait( trait_ILLITERATE ) ) { | |
| popup( _( "You're illiterate, and can't read the screen." ) ); | |
| } | |
| int pumpCount = getNearPumpCount( examp ); | |
| if( pumpCount == 0 ) { | |
| popup( str_to_illiterate_str( _( "Failure! No gas pumps found!" ) ) ); | |
| return; | |
| } | |
| int tankGasUnits; | |
| const cata::optional<tripoint> pTank_ = getNearFilledGasTank( examp, tankGasUnits ); | |
| if( !pTank_ ) { | |
| popup( str_to_illiterate_str( _( "Failure! No gas tank found!" ) ) ); | |
| return; | |
| } | |
| const tripoint pTank = *pTank_; | |
| if( tankGasUnits == 0 ) { | |
| popup( str_to_illiterate_str( | |
| _( "This station is out of fuel. We apologize for the inconvenience." ) ) ); | |
| return; | |
| } | |
| if( uistate.ags_pay_gas_selected_pump + 1 > pumpCount ) { | |
| uistate.ags_pay_gas_selected_pump = 0; | |
| } | |
| int discount = findBestGasDiscount( p ); | |
| std::string discountName = getGasDiscountName( discount ); | |
| int pricePerUnit = getGasPricePerLiter( discount ); | |
| bool can_hack = ( !p.has_trait( trait_ILLITERATE ) && ( ( p.has_charges( "electrohack", 25 ) ) || | |
| ( p.has_bionic( bionic_id( "bio_fingerhack" ) ) && p.power_level > 24 ) ) ); | |
| uilist amenu; | |
| amenu.selected = 1; | |
| amenu.text = str_to_illiterate_str( _( "Welcome to AutoGas!" ) ); | |
| amenu.addentry( 0, false, -1, str_to_illiterate_str( _( "What would you like to do?" ) ) ); | |
| amenu.addentry( buy_gas, true, 'b', str_to_illiterate_str( _( "Buy gas." ) ) ); | |
| amenu.addentry( refund, true, 'r', str_to_illiterate_str( _( "Refund cash." ) ) ); | |
| std::string gaspumpselected = str_to_illiterate_str( _( "Current gas pump: " ) ) + | |
| to_string( uistate.ags_pay_gas_selected_pump + 1 ); | |
| amenu.addentry( 0, false, -1, gaspumpselected ); | |
| amenu.addentry( choose_pump, true, 'p', str_to_illiterate_str( _( "Choose a gas pump." ) ) ); | |
| amenu.addentry( 0, false, -1, str_to_illiterate_str( _( "Your discount: " ) ) + discountName ); | |
| amenu.addentry( 0, false, -1, str_to_illiterate_str( _( "Your price per gasoline unit: " ) ) + | |
| format_money( pricePerUnit ) ); | |
| if( can_hack ) { | |
| amenu.addentry( hack, true, 'h', _( "Hack console." ) ); | |
| } | |
| amenu.query(); | |
| choice = amenu.ret; | |
| if( choose_pump == choice ) { | |
| uilist amenu; | |
| amenu.selected = uistate.ags_pay_gas_selected_pump; | |
| amenu.text = str_to_illiterate_str( _( "Please choose gas pump:" ) ); | |
| for( int i = 0; i < pumpCount; i++ ) { | |
| amenu.addentry( i, true, -1, | |
| str_to_illiterate_str( _( "Pump " ) ) + to_string( i + 1 ) ); | |
| } | |
| amenu.query(); | |
| choice = amenu.ret; | |
| if( choice < 0 ) { | |
| return; | |
| } | |
| uistate.ags_pay_gas_selected_pump = choice; | |
| turnOnSelectedPump( examp, uistate.ags_pay_gas_selected_pump ); | |
| return; | |
| } | |
| if( buy_gas == choice ) { | |
| int money = p.charges_of( "cash_card" ); | |
| if( money < pricePerUnit ) { | |
| popup( str_to_illiterate_str( | |
| _( "Not enough money, please refill your cash card." ) ) ); //or ride on a solar car, ha ha ha | |
| return; | |
| } | |
| int maximum_liters = std::min( money / pricePerUnit, tankGasUnits / 1000 ); | |
| std::string popupmsg = string_format( | |
| _( "How many liters of gasoline to buy? Max: %d L. (0 to cancel) " ), maximum_liters ); | |
| int liters = string_input_popup() | |
| .title( popupmsg ) | |
| .width( 20 ) | |
| .text( to_string( maximum_liters ) ) | |
| .only_digits( true ) | |
| .query_int(); | |
| if( liters <= 0 ) { | |
| return; | |
| } | |
| if( liters > maximum_liters ) { | |
| liters = maximum_liters; | |
| } | |
| const cata::optional<tripoint> pGasPump = getGasPumpByNumber( examp, | |
| uistate.ags_pay_gas_selected_pump ); | |
| if( !pGasPump || !toPumpFuel( pTank, *pGasPump, liters * 1000 ) ) { | |
| return; | |
| } | |
| sounds::sound( p.pos(), 6, sounds::sound_t::activity, _( "Glug Glug Glug" ), true, "tool", | |
| "gaspump" ); | |
| int cost = liters * pricePerUnit; | |
| money -= cost; | |
| p.use_charges( "cash_card", cost ); | |
| add_msg( m_info, _( "Your cash cards now hold %s." ), format_money( money ) ); | |
| p.moves -= to_moves<int>( 5_seconds ); | |
| return; | |
| } | |
| if( hack == choice ) { | |
| switch( hack_attempt( p ) ) { | |
| case HACK_UNABLE: | |
| break; | |
| case HACK_FAIL: | |
| p.add_memorial_log( pgettext( "memorial_male", "Set off an alarm." ), | |
| pgettext( "memorial_female", "Set off an alarm." ) ); | |
| sounds::sound( p.pos(), 60, sounds::sound_t::music, _( "an alarm sound!" ), true, "environment", | |
| "alarm" ); | |
| if( examp.z > 0 && !g->events.queued( EVENT_WANTED ) ) { | |
| g->events.add( EVENT_WANTED, calendar::turn + 30_minutes, 0, p.global_sm_location() ); | |
| } | |
| break; | |
| case HACK_NOTHING: | |
| add_msg( _( "Nothing happens." ) ); | |
| break; | |
| case HACK_SUCCESS: | |
| const cata::optional<tripoint> pGasPump = getGasPumpByNumber( examp, | |
| uistate.ags_pay_gas_selected_pump ); | |
| if( pGasPump && toPumpFuel( pTank, *pGasPump, tankGasUnits ) ) { | |
| add_msg( _( "You hack the terminal and route all available fuel to your pump!" ) ); | |
| sounds::sound( p.pos(), 6, sounds::sound_t::activity, | |
| _( "Glug Glug Glug Glug Glug Glug Glug Glug Glug" ), true, "tool", "gaspump" ); | |
| } else { | |
| add_msg( _( "Nothing happens." ) ); | |
| } | |
| break; | |
| } | |
| } | |
| if( refund == choice ) { | |
| const int pos = p.inv.position_by_type( itype_id( "cash_card" ) ); | |
| if( pos == INT_MIN ) { | |
| add_msg( _( "Never mind." ) ); | |
| return; | |
| } | |
| item *cashcard = &( p.i_at( pos ) ); | |
| // Okay, we have a cash card. Now we need to know what's left in the pump. | |
| const cata::optional<tripoint> pGasPump = getGasPumpByNumber( examp, | |
| uistate.ags_pay_gas_selected_pump ); | |
| int amount = pGasPump ? fromPumpFuel( pTank, *pGasPump ) : 0; | |
| if( amount >= 0 ) { | |
| sounds::sound( p.pos(), 6, sounds::sound_t::activity, _( "Glug Glug Glug" ), true, "tool", | |
| "gaspump" ); | |
| cashcard->charges += amount * pricePerUnit / 1000.0f; | |
| add_msg( m_info, _( "Your cash cards now hold %s." ), format_money( p.charges_of( "cash_card" ) ) ); | |
| p.moves -= to_moves<int>( 5_seconds ); | |
| return; | |
| } else { | |
| popup( _( "Unable to refund, no fuel in pump." ) ); | |
| return; | |
| } | |
| } | |
| } | |
| void iexamine::ledge( player &p, const tripoint &examp ) | |
| { | |
| uilist cmenu; | |
| cmenu.text = _( "There is a ledge here. What do you want to do?" ); | |
| cmenu.addentry( 1, true, 'j', _( "Jump over." ) ); | |
| cmenu.addentry( 2, true, 'c', _( "Climb down." ) ); | |
| cmenu.query(); | |
| switch( cmenu.ret ) { | |
| case 1: { | |
| tripoint dest( p.posx() + 2 * sgn( examp.x - p.posx() ), p.posy() + 2 * sgn( examp.y - p.posy() ), | |
| p.posz() ); | |
| if( p.get_str() < 4 ) { | |
| add_msg( m_warning, _( "You are too weak to jump over an obstacle." ) ); | |
| } else if( 100 * p.weight_carried() / p.weight_capacity() > 25 ) { | |
| add_msg( m_warning, _( "You are too burdened to jump over an obstacle." ) ); | |
| } else if( !g->m.valid_move( examp, dest, false, true ) ) { | |
| add_msg( m_warning, _( "You cannot jump over an obstacle - something is blocking the way." ) ); | |
| } else if( g->critter_at( dest ) ) { | |
| add_msg( m_warning, _( "You cannot jump over an obstacle - there is %s blocking the way." ), | |
| g->critter_at( dest )->disp_name() ); | |
| } else if( g->m.ter( dest ).obj().trap == tr_ledge ) { | |
| add_msg( m_warning, _( "You are not going to jump over an obstacle only to fall down." ) ); | |
| } else { | |
| add_msg( m_info, _( "You jump over an obstacle." ) ); | |
| p.increase_activity_level( LIGHT_EXERCISE ); | |
| p.setpos( dest ); | |
| } | |
| break; | |
| } | |
| case 2: { | |
| if( !g->m.has_zlevels() ) { | |
| // No climbing down in 2D mode | |
| return; | |
| } | |
| if( !g->m.valid_move( p.pos(), examp, false, true ) ) { | |
| // Covered with something | |
| return; | |
| } | |
| tripoint where = examp; | |
| tripoint below = examp; | |
| below.z--; | |
| while( g->m.valid_move( where, below, false, true ) ) { | |
| where.z--; | |
| below.z--; | |
| } | |
| const int height = examp.z - where.z; | |
| if( height == 0 ) { | |
| p.add_msg_if_player( _( "You can't climb down there" ) ); | |
| return; | |
| } | |
| const int climb_cost = p.climbing_cost( where, examp ); | |
| const auto fall_mod = p.fall_damage_mod(); | |
| std::string query_str = ngettext( "Looks like %d story. Jump down?", | |
| "Looks like %d stories. Jump down?", | |
| height ); | |
| if( height > 1 && !query_yn( query_str.c_str(), height ) ) { | |
| return; | |
| } else if( height == 1 ) { | |
| std::string query; | |
| p.increase_activity_level( MODERATE_EXERCISE ); | |
| if( climb_cost <= 0 && fall_mod > 0.8 ) { | |
| query = _( "You probably won't be able to get up and jumping down may hurt. Jump?" ); | |
| } else if( climb_cost <= 0 ) { | |
| query = _( "You probably won't be able to get back up. Climb down?" ); | |
| } else if( climb_cost < 200 ) { | |
| query = _( "You should be able to climb back up easily if you climb down there. Climb down?" ); | |
| } else { | |
| query = _( "You may have problems climbing back up. Climb down?" ); | |
| } | |
| if( !query_yn( query.c_str() ) ) { | |
| return; | |
| } | |
| } | |
| p.moves -= to_moves<int>( 1_seconds + 1_seconds * fall_mod ); | |
| p.setpos( examp ); | |
| if( climb_cost > 0 || rng_float( 0.8, 1.0 ) > fall_mod ) { | |
| // One tile of falling less (possibly zero) | |
| g->vertical_move( -1, true ); | |
| } | |
| g->m.creature_on_trap( p ); | |
| break; | |
| } | |
| default: | |
| popup( _( "You decided to step back from the ledge." ) ); | |
| break; | |
| } | |
| } | |
| static player &player_on_couch( player &p, const tripoint &autodoc_loc, player &null_patient, | |
| bool &adjacent_couch, tripoint &couch_pos ) | |
| { | |
| for( const auto &couch_loc : g->m.points_in_radius( autodoc_loc, 1, 0 ) ) { | |
| const furn_str_id couch( "f_autodoc_couch" ); | |
| if( g->m.furn( couch_loc ) == couch ) { | |
| adjacent_couch = true; | |
| couch_pos = couch_loc; | |
| if( p.pos() == couch_loc ) { | |
| return p; | |
| } | |
| for( const npc *e : g->allies() ) { | |
| if( e->pos() == couch_loc ) { | |
| return *g->critter_by_id<player>( e->getID() ); | |
| } | |
| } | |
| } | |
| } | |
| return null_patient; | |
| } | |
| static item &cyborg_on_couch( const tripoint &couch_pos, item &null_cyborg ) | |
| { | |
| for( item &it : g->m.i_at( couch_pos ) ) { | |
| if( it.typeId() == "bot_broken_cyborg" || it.typeId() == "bot_prototype_cyborg" ) { | |
| return it; | |
| } | |
| if( it.typeId() == "corpse" ) { | |
| if( it.get_mtype()->id == "mon_broken_cyborg" || it.get_mtype()->id == "mon_prototype_cyborg" ) { | |
| return it; | |
| } | |
| } | |
| } | |
| return null_cyborg; | |
| } | |
| static player &best_installer( player &p, player &null_player, int difficulty ) | |
| { | |
| float player_skill = p.bionics_adjusted_skill( skill_firstaid, | |
| skill_computer, | |
| skill_electronics ); | |
| std::vector< std::pair<float, int>> ally_skills; | |
| ally_skills.reserve( g->allies().size() ); | |
| for( size_t i = 0; i < g->allies().size() ; i ++ ) { | |
| std::pair<float, int> ally_skill; | |
| const npc *e = g->allies()[ i ]; | |
| player &ally = *g->critter_by_id<player>( e->getID() ); | |
| ally_skill.second = i; | |
| ally_skill.first = ally.bionics_adjusted_skill( skill_firstaid, | |
| skill_computer, | |
| skill_electronics ); | |
| ally_skills.push_back( ally_skill ); | |
| } | |
| std::sort( ally_skills.begin(), ally_skills.end(), [&]( const std::pair<float, int> &lhs, | |
| const std::pair<float, int> &rhs ) { | |
| return rhs.first < lhs.first; | |
| } ); | |
| int player_cos = bionic_manip_cos( player_skill, true, difficulty ); | |
| for( size_t i = 0; i < g->allies().size() ; i ++ ) { | |
| if( ally_skills[ i ].first > player_skill ) { | |
| const npc *e = g->allies()[ ally_skills[ i ].second ]; | |
| player &ally = *g->critter_by_id<player>( e->getID() ); | |
| int ally_cos = bionic_manip_cos( ally_skills[ i ].first, true, difficulty ); | |
| if( e->has_effect( effect_sleep ) ) { | |
| //~ %1$s is the name of the ally | |
| if( !g->u.query_yn( string_format( | |
| _( "<color_white>%1$s is asleep, but has a <color_green>%2$d<color_white> chance of success compared to your <color_red>%3$d<color_white> chance of success. Continue with a higher risk of failure?</color>" ), | |
| ally.disp_name(), ally_cos, player_cos ) ) ) { | |
| return null_player; | |
| } else { | |
| continue; | |
| } | |
| } | |
| //~ %1$s is the name of the ally | |
| add_msg( _( "%1$s will perform the operation with a %2$d chance of success." ), ally.disp_name(), | |
| ally_cos ); | |
| return ally; | |
| } else { | |
| break; | |
| } | |
| } | |
| return p; | |
| } | |
| void iexamine::autodoc( player &p, const tripoint &examp ) | |
| { | |
| enum options { | |
| INSTALL_CBM, | |
| UNINSTALL_CBM, | |
| BONESETTING, | |
| }; | |
| bool adjacent_couch = false; | |
| static avatar null_player; | |
| tripoint couch_pos; | |
| player &patient = player_on_couch( p, examp, null_player, adjacent_couch, couch_pos ); | |
| static item null_cyborg; | |
| item &cyborg = cyborg_on_couch( couch_pos, null_cyborg ); | |
| if( !adjacent_couch ) { | |
| popup( _( "No connected couches found. Operation impossible. Exiting." ) ); | |
| return; | |
| } | |
| if( &patient == &null_player ) { | |
| if( &cyborg != &null_cyborg ) { | |
| if( cyborg.typeId() == "corpse" && !cyborg.active ) { | |
| popup( _( "Patient is dead. Please remove corpse to proceed. Exiting." ) ); | |
| return; | |
| } else if( cyborg.typeId() == "bot_broken_cyborg" || cyborg.typeId() == "corpse" ) { | |
| popup( _( "ERROR Bionic Level Assessement : FULL CYBORG. Autodoc Mk. XI can't opperate. Please move patient to appropriate facility. Exiting." ) ); | |
| return; | |
| } | |
| uilist cmenu; | |
| cmenu.text = _( "Autodoc Mk. XI. Status: Online. Please choose operation." ); | |
| cmenu.addentry( 1, true, 'i', _( "Choose Compact Bionic Module to install." ) ); | |
| cmenu.addentry( 2, true, 'u', _( "Choose installed bionic to uninstall." ) ); | |
| cmenu.query(); | |
| switch( cmenu.ret ) { | |
| case 1: { | |
| popup( _( "ERROR NO SPACE AVAILABLE. Operation impossible. Exiting." ) ); | |
| break; | |
| } | |
| case 2: { | |
| std::vector<std::string> choice_names; | |
| choice_names.push_back( "Personality_Override" ); | |
| for( size_t i = 0; i < 6; i++ ) { | |
| choice_names.push_back( "C0RR#PTED?D#TA" ); | |
| } | |
| int choice_index = uilist( _( "Choose bionic to uninstall" ), choice_names ); | |
| if( choice_index == 0 ) { | |
| g->save_cyborg( &cyborg, couch_pos, p ); | |
| } else { | |
| popup( _( "UNKNOWN COMMAND. Autodoc Mk. XI. Crashed." ) ); | |
| return; | |
| } | |
| break; | |
| } | |
| default: | |
| return; | |
| } | |
| return; | |
| } else { | |
| popup( _( "No patient found located on the connected couches. Operation impossible. Exiting." ) ); | |
| return; | |
| } | |
| } else if( patient.has_effect( effect_under_op ) ) { | |
| popup( _( "Operation underway. Please wait until the end of the current procedure. Estimated time remaining: %s." ), | |
| to_string( patient.get_effect_dur( effect_under_op ) ) ); | |
| p.add_msg_if_player( m_info, _( "The autodoc is working on %s." ), patient.disp_name() ); | |
| return; | |
| } | |
| uilist amenu; | |
| amenu.text = _( "Autodoc Mk. XI. Status: Online. Please choose operation" ); | |
| amenu.addentry( INSTALL_CBM, true, 'i', _( "Choose Compact Bionic Module to install" ) ); | |
| amenu.addentry( UNINSTALL_CBM, true, 'u', _( "Choose installed bionic to uninstall" ) ); | |
| amenu.addentry( BONESETTING, true, 's', _( "Splint broken limbs" ) ); | |
| amenu.query(); | |
| bool needs_anesthesia = true; | |
| // Legacy | |
| std::vector<item_comp> acomps; | |
| std::vector<tool_comp> anesth_kit; | |
| int drug_count = 0; | |
| if( patient.has_trait( trait_NOPAIN ) || patient.has_bionic( bionic_id( "bio_painkiller" ) ) || | |
| amenu.ret > 1 ) { | |
| needs_anesthesia = false; | |
| } else { | |
| const inventory &crafting_inv = p.crafting_inventory(); | |
| std::vector<const item *> a_filter = crafting_inv.items_with( []( const item & it ) { | |
| return it.has_quality( quality_id( "ANESTHESIA" ) ); | |
| } ); | |
| std::vector<const item *> b_filter = crafting_inv.items_with( []( const item & it ) { | |
| return it.has_flag( "ANESTHESIA" ); // legacy | |
| } ); | |
| for( const item *anesthesia_item : a_filter ) { | |
| if( anesthesia_item->ammo_remaining() >= 1 ) { | |
| anesth_kit.push_back( tool_comp( anesthesia_item->typeId(), 1 ) ); | |
| drug_count += anesthesia_item->ammo_remaining(); | |
| } | |
| } | |
| for( const item *anesthesia_item : b_filter ) { | |
| acomps.push_back( item_comp( anesthesia_item->typeId(), 1 ) ); // legacy | |
| } | |
| } | |
| switch( amenu.ret ) { | |
| case INSTALL_CBM: { | |
| const item_location bionic = game_menus::inv::install_bionic( p, patient ); | |
| if( !bionic ) { | |
| return; | |
| } | |
| const item *it = bionic.get_item(); | |
| const itype *itemtype = it->type; | |
| player &installer = best_installer( p, null_player, itemtype->bionic->difficulty ); | |
| if( &installer == &null_player ) { | |
| return; | |
| } | |
| const float volume_anesth = itemtype->bionic->difficulty * 20 * 2; // 2ml/min | |
| if( patient.can_install_bionics( ( *itemtype ), installer, true ) ) { | |
| const time_duration duration = itemtype->bionic->difficulty * 20_minutes; | |
| patient.introduce_into_anesthesia( duration, installer, needs_anesthesia ); | |
| std::vector<item_comp> comps; | |
| comps.push_back( item_comp( it->typeId(), 1 ) ); | |
| p.consume_items( comps, 1, is_crafting_component ); | |
| if( needs_anesthesia ) { | |
| // Consume obsolete anesthesia first | |
| if( acomps.empty() ) { | |
| p.consume_tools( anesth_kit, volume_anesth ); | |
| } else { | |
| // Legacy | |
| p.consume_items( acomps, 1, is_crafting_component ); | |
| } | |
| } | |
| installer.mod_moves( -to_moves<int>( 1_minutes ) ); | |
| patient.add_effect( effect_under_op, duration, num_bp ); | |
| patient.install_bionics( ( *itemtype ), installer, true ); | |
| } | |
| break; | |
| } | |
| case UNINSTALL_CBM: { | |
| bionic_collection installed_bionics = *patient.my_bionics; | |
| if( installed_bionics.empty() ) { | |
| //~ %1$s is patient name | |
| popup_player_or_npc( patient, _( "You don't have any bionics installed." ), | |
| _( "%1$s doesn't have any bionics installed." ) ); | |
| return; | |
| } | |
| for( auto &bio : installed_bionics ) { | |
| if( bio.id != bionic_id( "bio_power_storage" ) || | |
| bio.id != bionic_id( "bio_power_storage_mkII" ) ) { | |
| if( item::type_is_defined( bio.id.str() ) ) {// put cbm items in your inventory | |
| item bionic_to_uninstall( bio.id.str(), calendar::turn ); | |
| bionic_to_uninstall.set_flag( "IN_CBM" ); | |
| g->u.i_add( bionic_to_uninstall ); | |
| } | |
| } | |
| } | |
| const item_location bionic = game_menus::inv::uninstall_bionic( p, patient ); | |
| if( !bionic ) { | |
| g->u.remove_items_with( []( const item & it ) {// remove cbm items from inventory | |
| return it.has_flag( "IN_CBM" ); | |
| } ); | |
| return; | |
| } | |
| const item *it = bionic.get_item(); | |
| const itype *itemtype = it->type; | |
| const bionic_id &bid = itemtype->bionic->id; | |
| g->u.remove_items_with( []( const item & it ) {// remove cbm items from inventory | |
| return it.has_flag( "IN_CBM" ); | |
| } ); | |
| // Malfunctioning bionics that don't have associated items and get a difficulty of 12 | |
| const int difficulty = itemtype->bionic ? itemtype->bionic->difficulty : 12; | |
| const float volume_anesth = difficulty * 20 * 2; // 2ml/min | |
| player &installer = best_installer( p, null_player, difficulty ); | |
| if( &installer == &null_player ) { | |
| return; | |
| } | |
| if( patient.can_uninstall_bionic( bid, installer, true ) ) { | |
| const time_duration duration = difficulty * 20_minutes; | |
| patient.introduce_into_anesthesia( duration, installer, needs_anesthesia ); | |
| if( needs_anesthesia ) { | |
| if( acomps.empty() ) { | |
| // consume obsolete anesthesia first | |
| p.consume_tools( anesth_kit, volume_anesth ); | |
| } else { | |
| p.consume_items( acomps, 1, is_crafting_component ); // legacy | |
| } | |
| } | |
| installer.mod_moves( -to_moves<int>( 1_minutes ) ); | |
| patient.add_effect( effect_under_op, duration, num_bp ); | |
| patient.uninstall_bionic( bid, installer, true ); | |
| } | |
| break; | |
| } | |
| case BONESETTING: { | |
| int broken_limbs_count = 0; | |
| for( int i = 0; i < num_hp_parts; i++ ) { | |
| const bool broken = patient.get_hp( static_cast<hp_part>( i ) ) <= 0; | |
| body_part part = patient.hp_to_bp( static_cast<hp_part>( i ) ); | |
| effect &existing_effect = patient.get_effect( effect_mending, part ); | |
| // Skip part if not broken or already healed 50% | |
| if( !broken || ( !existing_effect.is_null() && | |
| existing_effect.get_duration() > | |
| existing_effect.get_max_duration() - 5_days - 1_turns ) ) { | |
| continue; | |
| } | |
| broken_limbs_count++; | |
| patient.moves -= 500; | |
| patient.add_msg_player_or_npc( m_good, _( "The machine rapidly sets and splints your broken %s." ), | |
| _( "The machine rapidly sets and splints <npcname>'s broken %s." ), | |
| body_part_name( part ) ); | |
| // TODO: fail here if unable to perform the action, i.e. can't wear more, trait mismatch. | |
| if( !patient.worn_with_flag( "SPLINT", part ) ) { | |
| item splint; | |
| if( i == hp_arm_l || i == hp_arm_r ) { | |
| splint = item( "arm_splint", 0 ); | |
| } else if( i == hp_leg_l || i == hp_leg_r ) { | |
| splint = item( "leg_splint", 0 ); | |
| } | |
| item &equipped_splint = patient.i_add( splint ); | |
| cata::optional<std::list<item>::iterator> worn_item = | |
| patient.wear( equipped_splint, false ); | |
| if( worn_item && !patient.worn_with_flag( "SPLINT", part ) ) { | |
| patient.change_side( **worn_item, false ); | |
| } | |
| } | |
| patient.add_effect( effect_mending, 0_turns, part, true ); | |
| effect &mending_effect = patient.get_effect( effect_mending, part ); | |
| mending_effect.set_duration( mending_effect.get_max_duration() - 5_days ); | |
| } | |
| if( broken_limbs_count == 0 ) { | |
| //~ %1$s is patient name | |
| popup_player_or_npc( patient, _( "You have no limbs that require splinting." ), | |
| _( "%1$s doesn't have limbs that require splinting." ) ); | |
| } | |
| break; | |
| } | |
| default: | |
| return; | |
| } | |
| } | |
| namespace sm_rack | |
| { | |
| const int MIN_CHARCOAL = 100; | |
| const int CHARCOAL_PER_LITER = 25; | |
| const units::volume MAX_FOOD_VOLUME = units::from_liter( 20 ); | |
| const units::volume MAX_FOOD_VOLUME_PORTABLE = units::from_liter( 15 ); | |
| } // namespace sm_rack | |
| static int get_charcoal_charges( units::volume food ) | |
| { | |
| const int charcoal = to_liter( food ) * sm_rack::CHARCOAL_PER_LITER; | |
| return std::max( charcoal, sm_rack::MIN_CHARCOAL ); | |
| } | |
| static bool is_non_rotten_crafting_component( const item &it ) | |
| { | |
| return is_crafting_component( it ) && !it.rotten(); | |
| } | |
| static void mill_activate( player &p, const tripoint &examp ) | |
| { | |
| const furn_id cur_mill_type = g->m.furn( examp ); | |
| furn_id next_mill_type = f_null; | |
| if( cur_mill_type == f_wind_mill ) { | |
| next_mill_type = f_wind_mill_active; | |
| } else if( cur_mill_type == f_water_mill ) { | |
| next_mill_type = f_water_mill_active; | |
| } else { | |
| debugmsg( "Examined furniture has action mill_activate, but is of type %s", | |
| g->m.furn( examp ).id().c_str() ); | |
| return; | |
| } | |
| bool food_present = false; | |
| map_stack items = g->m.i_at( examp ); | |
| units::volume food_volume = 0_ml; | |
| for( item &it : items ) { | |
| if( it.typeId() == "flour" ) { | |
| add_msg( _( "This mill already contains flour." ) ); | |
| add_msg( _( "Remove it before starting the mill again." ) ); | |
| return; | |
| } | |
| if( it.has_flag( "MILLABLE" ) ) { | |
| food_present = true; | |
| food_volume += it.volume(); | |
| continue; | |
| } | |
| if( !it.has_flag( "MILLABLE" ) ) { | |
| add_msg( m_bad, _( "This rack contains %s, which can't be milled!" ), it.tname( 1, | |
| false ) ); | |
| add_msg( _( "You remove the %s from the mill." ), it.tname() ); | |
| g->m.add_item_or_charges( p.pos(), it ); | |
| g->m.i_rem( examp, &it ); | |
| p.mod_moves( -p.item_handling_cost( it ) ); | |
| return; | |
| } | |
| } | |
| if( !food_present ) { | |
| add_msg( _( "This mill is empty. Fill it with starchy products such as wheat, barley or oats and try again." ) ); | |
| return; | |
| } | |
| // TODO : currently mill just uses sm_rack defined max volume | |
| if( food_volume > sm_rack::MAX_FOOD_VOLUME ) { | |
| add_msg( _( "This mill is overloaded with products, and the millstone can't turn. Remove some and try again." ) ); | |
| add_msg( pgettext( "volume units", "You think that you can load about %s %s in it." ), | |
| format_volume( sm_rack::MAX_FOOD_VOLUME ), volume_units_long() ); | |
| return; | |
| } | |
| for( auto &it : g->m.i_at( examp ) ) { | |
| if( it.has_flag( "MILLABLE" ) ) { | |
| // Do one final rot check before milling, then apply the PROCESSING flag to prevent further checks. | |
| it.process_temperature_rot( 1, examp, nullptr ); | |
| it.set_flag( "PROCESSING" ); | |
| } | |
| } | |
| g->m.furn_set( examp, next_mill_type ); | |
| item result( "fake_milling_item", calendar::turn ); | |
| result.item_counter = to_turns<int>( 6_hours ); | |
| result.activate(); | |
| g->m.add_item( examp, result ); | |
| add_msg( _( "You remove the brake on the millstone and it slowly starts to turn." ) ); | |
| } | |
| static void smoker_activate( player &p, const tripoint &examp ) | |
| { | |
| furn_id cur_smoker_type = g->m.furn( examp ); | |
| furn_id next_smoker_type = f_null; | |
| const bool portable = g->m.furn( examp ) == furn_str_id( "f_metal_smoking_rack" ) || | |
| g->m.furn( examp ) == furn_str_id( "f_metal_smoking_rack_active" ); | |
| if( cur_smoker_type == f_smoking_rack ) { | |
| next_smoker_type = f_smoking_rack_active; | |
| } else if( cur_smoker_type == f_metal_smoking_rack ) { | |
| next_smoker_type = f_metal_smoking_rack_active; | |
| } else { | |
| debugmsg( "Examined furniture has action smoker_activate, but is of type %s", | |
| g->m.furn( examp ).id().c_str() ); | |
| return; | |
| } | |
| bool food_present = false; | |
| bool charcoal_present = false; | |
| map_stack items = g->m.i_at( examp ); | |
| units::volume food_volume = 0_ml; | |
| item *charcoal = nullptr; | |
| for( item &it : items ) { | |
| if( it.has_flag( "SMOKED" ) && !it.has_flag( "SMOKABLE" ) ) { | |
| add_msg( _( "This rack already contains smoked food." ) ); | |
| add_msg( _( "Remove it before firing the smoking rack again." ) ); | |
| return; | |
| } | |
| if( it.has_flag( "SMOKABLE" ) ) { | |
| food_present = true; | |
| food_volume += it.volume(); | |
| continue; | |
| } | |
| if( it.typeId() == "charcoal" ) { | |
| charcoal_present = true; | |
| charcoal = ⁢ | |
| } | |
| if( it.typeId() != "charcoal" && !it.has_flag( "SMOKABLE" ) ) { | |
| add_msg( m_bad, _( "This rack contains %s, which can't be smoked!" ), it.tname( 1, | |
| false ) ); | |
| add_msg( _( "You remove %s from the rack." ), it.tname() ); | |
| g->m.add_item_or_charges( p.pos(), it ); | |
| g->m.i_rem( examp, &it ); | |
| p.mod_moves( -p.item_handling_cost( it ) ); | |
| return; | |
| } | |
| if( it.has_flag( "SMOKED" ) && it.has_flag( "SMOKABLE" ) ) { | |
| add_msg( _( "This rack has some smoked food that might be dehydrated by smoking it again." ) ); | |
| } | |
| } | |
| if( !food_present ) { | |
| add_msg( _( "This rack is empty. Fill it with raw meat, fish or sausages and try again." ) ); | |
| return; | |
| } | |
| if( !charcoal_present ) { | |
| add_msg( _( "There is no charcoal in the rack." ) ); | |
| return; | |
| } | |
| if( portable && food_volume > sm_rack::MAX_FOOD_VOLUME_PORTABLE ) { | |
| add_msg( _( "This rack is overloaded with food, and it blocks the flow of smoke. Remove some and try again." ) ); | |
| add_msg( _( "You think that you can load about %s %s in it." ), | |
| format_volume( sm_rack::MAX_FOOD_VOLUME_PORTABLE ), volume_units_long() ); | |
| return; | |
| } else if( food_volume > sm_rack::MAX_FOOD_VOLUME ) { | |
| add_msg( _( "This rack is overloaded with food, and it blocks the flow of smoke. Remove some and try again." ) ); | |
| add_msg( _( "You think that you can load about %s %s in it." ), | |
| format_volume( sm_rack::MAX_FOOD_VOLUME ), volume_units_long() ); | |
| return; | |
| } | |
| int char_charges = get_charcoal_charges( food_volume ); | |
| if( count_charges_in_list( charcoal->type, g->m.i_at( examp ) ) < char_charges ) { | |
| add_msg( _( "There is not enough charcoal in the rack to smoke this much food." ) ); | |
| add_msg( _( "You need at least %1$s pieces of charcoal, and the smoking rack has %2$s inside." ), | |
| char_charges, count_charges_in_list( charcoal->type, g->m.i_at( examp ) ) ); | |
| return; | |
| } | |
| if( !p.has_charges( "fire", 1 ) ) { | |
| add_msg( _( "This smoking rack is ready to be fired, but you have no fire source." ) ); | |
| return; | |
| } else if( !query_yn( _( "Fire the smoking rack?" ) ) ) { | |
| return; | |
| } | |
| p.use_charges( "fire", 1 ); | |
| for( auto &it : g->m.i_at( examp ) ) { | |
| if( it.has_flag( "SMOKABLE" ) ) { | |
| it.process_temperature_rot( 1, examp, nullptr ); | |
| it.set_flag( "PROCESSING" ); | |
| } | |
| } | |
| g->m.furn_set( examp, next_smoker_type ); | |
| if( charcoal->charges == char_charges ) { | |
| g->m.i_rem( examp, charcoal ); | |
| } else { | |
| charcoal->charges -= char_charges; | |
| } | |
| item result( "fake_smoke_plume", calendar::turn ); | |
| result.item_counter = to_turns<int>( 6_hours ); | |
| result.activate(); | |
| g->m.add_item( examp, result ); | |
| add_msg( _( "You light a small fire under the rack and it starts to smoke." ) ); | |
| } | |
| void iexamine::mill_finalize( player &, const tripoint &examp, const time_point &start_time ) | |
| { | |
| const furn_id cur_mill_type = g->m.furn( examp ); | |
| furn_id next_mill_type = f_null; | |
| if( cur_mill_type == f_wind_mill_active ) { | |
| next_mill_type = f_wind_mill; | |
| } else if( cur_mill_type == f_water_mill_active ) { | |
| next_mill_type = f_water_mill; | |
| } else { | |
| debugmsg( "Furniture executed action mill_finalize, but is of type %s", | |
| g->m.furn( examp ).id().c_str() ); | |
| return; | |
| } | |
| map_stack items = g->m.i_at( examp ); | |
| if( items.empty() ) { | |
| g->m.furn_set( examp, next_mill_type ); | |
| return; | |
| } | |
| for( item &it : items ) { | |
| if( it.has_flag( "MILLABLE" ) && it.get_comestible() ) { | |
| it.calc_rot_while_processing( 6_hours ); | |
| item result( "flour", start_time + 6_hours, it.charges * 15 ); | |
| // Set flag to tell set_relative_rot() to calc from bday not now | |
| result.set_flag( "PROCESSING_RESULT" ); | |
| result.set_relative_rot( it.get_relative_rot() ); | |
| result.unset_flag( "PROCESSING_RESULT" ); | |
| it = result; | |
| } | |
| } | |
| g->m.furn_set( examp, next_mill_type ); | |
| } | |
| static void smoker_finalize( player &, const tripoint &examp, const time_point &start_time ) | |
| { | |
| furn_id cur_smoker_type = g->m.furn( examp ); | |
| furn_id next_smoker_type = f_null; | |
| if( cur_smoker_type == f_smoking_rack_active ) { | |
| next_smoker_type = f_smoking_rack; | |
| } else if( cur_smoker_type == f_metal_smoking_rack_active ) { | |
| next_smoker_type = f_metal_smoking_rack; | |
| } else { | |
| debugmsg( "Furniture executed action smoker_finalize, but is of type %s", | |
| g->m.furn( examp ).id().c_str() ); | |
| return; | |
| } | |
| map_stack items = g->m.i_at( examp ); | |
| if( items.empty() ) { | |
| g->m.furn_set( examp, next_smoker_type ); | |
| return; | |
| } | |
| for( item &it : items ) { | |
| if( it.has_flag( "SMOKABLE" ) && it.get_comestible() ) { | |
| if( it.get_comestible()->smoking_result.empty() ) { | |
| it.unset_flag( "PROCESSING" ); | |
| } else { | |
| it.calc_rot_while_processing( 6_hours ); | |
| item result( it.get_comestible()->smoking_result, start_time + 6_hours, it.charges ); | |
| // Set flag to tell set_relative_rot() to calc from bday not now | |
| result.set_flag( "PROCESSING_RESULT" ); | |
| result.set_relative_rot( it.get_relative_rot() ); | |
| result.unset_flag( "PROCESSING_RESULT" ); | |
| it = result; | |
| } | |
| } | |
| } | |
| g->m.furn_set( examp, next_smoker_type ); | |
| } | |
| static void smoker_load_food( player &p, const tripoint &examp, | |
| const units::volume &remaining_capacity ) | |
| { | |
| std::vector<item_comp> comps; | |
| if( g->m.furn( examp ) == furn_str_id( "f_smoking_rack_active" ) || | |
| g->m.furn( examp ) == furn_str_id( "f_metal_smoking_rack_active" ) ) { | |
| p.add_msg_if_player( _( "You can't place more food while it's smoking." ) ); | |
| return; | |
| } | |
| // filter SMOKABLE food | |
| inventory inv = p.crafting_inventory(); | |
| inv.remove_items_with( []( const item & it ) { | |
| return it.rotten(); | |
| } ); | |
| std::vector<const item *> filtered = p.crafting_inventory().items_with( []( const item & it ) { | |
| return it.has_flag( "SMOKABLE" ); | |
| } ); | |
| uilist smenu; | |
| smenu.text = _( "Load smoking rack with what kind of food?" ); | |
| // count and ask for item to be placed ... | |
| std::list<std::string> names; | |
| std::vector<const item *> entries; | |
| for( const item *smokable_item : filtered ) { | |
| int count; | |
| if( smokable_item->count_by_charges() ) { | |
| count = inv.charges_of( smokable_item->typeId() ); | |
| } else { | |
| count = inv.amount_of( smokable_item->typeId() ); | |
| } | |
| if( count != 0 ) { | |
| auto on_list = std::find( names.begin(), names.end(), item::nname( smokable_item->typeId(), 1 ) ); | |
| if( on_list == names.end() ) { | |
| smenu.addentry( item::nname( smokable_item->typeId(), 1 ) ); | |
| entries.push_back( smokable_item ); | |
| } | |
| names.push_back( item::nname( smokable_item->typeId(), 1 ) ); | |
| comps.push_back( item_comp( smokable_item->typeId(), count ) ); | |
| } | |
| } | |
| if( comps.empty() ) { | |
| p.add_msg_if_player( _( "You don't have any food that can be smoked." ) ); | |
| return; | |
| } | |
| smenu.query(); | |
| if( smenu.ret < 0 || static_cast<size_t>( smenu.ret ) >= entries.size() ) { | |
| add_msg( m_info, _( "Never mind." ) ); | |
| return; | |
| } | |
| int count = 0; | |
| auto what = entries[smenu.ret]; | |
| for( const auto &c : comps ) { | |
| if( c.type == what->typeId() ) { | |
| count = c.count; | |
| } | |
| } | |
| const int max_count_for_capacity = remaining_capacity / what->base_volume(); | |
| const int max_count = std::min( count, max_count_for_capacity ); | |
| // ... then ask how many to put it | |
| const std::string popupmsg = string_format( _( "Insert how many %s into the rack?" ), | |
| item::nname( what->typeId(), count ) ); | |
| int amount = string_input_popup() | |
| .title( popupmsg ) | |
| .width( 20 ) | |
| .text( to_string( max_count ) ) | |
| .only_digits( true ) | |
| .query_int(); | |
| if( amount == 0 ) { | |
| add_msg( m_info, _( "Never mind." ) ); | |
| return; | |
| } else if( amount > count ) { | |
| add_msg( m_info, _( "You don't have that many." ) ); | |
| return; | |
| } else if( amount > max_count_for_capacity ) { | |
| add_msg( m_info, _( "You can't place that many." ) ); | |
| return; | |
| } | |
| // reload comps with chosen items and quantity | |
| comps.clear(); | |
| comps.push_back( item_comp( what->typeId(), amount ) ); | |
| // select from where to get the items from and place them | |
| inv.form_from_map( g->u.pos(), PICKUP_RANGE ); | |
| inv.remove_items_with( []( const item & it ) { | |
| return it.rotten(); | |
| } ); | |
| comp_selection<item_comp> selected = p.select_item_component( comps, 1, inv, true, | |
| is_non_rotten_crafting_component ); | |
| std::list<item> moved = p.consume_items( selected, 1, is_non_rotten_crafting_component ); | |
| for( const item &m : moved ) { | |
| g->m.add_item( examp, m ); | |
| p.mod_moves( -p.item_handling_cost( m ) ); | |
| add_msg( m_info, _( "You carefully place %s %s in the rack." ), amount, m.nname( m.typeId(), | |
| amount ) ); | |
| } | |
| p.invalidate_crafting_inventory(); | |
| } | |
| static void mill_load_food( player &p, const tripoint &examp, | |
| const units::volume &remaining_capacity ) | |
| { | |
| std::vector<item_comp> comps; | |
| if( g->m.furn( examp ) == furn_str_id( "f_wind_mill_active" ) || | |
| g->m.furn( examp ) == furn_str_id( "f_water_mill_active" ) ) { | |
| p.add_msg_if_player( _( "You can't place more food while it's milling." ) ); | |
| return; | |
| } | |
| // filter millable food | |
| inventory inv = p.crafting_inventory(); | |
| inv.remove_items_with( []( const item & it ) { | |
| return it.rotten(); | |
| } ); | |
| std::vector<const item *> filtered = p.crafting_inventory().items_with( []( const item & it ) { | |
| return it.has_flag( "MILLABLE" ); | |
| } ); | |
| uilist smenu; | |
| smenu.text = _( "Load mill with what kind of product?" ); | |
| // count and ask for item to be placed ... | |
| std::list<std::string> names; | |
| std::vector<const item *> entries; | |
| for( const item *millable_item : filtered ) { | |
| int count; | |
| if( millable_item->count_by_charges() ) { | |
| count = inv.charges_of( millable_item->typeId() ); | |
| } else { | |
| count = inv.amount_of( millable_item->typeId() ); | |
| } | |
| if( count != 0 ) { | |
| auto on_list = std::find( names.begin(), names.end(), item::nname( millable_item->typeId(), 1 ) ); | |
| if( on_list == names.end() ) { | |
| smenu.addentry( item::nname( millable_item->typeId(), 1 ) ); | |
| entries.push_back( millable_item ); | |
| } | |
| names.push_back( item::nname( millable_item->typeId(), 1 ) ); | |
| comps.push_back( item_comp( millable_item->typeId(), count ) ); | |
| } | |
| } | |
| if( comps.empty() ) { | |
| p.add_msg_if_player( _( "You don't have any products that can be milled." ) ); | |
| return; | |
| } | |
| smenu.query(); | |
| if( smenu.ret < 0 || static_cast<size_t>( smenu.ret ) >= entries.size() ) { | |
| add_msg( m_info, _( "Never mind." ) ); | |
| return; | |
| } | |
| int count = 0; | |
| auto what = entries[smenu.ret]; | |
| for( const auto &c : comps ) { | |
| if( c.type == what->typeId() ) { | |
| count = c.count; | |
| } | |
| } | |
| const int max_count_for_capacity = remaining_capacity / what->base_volume(); | |
| const int max_count = std::min( count, max_count_for_capacity ); | |
| // ... then ask how many to put it | |
| const std::string popupmsg = string_format( _( "Insert how many %s into the mill?" ), | |
| item::nname( what->typeId(), count ) ); | |
| int amount = string_input_popup() | |
| .title( popupmsg ) | |
| .width( 20 ) | |
| .text( to_string( max_count ) ) | |
| .only_digits( true ) | |
| .query_int(); | |
| if( amount == 0 ) { | |
| add_msg( m_info, _( "Never mind." ) ); | |
| return; | |
| } else if( amount > count ) { | |
| add_msg( m_info, _( "You don't have that many." ) ); | |
| return; | |
| } else if( amount > max_count_for_capacity ) { | |
| add_msg( m_info, _( "You can't place that many." ) ); | |
| return; | |
| } | |
| // reload comps with chosen items and quantity | |
| comps.clear(); | |
| comps.push_back( item_comp( what->typeId(), amount ) ); | |
| // select from where to get the items from and place them | |
| inv.form_from_map( g->u.pos(), PICKUP_RANGE ); | |
| inv.remove_items_with( []( const item & it ) { | |
| return it.rotten(); | |
| } ); | |
| comp_selection<item_comp> selected = p.select_item_component( comps, 1, inv, true, | |
| is_non_rotten_crafting_component ); | |
| std::list<item> moved = p.consume_items( selected, 1, is_non_rotten_crafting_component ); | |
| for( const item &m : moved ) { | |
| g->m.add_item( examp, m ); | |
| p.mod_moves( -p.item_handling_cost( m ) ); | |
| add_msg( m_info, pgettext( "item amount and name", "You carefully place %s %s in the mill." ), | |
| amount, m.nname( m.typeId(), | |
| amount ) ); | |
| } | |
| p.invalidate_crafting_inventory(); | |
| } | |
| void iexamine::on_smoke_out( const tripoint &examp, const time_point &start_time ) | |
| { | |
| if( g->m.furn( examp ) == furn_str_id( "f_smoking_rack_active" ) || | |
| g->m.furn( examp ) == furn_str_id( "f_metal_smoking_rack_active" ) ) { | |
| smoker_finalize( g->u, examp, start_time ); | |
| } | |
| } | |
| void iexamine::quern_examine( player &p, const tripoint &examp ) | |
| { | |
| if( g->m.furn( examp ) == furn_str_id( "f_water_mill" ) ) { | |
| if( !g->m.is_water_shallow_current( examp ) ) { | |
| add_msg( _( "The water mill needs to be over shallow flowing water to work." ) ); | |
| return; | |
| } | |
| } | |
| if( g->m.furn( examp ) == furn_str_id( "f_wind_mill" ) ) { | |
| if( g->is_sheltered( examp ) ) { | |
| add_msg( _( "The wind mill needs to be outside in the wind to work." ) ); | |
| return; | |
| } | |
| } | |
| const bool active = g->m.furn( examp ) == furn_str_id( "f_water_mill_active" ) || | |
| g->m.furn( examp ) == furn_str_id( "f_wind_mill_active" ); | |
| map_stack items_here = g->m.i_at( examp ); | |
| if( items_here.empty() && active ) { | |
| debugmsg( "active mill was empty!" ); | |
| if( g->m.furn( examp ) == furn_str_id( "f_water_mill_active" ) ) { | |
| g->m.furn_set( examp, f_water_mill ); | |
| } else if( g->m.furn( examp ) == furn_str_id( "f_wind_mill_active" ) ) { | |
| g->m.furn_set( examp, f_wind_mill ); | |
| } | |
| return; | |
| } | |
| if( items_here.size() == 1 && items_here.begin()->typeId() == "fake_milling_item" ) { | |
| debugmsg( "f_mill_active was empty, and had fake_milling_item!" ); | |
| if( g->m.furn( examp ) == furn_str_id( "f_water_mill_active" ) ) { | |
| g->m.furn_set( examp, f_water_mill ); | |
| } else if( g->m.furn( examp ) == furn_str_id( "f_wind_mill_active" ) ) { | |
| g->m.furn_set( examp, f_wind_mill ); | |
| } | |
| items_here.erase( items_here.begin() ); | |
| return; | |
| } | |
| std::stringstream pop; | |
| time_duration time_left = 0_turns; | |
| int hours_left = 0; | |
| int minutes_left = 0; | |
| units::volume f_volume = 0_ml; | |
| bool f_check = false; | |
| for( const item &it : items_here ) { | |
| if( it.is_food() ) { | |
| f_check = true; | |
| f_volume += it.volume(); | |
| } | |
| if( active && it.typeId() == "fake_milling_item" ) { | |
| time_left = time_duration::from_turns( it.item_counter ); | |
| hours_left = to_hours<int>( time_left ); | |
| minutes_left = to_minutes<int>( time_left ) + 1; | |
| } | |
| } | |
| const bool empty = f_volume == 0_ml; | |
| const bool full = f_volume >= sm_rack::MAX_FOOD_VOLUME; | |
| const auto remaining_capacity = sm_rack::MAX_FOOD_VOLUME - f_volume; | |
| uilist smenu; | |
| smenu.text = _( "What to do with the mill?" ); | |
| smenu.desc_enabled = true; | |
| smenu.addentry( 0, true, 'i', _( "Inspect mill" ) ); | |
| if( !active ) { | |
| smenu.addentry_desc( 1, !empty, 'r', | |
| empty ? _( "Remove brake and start milling... insert some products for milling first" ) : | |
| _( "Remove brake and start milling" ), | |
| _( "Remove brake and start milling, milling will take about 6 hours." ) ); | |
| smenu.addentry_desc( 2, !full, 'p', | |
| full ? _( "Insert products for milling... mill is full" ) : | |
| string_format( _( "Insert products for milling... remaining capacity is %s %s" ), | |
| format_volume( remaining_capacity ), volume_units_abbr() ), | |
| _( "Fill the mill with starchy products such as wheat, barley or oats." ) ); | |
| if( f_check ) { | |
| smenu.addentry( 3, f_check, 'e', _( "Remove products from mill" ) ); | |
| } | |
| } else { | |
| smenu.addentry_desc( 4, true, 'x', | |
| _( "Apply brake to mill" ), | |
| _( "Applying the brake will stop milling process." ) ); | |
| } | |
| smenu.query(); | |
| switch( smenu.ret ) { | |
| case 0: { //inspect mill | |
| if( active ) { | |
| pop << "<color_green>" << _( "There's a mill here. It is turning and milling." ) << "</color>" | |
| << "\n"; | |
| if( time_left > 0_turns ) { | |
| if( minutes_left > 60 ) { | |
| pop << string_format( ngettext( "It will finish milling in about %d hour.", | |
| "It will finish milling in about %d hours.", | |
| hours_left ), hours_left ) << "\n \n "; | |
| } else if( minutes_left > 30 ) { | |
| pop << _( "It will finish milling in less than an hour." ); | |
| } else { | |
| pop << string_format( _( "It should take about %d minutes to finish milling." ), minutes_left ); | |
| } | |
| } | |
| } else { | |
| pop << "<color_green>" << _( "There's a mill here." ) << "</color>" << "\n"; | |
| } | |
| pop << "<color_green>" << _( "You inspect its contents and find: " ) << "</color>" << "\n \n "; | |
| if( items_here.empty() ) { | |
| pop << _( "... that it is empty." ); | |
| } else { | |
| for( const item &it : items_here ) { | |
| if( it.typeId() == "fake_milling_item" ) { | |
| pop << "\n " << "<color_red>" << _( "You see some grains that are not yet milled to fine flour." ) | |
| << "</color>" << | |
| "\n "; | |
| continue; | |
| } | |
| pop << "-> " << it.nname( it.typeId(), it.charges ); | |
| pop << " (" << std::to_string( it.charges ) << ") \n "; | |
| } | |
| } | |
| popup( pop.str(), PF_NONE ); | |
| break; | |
| } | |
| case 1: //activate | |
| if( active ) { | |
| add_msg( _( "It is already milling." ) ); | |
| } else { | |
| mill_activate( p, examp ); | |
| } | |
| break; | |
| case 2: // load food | |
| mill_load_food( p, examp, remaining_capacity ); | |
| break; | |
| case 3: // remove food | |
| for( map_stack::iterator it = items_here.begin(); it != items_here.end(); ) { | |
| if( it->is_food() ) { | |
| // get handling cost before the item reference is invalidated | |
| const int handling_cost = -p.item_handling_cost( *it ); | |
| add_msg( _( "You remove %s from the mill." ), it->tname() ); | |
| g->m.add_item_or_charges( p.pos(), *it ); | |
| it = items_here.erase( it ); | |
| p.mod_moves( handling_cost ); | |
| } else { | |
| ++it; | |
| } | |
| } | |
| if( active ) { | |
| if( g->m.furn( examp ) == furn_str_id( "f_water_mill_active" ) ) { | |
| g->m.furn_set( examp, f_water_mill ); | |
| } else if( g->m.furn( examp ) == furn_str_id( "f_wind_mill_active" ) ) { | |
| g->m.furn_set( examp, f_wind_mill ); | |
| } | |
| add_msg( m_info, _( "You stop the milling process." ) ); | |
| } | |
| break; | |
| default: | |
| add_msg( m_info, _( "Never mind." ) ); | |
| break; | |
| case 4: | |
| if( g->m.furn( examp ) == furn_str_id( "f_water_mill_active" ) ) { | |
| g->m.furn_set( examp, f_water_mill ); | |
| } else if( g->m.furn( examp ) == furn_str_id( "f_wind_mill_active" ) ) { | |
| g->m.furn_set( examp, f_wind_mill ); | |
| } | |
| add_msg( m_info, _( "You stop the milling process." ) ); | |
| break; | |
| } | |
| } | |
| void iexamine::smoker_options( player &p, const tripoint &examp ) | |
| { | |
| const bool active = g->m.furn( examp ) == furn_str_id( "f_smoking_rack_active" ) || | |
| g->m.furn( examp ) == furn_str_id( "f_metal_smoking_rack_active" ); | |
| const bool portable = g->m.furn( examp ) == furn_str_id( "f_metal_smoking_rack" ) || | |
| g->m.furn( examp ) == furn_str_id( "f_metal_smoking_rack_active" ); | |
| map_stack items_here = g->m.i_at( examp ); | |
| if( portable && items_here.empty() && active ) { | |
| debugmsg( "f_metal_smoking_rack_active was empty!" ); | |
| g->m.furn_set( examp, f_metal_smoking_rack ); | |
| return; | |
| } else if( items_here.empty() && active ) { | |
| debugmsg( "f_smoking_rack_active was empty!" ); | |
| g->m.furn_set( examp, f_smoking_rack ); | |
| return; | |
| } | |
| if( portable && items_here.size() == 1 && items_here.begin()->typeId() == "fake_smoke_plume" ) { | |
| debugmsg( "f_metal_smoking_rack_active was empty, and had fake_smoke_plume!" ); | |
| g->m.furn_set( examp, f_metal_smoking_rack ); | |
| items_here.erase( items_here.begin() ); | |
| return; | |
| } else if( items_here.size() == 1 && items_here.begin()->typeId() == "fake_smoke_plume" ) { | |
| debugmsg( "f_smoking_rack_active was empty, and had fake_smoke_plume!" ); | |
| g->m.furn_set( examp, f_smoking_rack ); | |
| items_here.erase( items_here.begin() ); | |
| return; | |
| } | |
| bool rem_f_opt = false; | |
| std::stringstream pop; | |
| time_duration time_left = 0_turns; | |
| int hours_left = 0; | |
| int minutes_left = 0; | |
| units::volume f_volume = 0_ml; | |
| bool f_check = false; | |
| for( const item &it : items_here ) { | |
| if( it.is_food() ) { | |
| f_check = true; | |
| f_volume += it.volume(); | |
| } | |
| if( active && it.typeId() == "fake_smoke_plume" ) { | |
| time_left = time_duration::from_turns( it.item_counter ); | |
| hours_left = to_hours<int>( time_left ); | |
| minutes_left = to_minutes<int>( time_left ) + 1; | |
| } | |
| } | |
| const bool empty = f_volume == 0_ml; | |
| const bool full = f_volume >= sm_rack::MAX_FOOD_VOLUME; | |
| const bool full_portable = f_volume >= sm_rack::MAX_FOOD_VOLUME_PORTABLE; | |
| const auto remaining_capacity = sm_rack::MAX_FOOD_VOLUME - f_volume; | |
| const auto remaining_capacity_portable = sm_rack::MAX_FOOD_VOLUME_PORTABLE - f_volume; | |
| const auto has_coal_in_inventory = p.charges_of( "charcoal" ) > 0; | |
| const auto coal_charges = count_charges_in_list( item::find_type( "charcoal" ), items_here ); | |
| const auto need_charges = get_charcoal_charges( f_volume ); | |
| const bool has_coal = coal_charges > 0; | |
| const bool has_enough_coal = coal_charges >= need_charges; | |
| uilist smenu; | |
| smenu.text = _( "What to do with the smoking rack:" ); | |
| smenu.desc_enabled = true; | |
| smenu.addentry( 0, true, 'i', _( "Inspect smoking rack" ) ); | |
| if( !active ) { | |
| smenu.addentry_desc( 1, !empty && has_enough_coal, 'l', | |
| empty ? _( "Light up and smoke food... insert some food for smoking first" ) : | |
| !has_enough_coal ? string_format( | |
| _( "Light up and smoke food... need extra %d charges of charcoal" ), | |
| need_charges - coal_charges ) : | |
| _( "Light up and smoke food" ), | |
| _( "Light up the smoking rack and start smoking. Smoking will take about 6 hours." ) ); | |
| if( portable ) { | |
| smenu.addentry_desc( 2, !full_portable, 'f', | |
| full_portable ? _( "Insert food for smoking... smoking rack is full" ) : | |
| string_format( _( "Insert food for smoking... remaining capacity is %s %s" ), | |
| format_volume( remaining_capacity_portable ), volume_units_abbr() ), | |
| _( "Fill the smoking rack with raw meat, fish or sausages for smoking or fruit or vegetable or smoked meat for drying." ) ); | |
| smenu.addentry_desc( 8, !active, 'z', | |
| active ? _( "You cannot disassemble this smoking rack while it is active!" ) : | |
| _( "Disassemble the smoking rack" ), "" ); | |
| } else { | |
| smenu.addentry_desc( 2, !full, 'f', | |
| full ? _( "Insert food for smoking... smoking rack is full" ) : | |
| string_format( _( "Insert food for smoking... remaining capacity is %s %s" ), | |
| format_volume( remaining_capacity ), volume_units_abbr() ), | |
| _( "Fill the smoking rack with raw meat, fish or sausages for smoking or fruit or vegetable or smoked meat for drying." ) ); | |
| } | |
| if( f_check ) { | |
| smenu.addentry( 4, f_check, 'e', _( "Remove food from smoking rack" ) ); | |
| } | |
| smenu.addentry_desc( 3, has_coal_in_inventory, 'r', | |
| !has_coal_in_inventory ? _( "Reload with charcoal... you don't have any" ) : | |
| _( "Reload with charcoal" ), | |
| string_format( | |
| _( "You need %d charges of charcoal for %s %s of food. Minimal amount of charcoal is %d charges." ), | |
| sm_rack::CHARCOAL_PER_LITER, format_volume( 1000_ml ), volume_units_long(), | |
| sm_rack::MIN_CHARCOAL ) ); | |
| } else { | |
| smenu.addentry_desc( 7, true, 'x', | |
| _( "Quench burning charcoal" ), | |
| _( "Quenching will stop smoking process, but also destroy all used charcoal." ) ); | |
| } | |
| if( has_coal ) { | |
| smenu.addentry( 5, true, 'c', | |
| active ? string_format( _( "Rake out %d excess charges of charcoal from smoking rack" ), | |
| coal_charges ) : | |
| string_format( _( "Remove %d charges of charcoal from smoking rack" ), coal_charges ) ); | |
| } | |
| smenu.query(); | |
| switch( smenu.ret ) { | |
| case 0: { //inspect smoking rack | |
| if( active ) { | |
| pop << "<color_green>" << _( "There's a smoking rack here. It is lit and smoking." ) << "</color>" | |
| << "\n"; | |
| if( time_left > 0_turns ) { | |
| if( minutes_left > 60 ) { | |
| pop << string_format( ngettext( "It will finish smoking in about %d hour.", | |
| "It will finish smoking in about %d hours.", | |
| hours_left ), hours_left ) << "\n \n "; | |
| } else if( minutes_left > 30 ) { | |
| pop << _( "It will finish smoking in less than an hour." ) << "\n "; | |
| } else { | |
| pop << string_format( _( "It should take about %d minutes to finish smoking." ), | |
| minutes_left ) << "\n "; | |
| } | |
| } | |
| } else { | |
| pop << "<color_green>" << _( "There's a smoking rack here." ) << "</color>" << "\n"; | |
| } | |
| pop << "<color_green>" << _( "You inspect its contents and find: " ) << "</color>" << "\n \n "; | |
| if( items_here.empty() ) { | |
| pop << _( "... that it is empty." ); | |
| } else { | |
| for( const item &it : items_here ) { | |
| if( it.typeId() == "fake_smoke_plume" ) { | |
| pop << "\n " << "<color_red>" << _( "You see some smoldering embers there." ) << "</color>" << | |
| "\n "; | |
| continue; | |
| } | |
| pop << "-> " << it.nname( it.typeId(), it.charges ); | |
| pop << " (" << std::to_string( it.charges ) << ") \n "; | |
| } | |
| } | |
| popup( pop.str(), PF_NONE ); | |
| break; | |
| } | |
| case 1: //activate | |
| if( active ) { | |
| add_msg( _( "It is already lit and smoking." ) ); | |
| } else { | |
| smoker_activate( p, examp ); | |
| } | |
| break; | |
| case 2: // load food | |
| if( portable ) { | |
| smoker_load_food( p, examp, remaining_capacity_portable ); | |
| } else { | |
| smoker_load_food( p, examp, remaining_capacity ); | |
| } | |
| break; | |
| case 3: // load charcoal | |
| reload_furniture( p, examp ); | |
| break; | |
| case 4: // remove food | |
| rem_f_opt = true; | |
| /* fallthrough */ | |
| case 5: { //remove charcoal | |
| for( map_stack::iterator it = items_here.begin(); it != items_here.end(); ) { | |
| if( ( rem_f_opt && it->is_food() ) || ( !rem_f_opt && ( it->typeId() == "charcoal" ) ) ) { | |
| // get handling cost before the item reference is invalidated | |
| const int handling_cost = -p.item_handling_cost( *it ); | |
| add_msg( _( "You remove %s from the rack." ), it->tname() ); | |
| g->m.add_item_or_charges( p.pos(), *it ); | |
| it = items_here.erase( it ); | |
| p.mod_moves( handling_cost ); | |
| } else { | |
| ++it; | |
| } | |
| } | |
| if( portable && active && rem_f_opt ) { | |
| g->m.furn_set( examp, f_metal_smoking_rack ); | |
| add_msg( m_info, _( "You stop the smoking process." ) ); | |
| } else if( active && rem_f_opt ) { | |
| g->m.furn_set( examp, f_smoking_rack ); | |
| add_msg( m_info, _( "You stop the smoking process." ) ); | |
| } | |
| } | |
| break; | |
| default: | |
| add_msg( m_info, _( "Never mind." ) ); | |
| break; | |
| case 7: | |
| if( portable ) { | |
| g->m.furn_set( examp, f_metal_smoking_rack ); | |
| add_msg( m_info, _( "You stop the smoking process." ) ); | |
| } else { | |
| g->m.furn_set( examp, f_smoking_rack ); | |
| add_msg( m_info, _( "You stop the smoking process." ) ); | |
| } | |
| break; | |
| case 8: | |
| g->m.furn_set( examp, f_metal_smoking_rack ); | |
| deployed_furniture( p, examp ); | |
| break; | |
| } | |
| } | |
| void iexamine::open_safe( player &, const tripoint &examp ) | |
| { | |
| add_msg( m_info, _( "You open the unlocked safe. " ) ); | |
| g->m.furn_set( examp, f_safe_o ); | |
| } | |
| void iexamine::workbench( player &p, const tripoint &examp ) | |
| { | |
| workbench_internal( p, examp, cata::nullopt ); | |
| } | |
| void iexamine::workbench_internal( player &p, const tripoint &examp, | |
| const cata::optional<vpart_reference> &part ) | |
| { | |
| std::vector<item_location> crafts; | |
| std::string name; | |
| bool items_at_loc = false; | |
| if( part ) { | |
| name = part->part().name(); | |
| auto items_at_part = part->vehicle().get_items( part->part_index() ); | |
| for( item &it : items_at_part ) { | |
| if( it.is_craft() ) { | |
| crafts.emplace_back( item_location( vehicle_cursor( part->vehicle(), part->part_index() ), &it ) ); | |
| } | |
| } | |
| } else { | |
| name = g->m.furn( examp ).obj().name(); | |
| auto items_at_furn = g->m.i_at( examp ); | |
| items_at_loc = !items_at_furn.empty(); | |
| for( item &it : items_at_furn ) { | |
| if( it.is_craft() ) { | |
| crafts.emplace_back( item_location( map_cursor( examp ), &it ) ); | |
| } | |
| } | |
| } | |
| uilist amenu; | |
| enum option : int { | |
| start_craft = 0, | |
| repeat_craft, | |
| start_long_craft, | |
| work_on_craft, | |
| get_items | |
| }; | |
| amenu.text = string_format( pgettext( "furniture", "What to do at the %s?" ), name ); | |
| amenu.addentry( start_craft, true, '1', _( "Craft items" ) ); | |
| amenu.addentry( repeat_craft, true, '2', _( "Recraft last recipe" ) ); | |
| amenu.addentry( start_long_craft, true, '3', _( "Craft as long as possible" ) ); | |
| amenu.addentry( work_on_craft, !crafts.empty(), '4', _( "Work on craft" ) ); | |
| if( !part ) { | |
| amenu.addentry( get_items, items_at_loc, '5', _( "Get items" ) ); | |
| } | |
| amenu.query(); | |
| const option choice = static_cast<option>( amenu.ret ); | |
| switch( choice ) { | |
| case start_craft: { | |
| if( p.has_active_mutation( trait_SHELL2 ) ) { | |
| p.add_msg_if_player( m_info, _( "You can't craft while you're in your shell." ) ); | |
| } else { | |
| p.craft( examp ); | |
| } | |
| break; | |
| } | |
| case repeat_craft: { | |
| if( p.has_active_mutation( trait_SHELL2 ) ) { | |
| p.add_msg_if_player( m_info, _( "You can't craft while you're in your shell." ) ); | |
| } else { | |
| p.recraft( examp ); | |
| } | |
| break; | |
| } | |
| case start_long_craft: { | |
| if( p.has_active_mutation( trait_SHELL2 ) ) { | |
| p.add_msg_if_player( m_info, _( "You can't craft while you're in your shell." ) ); | |
| } else { | |
| p.long_craft( examp ); | |
| } | |
| break; | |
| } | |
| case work_on_craft: { | |
| std::vector<std::string> item_names; | |
| for( item_location &it : crafts ) { | |
| if( it ) { | |
| item_names.emplace_back( it.get_item()->tname() ); | |
| } | |
| } | |
| uilist amenu2( _( "Which craft to work on?" ), item_names ); | |
| if( amenu2.ret == UILIST_CANCEL ) { | |
| break; | |
| } | |
| item *selected_craft = crafts[amenu2.ret].get_item(); | |
| if( !p.can_continue_craft( *selected_craft ) ) { | |
| break; | |
| } | |
| const recipe &rec = selected_craft->get_making(); | |
| if( p.has_recipe( &rec, p.crafting_inventory(), p.get_crafting_helpers() ) == -1 ) { | |
| p.add_msg_player_or_npc( | |
| string_format( _( "You don't know the recipe for the %s and can't continue crafting." ), | |
| rec.result_name() ), | |
| string_format( _( "<npcname> doesn't know the recipe for the %s and can't continue crafting." ), | |
| rec.result_name() ) | |
| ); | |
| break; | |
| } | |
| p.add_msg_player_or_npc( | |
| string_format( pgettext( "in progress craft", "You start working on the %s." ), | |
| selected_craft->tname() ), | |
| string_format( pgettext( "in progress craft", "<npcname> starts working on the %s." ), | |
| selected_craft->tname() ) ); | |
| p.assign_activity( activity_id( "ACT_CRAFT" ) ); | |
| p.activity.targets.push_back( crafts[amenu2.ret] ); | |
| p.activity.values.push_back( 0 ); // Not a long craft | |
| break; | |
| } | |
| case get_items: { | |
| Pickup::pick_up( examp, 0 ); | |
| break; | |
| } | |
| } | |
| } | |
| /** | |
| * Given then name of one of the above functions, returns the matching function | |
| * pointer. If no match is found, defaults to iexamine::none but prints out a | |
| * debug message as a warning. | |
| * @param function_name The name of the function to get. | |
| * @return A function pointer to the specified function. | |
| */ | |
| iexamine_function iexamine_function_from_string( const std::string &function_name ) | |
| { | |
| static const std::map<std::string, iexamine_function> function_map = {{ | |
| { "none", &iexamine::none }, | |
| { "deployed_furniture", &iexamine::deployed_furniture }, | |
| { "cvdmachine", &iexamine::cvdmachine }, | |
| { "nanofab", &iexamine::nanofab }, | |
| { "gaspump", &iexamine::gaspump }, | |
| { "atm", &iexamine::atm }, | |
| { "vending", &iexamine::vending }, | |
| { "toilet", &iexamine::toilet }, | |
| { "elevator", &iexamine::elevator }, | |
| { "controls_gate", &iexamine::controls_gate }, | |
| { "cardreader", &iexamine::cardreader }, | |
| { "cardreader_robofac", &iexamine::cardreader_robofac }, | |
| { "cardreader_fp", &iexamine::cardreader_foodplace }, | |
| { "intercom", &iexamine::intercom }, | |
| { "rubble", &iexamine::rubble }, | |
| { "chainfence", &iexamine::chainfence }, | |
| { "bars", &iexamine::bars }, | |
| { "portable_structure", &iexamine::portable_structure }, | |
| { "pit", &iexamine::pit }, | |
| { "pit_covered", &iexamine::pit_covered }, | |
| { "slot_machine", &iexamine::slot_machine }, | |
| { "safe", &iexamine::safe }, | |
| { "bulletin_board", &iexamine::bulletin_board }, | |
| { "fault", &iexamine::fault }, | |
| { "pedestal_wyrm", &iexamine::pedestal_wyrm }, | |
| { "pedestal_temple", &iexamine::pedestal_temple }, | |
| { "door_peephole", &iexamine::door_peephole }, | |
| { "fswitch", &iexamine::fswitch }, | |
| { "flower_poppy", &iexamine::flower_poppy }, | |
| { "flower_cactus", &iexamine::flower_cactus }, | |
| { "fungus", &iexamine::fungus }, | |
| { "flower_dahlia", &iexamine::flower_dahlia }, | |
| { "flower_marloss", &iexamine::flower_marloss }, | |
| { "egg_sackbw", &iexamine::egg_sackbw }, | |
| { "egg_sackcs", &iexamine::egg_sackcs }, | |
| { "egg_sackws", &iexamine::egg_sackws }, | |
| { "dirtmound", &iexamine::dirtmound }, | |
| { "aggie_plant", &iexamine::aggie_plant }, | |
| { "fvat_empty", &iexamine::fvat_empty }, | |
| { "fvat_full", &iexamine::fvat_full }, | |
| { "keg", &iexamine::keg }, | |
| { "harvest_furn_nectar", &iexamine::harvest_furn_nectar }, | |
| { "harvest_furn", &iexamine::harvest_furn }, | |
| { "harvest_ter_nectar", &iexamine::harvest_ter_nectar }, | |
| { "harvest_ter", &iexamine::harvest_ter }, | |
| { "harvested_plant", &iexamine::harvested_plant }, | |
| { "shrub_marloss", &iexamine::shrub_marloss }, | |
| { "translocator", &iexamine::translocator }, | |
| { "tree_marloss", &iexamine::tree_marloss }, | |
| { "tree_hickory", &iexamine::tree_hickory }, | |
| { "tree_maple", &iexamine::tree_maple }, | |
| { "tree_maple_tapped", &iexamine::tree_maple_tapped }, | |
| { "shrub_wildveggies", &iexamine::shrub_wildveggies }, | |
| { "recycle_compactor", &iexamine::recycle_compactor }, | |
| { "trap", &iexamine::trap }, | |
| { "water_source", &iexamine::water_source }, | |
| { "clean_water_source", &iexamine::clean_water_source }, | |
| { "reload_furniture", &iexamine::reload_furniture }, | |
| { "curtains", &iexamine::curtains }, | |
| { "sign", &iexamine::sign }, | |
| { "pay_gas", &iexamine::pay_gas }, | |
| { "gunsafe_ml", &iexamine::gunsafe_ml }, | |
| { "gunsafe_el", &iexamine::gunsafe_el }, | |
| { "locked_object", &iexamine::locked_object }, | |
| { "kiln_empty", &iexamine::kiln_empty }, | |
| { "kiln_full", &iexamine::kiln_full }, | |
| { "fireplace", &iexamine::fireplace }, | |
| { "ledge", &iexamine::ledge }, | |
| { "autodoc", &iexamine::autodoc }, | |
| { "quern_examine", &iexamine::quern_examine }, | |
| { "smoker_options", &iexamine::smoker_options }, | |
| { "open_safe", &iexamine::open_safe }, | |
| { "workbench", &iexamine::workbench } | |
| } | |
| }; | |
| auto iter = function_map.find( function_name ); | |
| if( iter != function_map.end() ) { | |
| return iter->second; | |
| } | |
| //No match found | |
| debugmsg( "Could not find an iexamine function matching '%s'!", function_name ); | |
| return &iexamine::none; | |
| } | |
| hack_result iexamine::hack_attempt( player &p ) | |
| { | |
| if( p.has_trait( trait_ILLITERATE ) ) { | |
| return HACK_UNABLE; | |
| } | |
| bool using_electrohack = p.has_charges( "electrohack", 25 ) && | |
| query_yn( _( "Use electrohack?" ) ); | |
| bool using_fingerhack = !using_electrohack && p.has_bionic( bionic_id( "bio_fingerhack" ) ) && | |
| p.power_level > 24 && query_yn( _( "Use fingerhack?" ) ); | |
| if( !( using_electrohack || using_fingerhack ) ) { | |
| return HACK_UNABLE; | |
| } | |
| p.moves -= to_moves<int>( 5_minutes ); | |
| p.practice( skill_computer, 20 ); | |
| ///\EFFECT_COMPUTER increases success chance of hacking card readers | |
| int player_computer_skill_level = p.get_skill_level( skill_computer ); | |
| int success = rng( player_computer_skill_level / 4 - 2, player_computer_skill_level * 2 ); | |
| success += rng( -3, 3 ); | |
| if( using_fingerhack ) { | |
| p.charge_power( -25 ); | |
| success++; | |
| } | |
| if( using_electrohack ) { | |
| p.use_charges( "electrohack", 25 ); | |
| success++; | |
| } | |
| // odds go up with int>8, down with int<8 | |
| // 4 int stat is worth 1 computer skill here | |
| ///\EFFECT_INT increases success chance of hacking card readers | |
| success += rng( 0, static_cast<int>( ( p.int_cur - 8 ) / 2 ) ); | |
| if( success < 0 ) { | |
| add_msg( _( "You cause a short circuit!" ) ); | |
| if( success <= -5 ) { | |
| if( using_electrohack ) { | |
| add_msg( m_bad, _( "Your electrohack is ruined!" ) ); | |
| p.use_amount( "electrohack", 1 ); | |
| } else { | |
| add_msg( m_bad, _( "Your power is drained!" ) ); | |
| p.charge_power( -rng( 0, p.power_level ) ); | |
| } | |
| } | |
| return HACK_FAIL; | |
| } else if( success < 6 ) { | |
| return HACK_NOTHING; | |
| } else { | |
| return HACK_SUCCESS; | |
| } | |
| } |