inline Activity *activity() { ActivityID *aid = activity_id(); UniqueComponentActivityID ucaid = randr(); Timestamp time_start = randr(); Timestamp time_stop = time_start + randr(); RemoteCallIdentifier *invoker = remote_call_id(); ActivityError error_value = randr(); vector<ActivityID> parents; int num_parents = randr(0, 20); for (int i = 0; i < num_parents; i++) parents.push_back(*activity_id()); vector<Attribute> attributes; int num_attrs = randr(0, 20); for (int i = 0; i < num_attrs; i++) attributes.push_back(*attribute()); vector<RemoteCall> remote_calls; int num_rcs = randr(0, 20); for (int i = 0; i < num_rcs; i++) remote_calls.push_back(*remote_call()); Activity *act = new Activity(ucaid, time_start, time_stop, *aid, parents, attributes, remote_calls, invoker, error_value); return act; }
void player::disassemble_all( bool one_pass ) { // Reset all the activity values assign_activity( activity_id( "ACT_DISASSEMBLE" ), 0 ); auto items = g->m.i_at( pos() ); bool found_any = false; if( !one_pass ) { // Kinda hacky // That INT_MIN notes we want infinite uncraft // If INT_MIN is reached in complete_disassemble, // we will call this function again. activity.values.push_back( INT_MIN ); activity.str_values.push_back( "" ); activity.coords.push_back( tripoint_min ); } for( size_t i = 0; i < items.size(); i++ ) { if( disassemble( items[i], i, true, false ) ) { found_any = true; } } if( !found_any ) { // Reset the activity - don't loop if there is nothing to do activity = player_activity(); } }
void talk_function::buy_haircut( npc &p ) { g->u.add_morale( MORALE_HAIRCUT, 5, 5, 720_minutes, 3_minutes ); g->u.assign_activity( activity_id( "ACT_WAIT_NPC" ), 300 ); g->u.activity.str_values.push_back( p.name ); add_msg( m_good, _( "%s gives you a decent haircut..." ), p.name ); }
void place_construction( const std::string &desc ) { g->refresh_all(); const inventory &total_inv = g->u.crafting_inventory(); std::vector<construction *> cons = constructions_by_desc( desc ); std::map<tripoint, const construction *> valid; for( const tripoint &p : g->m.points_in_radius( g->u.pos(), 1 ) ) { for( const auto *con : cons ) { if( p != g->u.pos() && can_construct( *con, p ) && player_can_build( g->u, total_inv, *con ) ) { valid[ p ] = con; } } } for( auto &elem : valid ) { g->m.drawsq( g->w_terrain, g->u, elem.first, true, false, g->u.pos() + g->u.view_offset ); } wrefresh( g->w_terrain ); tripoint dirp; if( !choose_adjacent( _( "Construct where?" ), dirp ) ) { return; } if( valid.find( dirp ) == valid.end() ) { add_msg( m_info, _( "You cannot build there!" ) ); return; } const construction &con = *valid.find( dirp )->second; g->u.assign_activity( activity_id( "ACT_BUILD" ), con.adjusted_time(), con.id ); g->u.activity.placement = dirp; }
void talk_function::start_training( npc &p ) { int cost; time_duration time = 0_turns; std::string name; const skill_id &skill = p.chatbin.skill; const matype_id &style = p.chatbin.style; if( skill.is_valid() && g->u.get_skill_level( skill ) < p.get_skill_level( skill ) ) { cost = calc_skill_training_cost( p, skill ); time = calc_skill_training_time( p, skill ); name = skill.str(); } else if( p.chatbin.style.is_valid() && !g->u.has_martialart( style ) ) { cost = calc_ma_style_training_cost( p, style ); time = calc_ma_style_training_time( p, style ); name = p.chatbin.style.str(); } else { debugmsg( "start_training with no valid skill or style set" ); return; } mission *miss = p.chatbin.mission_selected; if( miss != nullptr && miss->get_assigned_player_id() == g->u.getID() ) { clear_mission( p ); } else if( !pay_npc( p, cost ) ) { return; } g->u.assign_activity( activity_id( "ACT_TRAIN" ), to_moves<int>( time ), p.getID(), 0, name ); p.add_effect( effect_asked_to_train, 6_hours ); }
void talk_function::morale_chat_activity( npc &p ) { g->u.assign_activity( activity_id( "ACT_SOCIALIZE" ), 10000 ); g->u.activity.str_values.push_back( p.name ); add_msg( m_good, _( "That was a pleasant conversation with %s." ), p.disp_name() ); g->u.add_morale( MORALE_CHAT, rng( 3, 10 ), 10, 200_minutes, 5_minutes / 2 ); }
void talk_function::buy_shave( npc &p ) { g->u.add_morale( MORALE_SHAVE, 10, 10, 360_minutes, 3_minutes ); g->u.assign_activity( activity_id( "ACT_WAIT_NPC" ), 100 ); g->u.activity.str_values.push_back( p.name ); add_msg( m_good, _( "%s gives you a decent shave..." ), p.name ); }
void pick_up_from_feet( player &p, int pos ) { auto size_before = g->m.i_at( p.pos() ).size(); REQUIRE( size_before > pos ); p.moves = 100; p.assign_activity( activity_id( "ACT_PICKUP" ) ); p.activity.placement = tripoint(0, 0, 0); p.activity.values.push_back( false ); // not from vehicle p.activity.values.push_back( pos ); // index of item to pick up p.activity.values.push_back( 0 ); p.activity.do_turn( p ); REQUIRE( g->m.i_at( p.pos() ).size() == size_before - 1 ); }
bool player_activity::can_resume_with( const player_activity &other, const Character & ) const { // Should be used for relative positions // And to forbid resuming now-invalid crafting // @todo: Once activity_handler_actors exist, the less ugly method of using a // pure virtual can_resume_with should be used if( !*this || !other || type->no_resume() ) { return false; } if( id() == activity_id( "ACT_CRAFT" ) || id() == activity_id( "ACT_LONGCRAFT" ) ) { if( !containers_equal( values, other.values ) || !containers_equal( coords, other.coords ) ) { return false; } } else if( id() == activity_id( "ACT_CLEAR_RUBBLE" ) ) { if( other.coords.empty() || other.coords[0] != coords[0] ) { return false; } } else if( id() == activity_id( "ACT_READ" ) ) { // Return false if any NPCs joined or left the study session // the vector {1, 2} != {2, 1}, so we'll have to check manually if( values.size() != other.values.size() ) { return false; } for( int foo : other.values ) { if( std::find( values.begin(), values.end(), foo ) == values.end() ) { return false; } } if( targets.empty() || other.targets.empty() || targets[0] != other.targets[0] ) { return false; } } return !auto_resume && id() == other.id() && index == other.index && position == other.position && name == other.name && targets == other.targets; }
bool player::disassemble( item &obj, int pos, bool ground, bool interactive ) { // check sufficient tools for disassembly std::string err; if( !can_disassemble( obj, crafting_inventory(), &err ) ) { if( interactive ) { add_msg_if_player( m_info, "%s", err.c_str() ); } return false; } const auto &r = recipe_dictionary::get_uncraft( obj.typeId() ); // last chance to back out if( interactive && get_option<bool>( "QUERY_DISASSEMBLE" ) ) { const auto components( r.disassembly_requirements().get_components() ); std::ostringstream list; for( const auto &elem : components ) { list << "- " << elem.front().to_string() << std::endl; } if( !query_yn( _( "Disassembling the %s may yield:\n%s\nReally disassemble?" ), obj.tname().c_str(), list.str().c_str() ) ) { return false; } } if( activity.id() != activity_id( "ACT_DISASSEMBLE" ) ) { assign_activity( activity_id( "ACT_DISASSEMBLE" ), r.time ); } else if( activity.moves_left <= 0 ) { activity.moves_left = r.time; } activity.values.push_back( pos ); activity.coords.push_back( ground ? this->pos() : tripoint_min ); activity.str_values.push_back( r.result ); return true; }
void activity_on_turn_pickup() { // Pickup activity has source square, bool indicating source type, // indices of items on map, and quantities of same. bool from_vehicle = g->u.activity.values.front(); tripoint pickup_target = g->u.activity.placement; tripoint true_target = g->u.pos(); true_target += pickup_target; // Auto_resume implies autopickup. bool autopickup = g->u.activity.auto_resume; std::list<int> indices; std::list<int> quantities; auto map_stack = g->m.i_at( true_target ); if( !from_vehicle && map_stack.empty() ) { g->u.cancel_activity(); return; } // Note i = 1, skipping first element. for( size_t i = 1; i < g->u.activity.values.size(); i += 2 ) { indices.push_back( g->u.activity.values[i] ); quantities.push_back( g->u.activity.values[ i + 1 ] ); } g->u.cancel_activity(); bool keep_going = Pickup::do_pickup( pickup_target, from_vehicle, indices, quantities, autopickup ); // If there are items left, we ran out of moves, so make a new activity with the remainder. if( keep_going && !indices.empty() ) { g->u.assign_activity( activity_id( "ACT_PICKUP" ) ); g->u.activity.placement = pickup_target; g->u.activity.auto_resume = autopickup; g->u.activity.values.push_back( from_vehicle ); while( !indices.empty() ) { g->u.activity.values.push_back( indices.front() ); indices.pop_front(); g->u.activity.values.push_back( quantities.front() ); quantities.pop_front(); } } // TODO: Move this to advanced inventory instead of hacking it in here if( !keep_going ) { cancel_aim_processing(); } }
/* values explanation * 0: items from vehicle? * 1: items to a vehicle? * 2: index <-+ * 3: amount | * n: ^-------+ */ void activity_on_turn_move_items() { // Drop activity if we don't know destination coordinates. if( g->u.activity.coords.empty() ) { g->u.activity = player_activity(); return; } // Move activity has source square, target square, // indices of items on map, and quantities of same. const tripoint destination = g->u.activity.coords[0]; const tripoint source = g->u.activity.placement; bool from_vehicle = g->u.activity.values[0]; bool to_vehicle = g->u.activity.values[1]; std::list<int> indices; std::list<int> quantities; // Note i = 4, skipping first few elements. for( size_t i = 2; i < g->u.activity.values.size(); i += 2 ) { indices.push_back( g->u.activity.values[i] ); quantities.push_back( g->u.activity.values[i + 1] ); } // Nuke the current activity, leaving the backlog alone. g->u.activity = player_activity(); // *puts on 3d glasses from 90s cereal box* move_items( source, from_vehicle, destination, to_vehicle, indices, quantities ); if( !indices.empty() ) { g->u.assign_activity( activity_id( "ACT_MOVE_ITEMS" ) ); g->u.activity.placement = source; g->u.activity.coords.push_back( destination ); g->u.activity.values.push_back( from_vehicle ); g->u.activity.values.push_back( to_vehicle ); while( !indices.empty() ) { g->u.activity.values.push_back( indices.front() ); indices.pop_front(); g->u.activity.values.push_back( quantities.front() ); quantities.pop_front(); } } }
void activity_type::load( JsonObject &jo ) { activity_type result; result.id_ = activity_id( jo.get_string( "id" ) ); assign( jo, "rooted", result.rooted_, true ); result.stop_phrase_ = _( jo.get_string( "stop_phrase" ).c_str() ); assign( jo, "abortable", result.abortable_, true ); assign( jo, "suspendable", result.suspendable_, true ); assign( jo, "no_resume", result.no_resume_, true ); result.based_on_ = io::string_to_enum_look_up( based_on_type_values, jo.get_string( "based_on" ) ); if( activity_type_all.find( result.id_ ) != activity_type_all.end() ) { debugmsg( "Redefinition of %s", result.id_.c_str() ); } else { activity_type_all.insert( { result.id_, result } ); } }
void talk_function::give_aid( npc &p ) { p.add_effect( effect_currently_busy, 30_minutes ); for( int i = 0; i < num_hp_parts; i++ ) { const body_part bp_healed = player::hp_to_bp( hp_part( i ) ); g->u.heal( hp_part( i ), 5 * rng( 2, 5 ) ); if( g->u.has_effect( effect_bite, bp_healed ) ) { g->u.remove_effect( effect_bite, bp_healed ); } if( g->u.has_effect( effect_bleed, bp_healed ) ) { g->u.remove_effect( effect_bleed, bp_healed ); } if( g->u.has_effect( effect_infected, bp_healed ) ) { g->u.remove_effect( effect_infected, bp_healed ); } } g->u.assign_activity( activity_id( "ACT_WAIT_NPC" ), 10000 ); g->u.activity.str_values.push_back( p.name ); }
void craft_command::execute() { if( empty() ) { return; } bool need_selections = true; inventory map_inv; map_inv.form_from_map( crafter->pos(), PICKUP_RANGE ); if( has_cached_selections() ) { std::vector<comp_selection<item_comp>> missing_items = check_item_components_missing( map_inv ); std::vector<comp_selection<tool_comp>> missing_tools = check_tool_components_missing( map_inv ); if( missing_items.empty() && missing_tools.empty() ) { // All items we used previously are still there, so we don't need to do selection. need_selections = false; } else if( !query_continue( missing_items, missing_tools ) ) { return; } } if( need_selections ) { item_selections.clear(); const auto needs = rec->requirements(); for( const auto &it : needs.get_components() ) { comp_selection<item_comp> is = crafter->select_item_component( it, batch_size, map_inv, true ); if( is.use_from == cancel ) { return; } item_selections.push_back( is ); } tool_selections.clear(); for( const auto &it : needs.get_tools() ) { comp_selection<tool_comp> ts = crafter->select_tool_component( it, batch_size, map_inv, DEFAULT_HOTKEYS, true ); if( ts.use_from == cancel ) { return; } tool_selections.push_back( ts ); } } auto type = activity_id( is_long ? "ACT_LONGCRAFT" : "ACT_CRAFT" ); auto activity = player_activity( type, crafter->base_time_to_craft( *rec, batch_size ), -1, INT_MIN, rec->ident().str() ); activity.values.push_back( batch_size ); activity.values.push_back( calendar::turn ); activity.coords.push_back( crafter->pos() ); crafter->assign_activity( activity ); /* legacy support for lua bindings to last_batch and lastrecipe */ crafter->last_batch = batch_size; crafter->lastrecipe = rec->ident(); const auto iter = std::find( uistate.recent_recipes.begin(), uistate.recent_recipes.end(), rec->ident() ); if( iter != uistate.recent_recipes.end() ) { uistate.recent_recipes.erase( iter ); } uistate.recent_recipes.push_back( rec->ident() ); if( uistate.recent_recipes.size() > 20 ) { uistate.recent_recipes.erase( uistate.recent_recipes.begin() ); } }
void player::activate_mutation( const trait_id &mut ) { const mutation_branch &mdata = mut.obj(); auto &tdata = my_mutations[mut]; int cost = mdata.cost; // You can take yourself halfway to Near Death levels of hunger/thirst. // Fatigue can go to Exhausted. if ((mdata.hunger && get_hunger() >= 700) || (mdata.thirst && get_thirst() >= 260) || (mdata.fatigue && get_fatigue() >= EXHAUSTED)) { // Insufficient Foo to *maintain* operation is handled in player::suffer add_msg_if_player(m_warning, _("You feel like using your %s would kill you!"), mdata.name.c_str()); return; } if (tdata.powered && tdata.charge > 0) { // Already-on units just lose a bit of charge tdata.charge--; } else { // Not-on units, or those with zero charge, have to pay the power cost if (mdata.cooldown > 0) { tdata.charge = mdata.cooldown - 1; } if (mdata.hunger){ mod_hunger(cost); } if (mdata.thirst){ mod_thirst(cost); } if (mdata.fatigue){ mod_fatigue(cost); } tdata.powered = true; // Handle stat changes from activation apply_mods(mut, true); recalc_sight_limits(); } if( mut == trait_WEB_WEAVER ) { g->m.add_field( pos(), fd_web, 1 ); add_msg_if_player(_("You start spinning web with your spinnerets!")); } else if (mut == "BURROW"){ if( is_underwater() ) { add_msg_if_player(m_info, _("You can't do that while underwater.")); tdata.powered = false; return; } tripoint dirp; if (!choose_adjacent(_("Burrow where?"), dirp)) { tdata.powered = false; return; } if( dirp == pos() ) { add_msg_if_player(_("You've got places to go and critters to beat.")); add_msg_if_player(_("Let the lesser folks eat their hearts out.")); tdata.powered = false; return; } time_duration time_to_do = 0_turns; if (g->m.is_bashable(dirp) && g->m.has_flag("SUPPORTS_ROOF", dirp) && g->m.ter(dirp) != t_tree) { // Being better-adapted to the task means that skillful Survivors can do it almost twice as fast. time_to_do = 30_minutes; } else if (g->m.move_cost(dirp) == 2 && g->get_levz() == 0 && g->m.ter(dirp) != t_dirt && g->m.ter(dirp) != t_grass) { time_to_do = 10_minutes; } else { add_msg_if_player(m_info, _("You can't burrow there.")); tdata.powered = false; return; } assign_activity( activity_id( "ACT_BURROW" ), to_moves<int>( time_to_do ), -1, 0 ); activity.placement = dirp; add_msg_if_player(_("You tear into the %s with your teeth and claws."), g->m.tername(dirp).c_str()); tdata.powered = false; return; // handled when the activity finishes } else if( mut == trait_SLIMESPAWNER ) { std::vector<tripoint> valid; for( const tripoint &dest : g->m.points_in_radius( pos(), 1 ) ) { if (g->is_empty(dest)) { valid.push_back( dest ); } } // Oops, no room to divide! if( valid.empty() ) { add_msg_if_player(m_bad, _("You focus, but are too hemmed in to birth a new slimespring!")); tdata.powered = false; return; } add_msg_if_player(m_good, _("You focus, and with a pleasant splitting feeling, birth a new slimespring!")); int numslime = 1; for (int i = 0; i < numslime && !valid.empty(); i++) { const tripoint target = random_entry_removed( valid ); if( monster * const slime = g->summon_mon( mtype_id( "mon_player_blob" ), target ) ) { slime->friendly = -1; } } if (one_in(3)) { //~ Usual enthusiastic slimespring small voices! :D add_msg_if_player(m_good, _("wow! you look just like me! we should look out for each other!")); } else if (one_in(2)) { //~ Usual enthusiastic slimespring small voices! :D add_msg_if_player(m_good, _("come on, big me, let's go!")); } else { //~ Usual enthusiastic slimespring small voices! :D add_msg_if_player(m_good, _("we're a team, we've got this!")); } tdata.powered = false; return; } else if( mut == trait_NAUSEA || mut == trait_VOMITOUS ) { vomit(); tdata.powered = false; return; } else if( mut == trait_M_FERTILE ) { spores(); tdata.powered = false; return; } else if( mut == trait_M_BLOOM ) { blossoms(); tdata.powered = false; return; } else if( mut == trait_SELFAWARE ) { print_health(); tdata.powered = false; return; } else if( !mdata.spawn_item.empty() ) { item tmpitem( mdata.spawn_item ); i_add_or_drop( tmpitem ); add_msg_if_player( _( mdata.spawn_item_message.c_str() ) ); tdata.powered = false; return; } }
void activity_on_turn_wear() { // Wear activity has source square, bools indicating source type, // indices of items on map or position of items in inventory, and quantities of same. tripoint source = g->u.activity.placement + g->u.pos(); bool from_inventory = g->u.activity.values[0]; bool from_vehicle = g->u.activity.values[1]; // load vehicle information if requested int s_cargo = -1; vehicle *s_veh = nullptr; if( from_vehicle ) { const cata::optional<vpart_reference> vp = g->m.veh_at( source ).part_with_feature( "CARGO", false ); assert( vp ); s_veh = &vp->vehicle(); s_cargo = vp->part_index(); assert( s_cargo >= 0 ); } std::list<int> indices; std::list<int> quantities; if( g->u.activity.values.size() % 2 != 0 ) { debugmsg( "ACT_WEAR started with uneven number of values." ); g->u.cancel_activity(); return; } else { // Note i = 2, skipping first 2 elements. for( size_t i = 2; i < g->u.activity.values.size(); i += 2 ) { indices.push_back( g->u.activity.values[i] ); quantities.push_back( g->u.activity.values[ i + 1 ] ); } } g->u.cancel_activity(); while( g->u.moves > 0 && !indices.empty() ) { int index = indices.back(); int quantity = quantities.back(); indices.pop_back(); quantities.pop_back(); if( from_inventory ) { if( g->u.wear( index ) ) { if( --quantity > 0 ) { indices.push_back( index ); quantities.push_back( quantity ); } } } else { item *temp_item = from_vehicle ? g->m.item_from( s_veh, s_cargo, index ) : g->m.item_from( source, index ); if( temp_item == nullptr ) { continue; // No such item. } // On successful wear remove from map or vehicle. if( g->u.wear_item( *temp_item ) ) { if( from_vehicle ) { s_veh->remove_item( s_cargo, index ); } else { g->m.i_rem( source, index ); } } } } // If there are items left, we ran out of moves, so make a new activity with the remainder. if( !indices.empty() ) { g->u.assign_activity( activity_id( "ACT_WEAR" ) ); g->u.activity.placement = source - g->u.pos(); g->u.activity.values.push_back( from_inventory ); g->u.activity.values.push_back( from_vehicle ); while( !indices.empty() ) { g->u.activity.values.push_back( indices.front() ); indices.pop_front(); g->u.activity.values.push_back( quantities.front() ); quantities.pop_front(); } } }
void activity_on_turn_move_loot( player_activity &, player &p ) { const activity_id act_move_loot = activity_id( "ACT_MOVE_LOOT" ); auto &mgr = zone_manager::get_manager(); if( g->m.check_vehicle_zones( g->get_levz() ) ) { mgr.cache_vzones(); } const auto abspos = g->m.getabs( p.pos() ); const auto &src_set = mgr.get_near( zone_type_id( "LOOT_UNSORTED" ), abspos ); vehicle *src_veh, *dest_veh; int src_part, dest_part; // Nuke the current activity, leaving the backlog alone. p.activity = player_activity(); // sort source tiles by distance const auto &src_sorted = get_sorted_tiles_by_distance( abspos, src_set ); if( !mgr.is_sorting() ) { mgr.start_sort( src_sorted ); } for( auto &src : src_sorted ) { const auto &src_loc = g->m.getlocal( src ); if( !g->m.inbounds( src_loc ) ) { if( !g->m.inbounds( p.pos() ) ) { // p is implicitly an NPC that has been moved off the map, so reset the activity // and unload them p.assign_activity( act_move_loot ); p.set_moves( 0 ); g->reload_npcs(); mgr.end_sort(); return; } std::vector<tripoint> route; route = g->m.route( p.pos(), src_loc, p.get_pathfinding_settings(), p.get_path_avoid() ); if( route.empty() ) { // can't get there, can't do anything, skip it continue; } p.set_destination( route, player_activity( act_move_loot ) ); mgr.end_sort(); return; } bool is_adjacent_or_closer = square_dist( p.pos(), src_loc ) <= 1; // skip tiles in IGNORE zone and tiles on fire // (to prevent taking out wood off the lit brazier) // and inaccessible furniture, like filled charcoal kiln if( mgr.has( zone_type_id( "LOOT_IGNORE" ), src ) || g->m.get_field( src_loc, fd_fire ) != nullptr || !g->m.can_put_items_ter_furn( src_loc ) ) { continue; } // the boolean in this pair being true indicates the item is from a vehicle storage space auto items = std::vector<std::pair<item *, bool>>(); //Check source for cargo part //map_stack and vehicle_stack are different types but inherit from item_stack // TODO: use one for loop if( const cata::optional<vpart_reference> vp = g->m.veh_at( src_loc ).part_with_feature( "CARGO", false ) ) { src_veh = &vp->vehicle(); src_part = vp->part_index(); for( auto &it : src_veh->get_items( src_part ) ) { items.push_back( std::make_pair( &it, true ) ); } } else { src_veh = nullptr; src_part = -1; } for( auto &it : g->m.i_at( src_loc ) ) { items.push_back( std::make_pair( &it, false ) ); } //Skip items that have already been processed for( auto it = items.begin() + mgr.get_num_processed( src ); it < items.end(); it++ ) { mgr.increment_num_processed( src ); const auto thisitem = it->first; if( thisitem->made_of_from_type( LIQUID ) ) { // skip unpickable liquid continue; } // Only if it's from a vehicle do we use the vehicle source location information. vehicle *this_veh = it->second ? src_veh : nullptr; const int this_part = it->second ? src_part : -1; const auto id = mgr.get_near_zone_type_for_item( *thisitem, abspos ); // checks whether the item is already on correct loot zone or not // if it is, we can skip such item, if not we move the item to correct pile // think empty bag on food pile, after you ate the content if( !mgr.has( id, src ) ) { const auto &dest_set = mgr.get_near( id, abspos ); for( auto &dest : dest_set ) { const auto &dest_loc = g->m.getlocal( dest ); //Check destination for cargo part if( const cata::optional<vpart_reference> vp = g->m.veh_at( dest_loc ).part_with_feature( "CARGO", false ) ) { dest_veh = &vp->vehicle(); dest_part = vp->part_index(); } else { dest_veh = nullptr; dest_part = -1; } // skip tiles with inaccessible furniture, like filled charcoal kiln if( !g->m.can_put_items_ter_furn( dest_loc ) ) { continue; } units::volume free_space; // if there's a vehicle with space do not check the tile beneath if( dest_veh ) { free_space = dest_veh->free_volume( dest_part ); } else { free_space = g->m.free_volume( dest_loc ); } // check free space at destination if( free_space >= thisitem->volume() ) { // before we move any item, check if player is at or // adjacent to the loot source tile if( !is_adjacent_or_closer ) { std::vector<tripoint> route; bool adjacent = false; // get either direct route or route to nearest adjacent tile if // source tile is impassable if( g->m.passable( src_loc ) ) { route = g->m.route( p.pos(), src_loc, p.get_pathfinding_settings(), p.get_path_avoid() ); } else { // immpassable source tile (locker etc.), // get route to nerest adjacent tile instead route = route_adjacent( p, src_loc ); adjacent = true; } // check if we found path to source / adjacent tile if( route.empty() ) { add_msg( m_info, _( "%s can't reach the source tile. Try to sort out loot without a cart." ), p.disp_name() ); mgr.end_sort(); return; } // shorten the route to adjacent tile, if necessary if( !adjacent ) { route.pop_back(); } // set the destination and restart activity after player arrives there // we don't need to check for safe mode, // activity will be restarted only if // player arrives on destination tile p.set_destination( route, player_activity( act_move_loot ) ); mgr.end_sort(); return; } move_item( p, *thisitem, thisitem->count(), src_loc, dest_loc, this_veh, this_part ); // moved item away from source so decrement mgr.decrement_num_processed( src ); break; } } if( p.moves <= 0 ) { // Restart activity and break from cycle. p.assign_activity( act_move_loot ); mgr.end_sort(); return; } } } } // If we got here without restarting the activity, it means we're done add_msg( m_info, string_format( _( "%s sorted out every item possible." ), p.disp_name() ) ); mgr.end_sort(); }
// Handles interactions with a vehicle in the examine menu. interact_results interact_with_vehicle( vehicle *veh, const tripoint &pos, int veh_root_part ) { if( veh == nullptr ) { return ITEMS_FROM_GROUND; } std::vector<std::string> menu_items; std::vector<uimenu_entry> options_message; const bool has_items_on_ground = g->m.sees_some_items( pos, g->u ); const bool items_are_sealed = g->m.has_flag( "SEALED", pos ); auto turret = veh->turret_query( pos ); const bool has_kitchen = ( veh->part_with_feature( veh_root_part, "KITCHEN" ) >= 0 ); const bool has_faucet = ( veh->part_with_feature( veh_root_part, "FAUCET" ) >= 0 ); const bool has_weldrig = ( veh->part_with_feature( veh_root_part, "WELDRIG" ) >= 0 ); const bool has_chemlab = ( veh->part_with_feature( veh_root_part, "CHEMLAB" ) >= 0 ); const bool has_purify = ( veh->part_with_feature( veh_root_part, "WATER_PURIFIER" ) >= 0 ); const bool has_controls = ( ( veh->part_with_feature( veh_root_part, "CONTROLS" ) >= 0 ) || ( veh->part_with_feature( veh_root_part, "CTRL_ELECTRONIC" ) >= 0 ) ); const int cargo_part = veh->part_with_feature( veh_root_part, "CARGO", false ); const bool from_vehicle = cargo_part >= 0 && !veh->get_items( cargo_part ).empty(); const bool can_be_folded = veh->is_foldable(); const bool is_convertible = ( veh->tags.count( "convertible" ) > 0 ); const bool remotely_controlled = g->remoteveh() == veh; const int washing_machine_part = veh->part_with_feature( veh_root_part, "WASHING_MACHINE" ); const bool has_washmachine = washing_machine_part >= 0; bool washing_machine_on = ( washing_machine_part == -1 ) ? false : veh->parts[washing_machine_part].enabled; const bool has_monster_capture = ( veh->part_with_feature( veh_root_part, "CAPTURE_MONSTER_VEH" ) >= 0 ); const int monster_capture_part = veh->part_with_feature( veh_root_part, "CAPTURE_MONSTER_VEH" ); typedef enum { EXAMINE, TRACK, CONTROL, CONTROL_ELECTRONICS, GET_ITEMS, GET_ITEMS_ON_GROUND, FOLD_VEHICLE, UNLOAD_TURRET, RELOAD_TURRET, USE_HOTPLATE, FILL_CONTAINER, DRINK, USE_WELDER, USE_PURIFIER, PURIFY_TANK, USE_WASHMACHINE, USE_MONSTER_CAPTURE } options; uimenu selectmenu; selectmenu.addentry( EXAMINE, true, 'e', _( "Examine vehicle" ) ); selectmenu.addentry( TRACK, true, keybind( "TOGGLE_TRACKING" ), veh->tracking_toggle_string() ); if( has_controls ) { selectmenu.addentry( CONTROL, true, 'v', _( "Control vehicle" ) ); selectmenu.addentry( CONTROL_ELECTRONICS, true, keybind( "CONTROL_MANY_ELECTRONICS" ), _( "Control multiple electronics" ) ); } if( has_washmachine ) { selectmenu.addentry( USE_WASHMACHINE, true, 'W', washing_machine_on ? _( "Deactivate the washing machine" ) : _( "Activate the washing machine (1.5 hours)" ) ); } if( from_vehicle && !washing_machine_on ) { selectmenu.addentry( GET_ITEMS, true, 'g', _( "Get items" ) ); } if( has_items_on_ground && !items_are_sealed ) { selectmenu.addentry( GET_ITEMS_ON_GROUND, true, 'i', _( "Get items on the ground" ) ); } if( ( can_be_folded || is_convertible ) && !remotely_controlled ) { selectmenu.addentry( FOLD_VEHICLE, true, 'f', _( "Fold vehicle" ) ); } if( turret.can_unload() ) { selectmenu.addentry( UNLOAD_TURRET, true, 'u', _( "Unload %s" ), turret.name().c_str() ); } if( turret.can_reload() ) { selectmenu.addentry( RELOAD_TURRET, true, 'r', _( "Reload %s" ), turret.name().c_str() ); } if( ( has_kitchen || has_chemlab ) && veh->fuel_left( "battery" ) > 0 ) { selectmenu.addentry( USE_HOTPLATE, true, 'h', _( "Use the hotplate" ) ); } if( has_faucet && veh->fuel_left( "water_clean" ) > 0 ) { selectmenu.addentry( FILL_CONTAINER, true, 'c', _( "Fill a container with water" ) ); selectmenu.addentry( DRINK, true, 'd', _( "Have a drink" ) ); } if( has_weldrig && veh->fuel_left( "battery" ) > 0 ) { selectmenu.addentry( USE_WELDER, true, 'w', _( "Use the welding rig?" ) ); } if( has_purify ) { bool can_purify = veh->fuel_left( "battery" ) >= item::find_type( "water_purifier" )->charges_to_use(); selectmenu.addentry( USE_PURIFIER, can_purify, 'p', _( "Purify water in carried container" ) ); selectmenu.addentry( PURIFY_TANK, can_purify && veh->fuel_left( "water" ), 'P', _( "Purify water in vehicle tank" ) ); } if( has_monster_capture ) { selectmenu.addentry( USE_MONSTER_CAPTURE, true, 'G', _( "Capture or release a creature" ) ); } int choice; if( selectmenu.entries.size() == 1 ) { choice = selectmenu.entries.front().retval; } else { selectmenu.return_invalid = true; selectmenu.text = _( "Select an action" ); selectmenu.selected = 0; selectmenu.query(); choice = selectmenu.ret; } auto veh_tool = [&]( const itype_id & obj ) { item pseudo( obj ); if( veh->fuel_left( "battery" ) < pseudo.ammo_required() ) { return false; } auto qty = pseudo.ammo_capacity() - veh->discharge_battery( pseudo.ammo_capacity() ); pseudo.ammo_set( "battery", qty ); g->u.invoke_item( &pseudo ); veh->charge_battery( pseudo.ammo_remaining() ); return true; }; switch( static_cast<options>( choice ) ) { case USE_MONSTER_CAPTURE: { veh->use_monster_capture( monster_capture_part, pos ); return DONE; } case USE_HOTPLATE: veh_tool( "hotplate" ); return DONE; case USE_WASHMACHINE: { veh->use_washing_machine( washing_machine_part ); return DONE; } case FILL_CONTAINER: g->u.siphon( *veh, "water_clean" ); return DONE; case DRINK: { item water( "water_clean", 0 ); if( g->u.eat( water ) ) { veh->drain( "water_clean", 1 ); g->u.moves -= 250; } return DONE; } case USE_WELDER: { if( veh_tool( "welder" ) ) { // Evil hack incoming auto &act = g->u.activity; if( act.id() == activity_id( "ACT_REPAIR_ITEM" ) ) { // Magic: first tell activity the item doesn't really exist act.index = INT_MIN; // Then tell it to search it on `pos` act.coords.push_back( pos ); // Finally tell if it is the vehicle part with welding rig act.values.resize( 2 ); act.values[1] = veh->part_with_feature( veh_root_part, "WELDRIG" ); } } return DONE; } case USE_PURIFIER: veh_tool( "water_purifier" ); return DONE; case PURIFY_TANK: { auto sel = []( const vehicle_part & pt ) { return pt.is_tank() && pt.ammo_current() == "water"; }; auto title = string_format( _( "Purify <color_%s>water</color> in tank" ), get_all_colors().get_name( item::find_type( "water" )->color ).c_str() ); auto &tank = veh_interact::select_part( *veh, sel, title ); if( tank ) { double cost = item::find_type( "water_purifier" )->charges_to_use(); if( veh->fuel_left( "battery" ) < tank.ammo_remaining() * cost ) { //~ $1 - vehicle name, $2 - part name add_msg( m_bad, _( "Insufficient power to purify the contents of the %1$s's %2$s" ), veh->name.c_str(), tank.name().c_str() ); } else { //~ $1 - vehicle name, $2 - part name add_msg( m_good, _( "You purify the contents of the %1$s's %2$s" ), veh->name.c_str(), tank.name().c_str() ); veh->discharge_battery( tank.ammo_remaining() * cost ); tank.ammo_set( "water_clean", tank.ammo_remaining() ); } } return DONE; } case UNLOAD_TURRET: { g->unload( *turret.base() ); return DONE; } case RELOAD_TURRET: { item::reload_option opt = g->u.select_ammo( *turret.base(), true ); if( opt ) { g->u.assign_activity( activity_id( "ACT_RELOAD" ), opt.moves(), opt.qty() ); g->u.activity.targets.emplace_back( turret.base() ); g->u.activity.targets.push_back( std::move( opt.ammo ) ); } return DONE; } case FOLD_VEHICLE: veh->fold_up(); return DONE; case CONTROL: veh->use_controls( pos ); return DONE; case CONTROL_ELECTRONICS: veh->control_electronics(); return DONE; case EXAMINE: g->exam_vehicle( *veh ); return DONE; case TRACK: veh->toggle_tracking( ); return DONE; case GET_ITEMS_ON_GROUND: return ITEMS_FROM_GROUND; case GET_ITEMS: return from_vehicle ? ITEMS_FROM_CARGO : ITEMS_FROM_GROUND; } return DONE; }
// Pick up items at (pos). void Pickup::pick_up( const tripoint &pos, int min ) { int cargo_part = -1; const optional_vpart_position vp = g->m.veh_at( pos ); vehicle *const veh = veh_pointer_or_null( vp ); bool from_vehicle = false; if( min != -1 ) { switch( interact_with_vehicle( veh, pos, vp ? vp->part_index() : -1 ) ) { case DONE: return; case ITEMS_FROM_CARGO: { const cata::optional<vpart_reference> carg = vp.part_with_feature( "CARGO", false ); cargo_part = carg ? carg->part_index() : -1; } from_vehicle = cargo_part >= 0; break; case ITEMS_FROM_GROUND: // Nothing to change, default is to pick from ground anyway. if( g->m.has_flag( "SEALED", pos ) ) { return; } break; } } if( !from_vehicle ) { bool isEmpty = ( g->m.i_at( pos ).empty() ); // Hide the pickup window if this is a toilet and there's nothing here // but water. if( ( !isEmpty ) && g->m.furn( pos ) == f_toilet ) { isEmpty = true; for( auto maybe_water : g->m.i_at( pos ) ) { if( maybe_water.typeId() != "water" ) { isEmpty = false; break; } } } if( isEmpty && ( min != -1 || !get_option<bool>( "AUTO_PICKUP_ADJACENT" ) ) ) { return; } } // which items are we grabbing? std::vector<item> here; if( from_vehicle ) { auto vehitems = veh->get_items( cargo_part ); here.resize( vehitems.size() ); std::copy( vehitems.begin(), vehitems.end(), here.begin() ); } else { auto mapitems = g->m.i_at( pos ); here.resize( mapitems.size() ); std::copy( mapitems.begin(), mapitems.end(), here.begin() ); } if( min == -1 ) { // Recursively pick up adjacent items if that option is on. if( get_option<bool>( "AUTO_PICKUP_ADJACENT" ) && g->u.pos() == pos ) { //Autopickup adjacent direction adjacentDir[8] = {NORTH, NORTHEAST, EAST, SOUTHEAST, SOUTH, SOUTHWEST, WEST, NORTHWEST}; for( auto &elem : adjacentDir ) { tripoint apos = tripoint( direction_XY( elem ), 0 ); apos += pos; pick_up( apos, min ); } } // Bail out if this square cannot be auto-picked-up if( g->check_zone( zone_type_id( "NO_AUTO_PICKUP" ), pos ) ) { return; } else if( g->m.has_flag( "SEALED", pos ) ) { return; } } // Not many items, just grab them if( ( int )here.size() <= min && min != -1 ) { g->u.assign_activity( activity_id( "ACT_PICKUP" ) ); g->u.activity.placement = pos - g->u.pos(); g->u.activity.values.push_back( from_vehicle ); // Only one item means index is 0. g->u.activity.values.push_back( 0 ); // auto-pickup means pick up all. g->u.activity.values.push_back( 0 ); return; } std::vector<std::list<item_idx>> stacked_here; for( size_t i = 0; i < here.size(); i++ ) { item &it = here[i]; bool found_stack = false; for( auto &stack : stacked_here ) { if( stack.begin()->_item.stacks_with( it ) ) { item_idx el = { it, i }; stack.push_back( el ); found_stack = true; break; } } if( !found_stack ) { std::list<item_idx> newstack; newstack.push_back( { it, i } ); stacked_here.push_back( newstack ); } } std::reverse( stacked_here.begin(), stacked_here.end() ); if( min != -1 ) { // don't bother if we're just autopickuping g->temp_exit_fullscreen(); } bool sideStyle = use_narrow_sidebar(); // Otherwise, we have Autopickup, 2 or more items and should list them, etc. int maxmaxitems = sideStyle ? TERMY : getmaxy( g->w_messages ) - 3; int itemsH = std::min( 25, TERMY / 2 ); int pickupBorderRows = 3; // The pickup list may consume the entire terminal, minus space needed for its // header/footer and the item info window. int minleftover = itemsH + pickupBorderRows; if( maxmaxitems > TERMY - minleftover ) { maxmaxitems = TERMY - minleftover; } const int minmaxitems = sideStyle ? 6 : 9; std::vector<pickup_count> getitem( stacked_here.size() ); int maxitems = stacked_here.size(); maxitems = ( maxitems < minmaxitems ? minmaxitems : ( maxitems > maxmaxitems ? maxmaxitems : maxitems ) ); int itemcount = 0; if( min == -1 ) { //Auto Pickup, select matching items if( !select_autopickup_items( stacked_here, getitem ) ) { // If we didn't find anything, bail out now. return; } } else { int pickupH = maxitems + pickupBorderRows; int pickupW = getmaxx( g->w_messages ); int pickupY = VIEW_OFFSET_Y; int pickupX = getbegx( g->w_messages ); int itemsW = pickupW; int itemsY = sideStyle ? pickupY + pickupH : TERMY - itemsH; int itemsX = pickupX; catacurses::window w_pickup = catacurses::newwin( pickupH, pickupW, pickupY, pickupX ); catacurses::window w_item_info = catacurses::newwin( itemsH, itemsW, itemsY, itemsX ); std::string action; long raw_input_char = ' '; input_context ctxt( "PICKUP" ); ctxt.register_action( "UP" ); ctxt.register_action( "DOWN" ); ctxt.register_action( "RIGHT" ); ctxt.register_action( "LEFT" ); ctxt.register_action( "NEXT_TAB", _( "Next page" ) ); ctxt.register_action( "PREV_TAB", _( "Previous page" ) ); ctxt.register_action( "SCROLL_UP" ); ctxt.register_action( "SCROLL_DOWN" ); ctxt.register_action( "CONFIRM" ); ctxt.register_action( "SELECT_ALL" ); ctxt.register_action( "QUIT", _( "Cancel" ) ); ctxt.register_action( "ANY_INPUT" ); ctxt.register_action( "HELP_KEYBINDINGS" ); ctxt.register_action( "FILTER" ); int start = 0; int cur_it = 0; bool update = true; mvwprintw( w_pickup, 0, 0, _( "PICK UP" ) ); int selected = 0; int iScrollPos = 0; std::string filter; std::string new_filter; std::vector<int> matches;//Indexes of items that match the filter bool filter_changed = true; if( g->was_fullscreen ) { g->draw_ter(); } // Now print the two lists; those on the ground and about to be added to inv // Continue until we hit return or space do { const std::string pickup_chars = ctxt.get_available_single_char_hotkeys( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:;" ); int idx = -1; for( int i = 1; i < pickupH; i++ ) { mvwprintw( w_pickup, i, 0, " " ); } if( action == "ANY_INPUT" && raw_input_char >= '0' && raw_input_char <= '9' ) { int raw_input_char_value = ( char )raw_input_char - '0'; itemcount *= 10; itemcount += raw_input_char_value; if( itemcount < 0 ) { itemcount = 0; } } else if( action == "SCROLL_UP" ) { iScrollPos--; } else if( action == "SCROLL_DOWN" ) { iScrollPos++; } else if( action == "PREV_TAB" ) { if( start > 0 ) { start -= maxitems; } else { start = ( int )( ( matches.size() - 1 ) / maxitems ) * maxitems; } selected = start; mvwprintw( w_pickup, maxitems + 2, 0, " " ); } else if( action == "NEXT_TAB" ) { if( start + maxitems < ( int )matches.size() ) { start += maxitems; } else { start = 0; } iScrollPos = 0; selected = start; mvwprintw( w_pickup, maxitems + 2, pickupH, " " ); } else if( action == "UP" ) { selected--; iScrollPos = 0; if( selected < 0 ) { selected = matches.size() - 1; start = ( int )( matches.size() / maxitems ) * maxitems; if( start >= ( int )matches.size() ) { start -= maxitems; } } else if( selected < start ) { start -= maxitems; } } else if( action == "DOWN" ) { selected++; iScrollPos = 0; if( selected >= ( int )matches.size() ) { selected = 0; start = 0; } else if( selected >= start + maxitems ) { start += maxitems; } } else if( selected >= 0 && selected < int( matches.size() ) && ( ( action == "RIGHT" && !getitem[matches[selected]].pick ) || ( action == "LEFT" && getitem[matches[selected]].pick ) ) ) { idx = selected; } else if( action == "FILTER" ) { new_filter = filter; string_input_popup popup; popup .title( _( "Set filter" ) ) .width( 30 ) .edit( new_filter ); if( !popup.canceled() ) { filter_changed = true; } else { wrefresh( g->w_terrain ); } } else if( action == "ANY_INPUT" && raw_input_char == '`' ) { std::string ext = string_input_popup() .title( _( "Enter 2 letters (case sensitive):" ) ) .width( 3 ) .max_length( 2 ) .query_string(); if( ext.size() == 2 ) { int p1 = pickup_chars.find( ext.at( 0 ) ); int p2 = pickup_chars.find( ext.at( 1 ) ); if( p1 != -1 && p2 != -1 ) { idx = pickup_chars.size() + ( p1 * pickup_chars.size() ) + p2; } } } else if( action == "ANY_INPUT" ) { idx = ( raw_input_char <= 127 ) ? pickup_chars.find( raw_input_char ) : -1; iScrollPos = 0; } if( idx >= 0 && idx < ( int )matches.size() ) { size_t true_idx = matches[idx]; if( itemcount != 0 || getitem[true_idx].count == 0 ) { item &temp = stacked_here[true_idx].begin()->_item; int amount_available = temp.count_by_charges() ? temp.charges : stacked_here[true_idx].size(); if( itemcount >= amount_available ) { itemcount = 0; } getitem[true_idx].count = itemcount; itemcount = 0; } // Note: this might not change the value of getitem[idx] at all! getitem[true_idx].pick = ( action == "RIGHT" ? true : ( action == "LEFT" ? false : !getitem[true_idx].pick ) ); if( action != "RIGHT" && action != "LEFT" ) { selected = idx; start = ( int )( idx / maxitems ) * maxitems; } if( !getitem[true_idx].pick ) { getitem[true_idx].count = 0; } update = true; } if( filter_changed ) { matches.clear(); while( matches.empty() ) { auto filter_func = item_filter_from_string( new_filter ); for( size_t index = 0; index < stacked_here.size(); index++ ) { if( filter_func( stacked_here[index].begin()->_item ) ) { matches.push_back( index ); } } if( matches.empty() ) { popup( _( "Your filter returned no results" ) ); wrefresh( g->w_terrain ); // The filter must have results, or simply be emptied or canceled, // as this screen can't be reached without there being // items available string_input_popup popup; popup .title( _( "Set filter" ) ) .width( 30 ) .edit( new_filter ); if( popup.canceled() ) { new_filter = filter; filter_changed = false; } } } if( filter_changed ) { filter = new_filter; filter_changed = false; selected = 0; start = 0; iScrollPos = 0; } wrefresh( g->w_terrain ); } item &selected_item = stacked_here[matches[selected]].begin()->_item; werase( w_item_info ); if( selected >= 0 && selected <= ( int )stacked_here.size() - 1 ) { std::vector<iteminfo> vThisItem; std::vector<iteminfo> vDummy; selected_item.info( true, vThisItem ); draw_item_info( w_item_info, "", "", vThisItem, vDummy, iScrollPos, true, true ); } draw_custom_border( w_item_info, false ); mvwprintw( w_item_info, 0, 2, "< " ); trim_and_print( w_item_info, 0, 4, itemsW - 8, c_white, "%s >", selected_item.display_name().c_str() ); wrefresh( w_item_info ); if( action == "SELECT_ALL" ) { int count = 0; for( auto i : matches ) { if( getitem[i].pick ) { count++; } getitem[i].pick = true; } if( count == ( int )stacked_here.size() ) { for( size_t i = 0; i < stacked_here.size(); i++ ) { getitem[i].pick = false; } } update = true; } for( cur_it = start; cur_it < start + maxitems; cur_it++ ) { mvwprintw( w_pickup, 1 + ( cur_it % maxitems ), 0, " " ); if( cur_it < ( int )matches.size() ) { int true_it = matches[cur_it]; item &this_item = stacked_here[ true_it ].begin()->_item; nc_color icolor = this_item.color_in_inventory(); if( cur_it == selected ) { icolor = hilite( icolor ); } if( cur_it < ( int )pickup_chars.size() ) { mvwputch( w_pickup, 1 + ( cur_it % maxitems ), 0, icolor, char( pickup_chars[cur_it] ) ); } else if( cur_it < ( int )pickup_chars.size() + ( int )pickup_chars.size() * ( int )pickup_chars.size() ) { int p = cur_it - pickup_chars.size(); int p1 = p / pickup_chars.size(); int p2 = p % pickup_chars.size(); mvwprintz( w_pickup, 1 + ( cur_it % maxitems ), 0, icolor, "`%c%c", char( pickup_chars[p1] ), char( pickup_chars[p2] ) ); } else { mvwputch( w_pickup, 1 + ( cur_it % maxitems ), 0, icolor, ' ' ); } if( getitem[true_it].pick ) { if( getitem[true_it].count == 0 ) { wprintz( w_pickup, c_light_blue, " + " ); } else { wprintz( w_pickup, c_light_blue, " # " ); } } else { wprintw( w_pickup, " - " ); } std::string item_name; if( stacked_here[true_it].begin()->_item.ammo_type() == "money" ) { //Count charges //TODO: transition to the item_location system used for the inventory unsigned long charges_total = 0; for( const auto item : stacked_here[true_it] ) { charges_total += item._item.charges; } //Picking up none or all the cards in a stack if( !getitem[true_it].pick || getitem[true_it].count == 0 ) { item_name = stacked_here[true_it].begin()->_item.display_money( stacked_here[true_it].size(), charges_total ); } else { unsigned long charges = 0; int c = getitem[true_it].count; for( auto it = stacked_here[true_it].begin(); it != stacked_here[true_it].end() && c > 0; ++it, --c ) { charges += it->_item.charges; } item_name = string_format( _( "%s of %s" ), stacked_here[true_it].begin()->_item.display_money( getitem[true_it].count, charges ), format_money( charges_total ) ); } } else { item_name = this_item.display_name( stacked_here[true_it].size() ); } if( stacked_here[true_it].size() > 1 ) { item_name = string_format( "%d %s", stacked_here[true_it].size(), item_name.c_str() ); } if( get_option<bool>( "ITEM_SYMBOLS" ) ) { item_name = string_format( "%s %s", this_item.symbol().c_str(), item_name.c_str() ); } trim_and_print( w_pickup, 1 + ( cur_it % maxitems ), 6, pickupW - 4, icolor, item_name ); } } mvwprintw( w_pickup, maxitems + 1, 0, _( "[%s] Unmark" ), ctxt.get_desc( "LEFT", 1 ).c_str() ); center_print( w_pickup, maxitems + 1, c_light_gray, string_format( _( "[%s] Help" ), ctxt.get_desc( "HELP_KEYBINDINGS", 1 ).c_str() ) ); right_print( w_pickup, maxitems + 1, 0, c_light_gray, string_format( _( "[%s] Mark" ), ctxt.get_desc( "RIGHT", 1 ).c_str() ) ); mvwprintw( w_pickup, maxitems + 2, 0, _( "[%s] Prev" ), ctxt.get_desc( "PREV_TAB", 1 ).c_str() ); center_print( w_pickup, maxitems + 2, c_light_gray, string_format( _( "[%s] All" ), ctxt.get_desc( "SELECT_ALL", 1 ).c_str() ) ); right_print( w_pickup, maxitems + 2, 0, c_light_gray, string_format( _( "[%s] Next" ), ctxt.get_desc( "NEXT_TAB", 1 ).c_str() ) ); if( update ) { // Update weight & volume information update = false; for( int i = 9; i < pickupW; ++i ) { mvwaddch( w_pickup, 0, i, ' ' ); } units::mass weight_picked_up = 0; units::volume volume_picked_up = 0; for( size_t i = 0; i < getitem.size(); i++ ) { if( getitem[i].pick ) { item temp = stacked_here[i].begin()->_item; if( temp.count_by_charges() && getitem[i].count < temp.charges && getitem[i].count != 0 ) { temp.charges = getitem[i].count; } int num_picked = std::min( stacked_here[i].size(), getitem[i].count == 0 ? stacked_here[i].size() : getitem[i].count ); weight_picked_up += temp.weight() * num_picked; volume_picked_up += temp.volume() * num_picked; } } auto weight_predict = g->u.weight_carried() + weight_picked_up; auto volume_predict = g->u.volume_carried() + volume_picked_up; mvwprintz( w_pickup, 0, 9, weight_predict > g->u.weight_capacity() ? c_red : c_white, _( "Wgt %.1f" ), round_up( convert_weight( weight_predict ), 1 ) ); wprintz( w_pickup, c_white, "/%.1f", round_up( convert_weight( g->u.weight_capacity() ), 1 ) ); std::string fmted_volume_predict = format_volume( volume_predict ); mvwprintz( w_pickup, 0, 24, volume_predict > g->u.volume_capacity() ? c_red : c_white, _( "Vol %s" ), fmted_volume_predict.c_str() ); std::string fmted_volume_capacity = format_volume( g->u.volume_capacity() ); wprintz( w_pickup, c_white, "/%s", fmted_volume_capacity.c_str() ); }; wrefresh( w_pickup ); action = ctxt.handle_input(); raw_input_char = ctxt.get_raw_input().get_first_input(); } while( action != "QUIT" && action != "CONFIRM" ); bool item_selected = false; // Check if we have selected an item. for( auto selection : getitem ) { if( selection.pick ) { item_selected = true; } } if( action != "CONFIRM" || !item_selected ) { w_pickup = catacurses::window(); w_item_info = catacurses::window(); add_msg( _( "Never mind." ) ); g->reenter_fullscreen(); g->refresh_all(); return; } } // At this point we've selected our items, register an activity to pick them up. g->u.assign_activity( activity_id( "ACT_PICKUP" ) ); g->u.activity.placement = pos - g->u.pos(); g->u.activity.values.push_back( from_vehicle ); if( min == -1 ) { // Auto pickup will need to auto resume since there can be several of them on the stack. g->u.activity.auto_resume = true; } std::vector<std::pair<int, int>> pick_values; for( size_t i = 0; i < stacked_here.size(); i++ ) { const auto &selection = getitem[i]; if( !selection.pick ) { continue; } const auto &stack = stacked_here[i]; // Note: items can be both charged and stacked // For robustness, let's assume they can be both in the same stack bool pick_all = selection.count == 0; size_t count = selection.count; for( const item_idx &it : stack ) { if( !pick_all && count == 0 ) { break; } if( it._item.count_by_charges() ) { size_t num_picked = std::min( ( size_t )it._item.charges, count ); pick_values.push_back( { static_cast<int>( it.idx ), static_cast<int>( num_picked ) } ); count -= num_picked; } else { size_t num_picked = 1; pick_values.push_back( { static_cast<int>( it.idx ), 0 } ); count -= num_picked; } } } // The pickup activity picks up items last-to-first from its values list, so make sure the // higher indices are at the end. std::sort( pick_values.begin(), pick_values.end() ); for( auto &it : pick_values ) { g->u.activity.values.push_back( it.first ); g->u.activity.values.push_back( it.second ); } g->reenter_fullscreen(); }