Ejemplo n.º 1
0
	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;
	}
Ejemplo n.º 2
0
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();
    }
}
Ejemplo n.º 3
0
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 );
}
Ejemplo n.º 4
0
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;
}
Ejemplo n.º 5
0
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 );
}
Ejemplo n.º 6
0
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 );
}
Ejemplo n.º 7
0
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 );
}
Ejemplo n.º 8
0
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 );
}
Ejemplo n.º 9
0
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;
}
Ejemplo n.º 10
0
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();
        }
    }
}
Ejemplo n.º 13
0
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 } );
    }
}
Ejemplo n.º 14
0
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 );
}
Ejemplo n.º 15
0
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() );
    }
}
Ejemplo n.º 16
0
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();
}
Ejemplo n.º 19
0
// 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;
}
Ejemplo n.º 20
0
// 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();
}