void check_lethality( std::string explosive_id, int range, float lethality,
                      float epsilon )
{
    int num_survivors = 0;
    int num_subjects = 0;
    int num_wounded = 0;
    statistics<double> lethality_ratios;
    std::stringstream survivor_stats;
    int total_hp = 0;
    int average_hp = 0;
    float error = 0.0;
    float lethality_ratio = 0.0;
    do {
        // Clear map
        clear_map();
        // Spawn some monsters in a circle.
        tripoint origin( 30, 30, 0 );
        for( const tripoint monster_position : closest_tripoints_first( range, origin ) ) {
            if( rl_dist( monster_position, origin ) != range ) {
                continue;
            }
            num_subjects++;
            spawn_test_monster( "mon_zombie", monster_position );
        }
        // Set off an explosion
        item grenade( explosive_id );
        grenade.charges = 0;
        grenade.type->invoke( g->u, grenade, origin );
        // see how many monsters survive
        std::vector<Creature *> survivors = g->get_creatures_if( []( const Creature & critter ) {
            return critter.is_monster();
        } );
        num_survivors += survivors.size();
        for( Creature *survivor : survivors ) {
            survivor_stats << survivor->pos() << " " << survivor->get_hp() << ", ";
            num_wounded += ( survivor->get_hp() < survivor->get_hp_max() ) ? 1 : 0;
            total_hp += survivor->get_hp();
        }
        if( num_survivors > 0 ) {
            survivor_stats << std::endl;
            average_hp = total_hp / num_survivors;
        }
        double survivor_ratio = static_cast<double>( num_survivors ) / num_subjects;
        lethality_ratio = 1.0 - survivor_ratio;
        lethality_ratios.add( lethality_ratio );
        error = lethality_ratios.margin_of_error();
    } while( lethality_ratios.n() < 5 ||
             ( lethality_ratios.avg() + error > lethality &&
               lethality_ratios.avg() - error < lethality ) );
    INFO( "samples " << lethality_ratios.n() );
    INFO( explosive_id );
    INFO( "range " << range );
    INFO( num_survivors << " survivors out of " << num_subjects << " targets." );
    INFO( survivor_stats.str() );
    INFO( "Wounded survivors: " << num_wounded );
    INFO( "average hp of survivors: " << average_hp );
    CHECK( lethality_ratio == Approx( lethality ).epsilon( epsilon ) );
}
Пример #2
0
void inventory_selector::add_nearby_items( int radius )
{
    if( radius >= 0 ) {
        for( const auto &pos : closest_tripoints_first( radius, u.pos() ) ) {
            add_map_items( pos );
            add_vehicle_items( pos );
        }
    }
}
Пример #3
0
void mdefense::acidsplash( monster &m, Creature *const source,
                           dealt_projectile_attack const *const proj )
{
    // Would be useful to have the attack data here, for cutting vs. bashing etc.
    if( proj != nullptr && proj->dealt_dam.total_damage() <= 0 ) {
        // Projectile didn't penetrate the target, no acid will splash out of it.
        return;
    }
    if( proj != nullptr && !one_in( 3 ) ) {
        return; //Less likely for a projectile to deliver enough force
    }

    size_t num_drops = rng( 4, 6 );
    player const *const foe = dynamic_cast<player *>( source );
    if( proj == nullptr && foe != nullptr ) {
        if( foe->weapon.is_melee( DT_CUT ) || foe->weapon.is_melee( DT_STAB ) ) {
            num_drops += rng( 3, 4 );
        }

        if( foe->unarmed_attack() ) {
            damage_instance const burn {
                DT_ACID, static_cast<float>( rng( 1, 5 ) )
            };

            if( one_in( 2 ) ) {
                source->deal_damage( &m, bp_hand_l, burn );
            } else {
                source->deal_damage( &m, bp_hand_r, burn );
            }

            source->add_msg_if_player( m_bad, _( "Acid covering %s burns your hand!" ),
                                       m.disp_name().c_str() );
        }
    }

    tripoint initial_target = source == nullptr ? m.pos() : source->pos();

    // Don't splatter directly on the `m`, that doesn't work well
    auto pts = closest_tripoints_first( 1, initial_target );
    pts.erase( std::remove( pts.begin(), pts.end(), m.pos() ), pts.end() );

    projectile prj;
    prj.speed = 10;
    prj.range = 4;
    prj.proj_effects.insert( "DRAW_AS_LINE" );
    prj.proj_effects.insert( "NO_DAMAGE_SCALING" );
    prj.impact.add_damage( DT_ACID, rng( 1, 3 ) );
    for( size_t i = 0; i < num_drops; i++ ) {
        const tripoint &target = random_entry( pts );
        projectile_attack( prj, m.pos(), target, { 1200 } );
    }

    if( g->u.sees( m.pos() ) ) {
        add_msg( m_warning, _( "Acid sprays out of %s as it is hit!" ),
                 m.disp_name().c_str() );
    }
}
Пример #4
0
void debug_menu::wishmonster( const tripoint &p )
{
    std::vector<const mtype *> mtypes;

    uimenu wmenu;
    wmenu.w_x = 0;
    wmenu.w_width = TERMX;
    // disabled due to foldstring crash //( TERMX - getmaxx(w_terrain) - 30 > 24 ? getmaxx(w_terrain) : TERMX );
    wmenu.pad_right = ( wmenu.w_width - 30 );
    wmenu.return_invalid = true;
    wmenu.selected = uistate.wishmonster_selected;
    wish_monster_callback cb( mtypes );
    wmenu.callback = &cb;

    int i = 0;
    for( const auto &montype : MonsterGenerator::generator().get_all_mtypes() ) {
        wmenu.addentry( i, true, 0, montype.nname() );
        wmenu.entries[i].extratxt.txt = montype.sym;
        wmenu.entries[i].extratxt.color = montype.color;
        wmenu.entries[i].extratxt.left = 1;
        ++i;
        mtypes.push_back( &montype );
    }

    do {
        wmenu.query();
        if( wmenu.ret >= 0 ) {
            monster mon = monster( mtypes[ wmenu.ret ]->id );
            if( cb.friendly ) {
                mon.friendly = -1;
            }
            if( cb.hallucination ) {
                mon.hallucination = true;
            }
            tripoint spawn = ( p == tripoint_min ? g->look_around() : p );
            if( spawn != tripoint_min ) {
                const std::vector<tripoint> spawn_points = closest_tripoints_first( cb.group, spawn );
                int num_spawned = 0;
                for( const tripoint &spawn_point : spawn_points ) {
                    if( g->critter_at( spawn_point ) == nullptr ) {
                        ++num_spawned;
                        mon.spawn( spawn_point );
                        g->add_zombie( mon, true );
                    }
                }
                input_context ctxt( "UIMENU" );
                cb.msg = string_format( _( "Spawned %d/%d monsters, choose another or [%s] to quit." ),
                                        num_spawned, int( spawn_points.size() ), ctxt.get_desc( "QUIT" ).c_str() );
                uistate.wishmonster_selected = wmenu.ret;
                wmenu.redraw();
            }
        }
    } while( wmenu.ret >= 0 );
}
Пример #5
0
void game::wishmonster( const tripoint &p )
{
    std::vector<const mtype*> mtypes;

    uimenu wmenu;
    wmenu.w_x = 0;
    wmenu.w_width = TERMX;
    // disabled due to foldstring crash //( TERMX - getmaxx(w_terrain) - 30 > 24 ? getmaxx(w_terrain) : TERMX );
    wmenu.pad_right = ( wmenu.w_width - 30 );
    wmenu.return_invalid = true;
    wmenu.selected = uistate.wishmonster_selected;
    wish_monster_callback *cb = new wish_monster_callback( mtypes );
    wmenu.callback = cb;

    int i = 0;
    for( const auto &montype : MonsterGenerator::generator().get_all_mtypes() ) {
        wmenu.addentry( i, true, 0, "%s", montype->nname().c_str() );
        wmenu.entries[i].extratxt.txt = montype->sym;
        wmenu.entries[i].extratxt.color = montype->color;
        wmenu.entries[i].extratxt.left = 1;
        ++i;
        mtypes.push_back( montype );
    }

    do {
        wmenu.query();
        if ( wmenu.ret >= 0 ) {
            monster mon = monster( mtypes[ wmenu.ret ]->id );
            if (cb->friendly) {
                mon.friendly = -1;
            }
            if (cb->hallucination) {
                mon.hallucination = true;
            }
            tripoint spawn = ( p == tripoint_min ? look_around() : p );
            if( spawn != tripoint_min ) {
                std::vector<tripoint> spawn_points = closest_tripoints_first( cb->group, spawn );
                for( auto spawn_point : spawn_points ) {
                    mon.spawn( spawn_point );
                    add_zombie(mon, true);
                }
                cb->msg = _("Monster spawned, choose another or 'q' to quit.");
                uistate.wishmonster_selected = wmenu.ret;
                wmenu.redraw();
            }
        }
    } while ( wmenu.keypress != 'q' && wmenu.keypress != KEY_ESCAPE && wmenu.keypress != ' ' );
    delete cb;
    cb = NULL;
    return;
}
Пример #6
0
void activity_handlers::refill_vehicle_do_turn( player_activity *act, player *p )
{
    vehicle *veh = g->m.veh_at( act->placement );
    if( veh == nullptr ) {  // Vehicle must've moved or something!
        act->moves_left = 0;
        return;
    }
    bool fuel_pumped = false;
    const auto around = closest_tripoints_first( 1, p->pos() );
    for( const auto &p : around ) {
        if( g->m.ter( p ) == t_gas_pump ||
            g->m.ter_at( p ).id == "t_gas_pump_a" ||
            g->m.ter( p ) == t_diesel_pump ) {
            auto maybe_gas = g->m.i_at( p );
            for( auto gas = maybe_gas.begin(); gas != maybe_gas.end(); ) {
                if( gas->type->id == "gasoline" || gas->type->id == "diesel" ) {
                    fuel_pumped = true;
                    int lack = std::min( veh->fuel_capacity(gas->type->id) -
                                         veh->fuel_left(gas->type->id),  200 );
                    if( gas->charges > lack ) {
                        veh->refill(gas->type->id, lack);
                        gas->charges -= lack;
                        act->moves_left -= 100;
                        gas++;
                    } else {
                        add_msg(m_bad, _("With a clang and a shudder, the pump goes silent."));
                        veh->refill (gas->type->id, gas->charges);
                        gas = maybe_gas.erase( gas );
                        act->moves_left = 0;
                    }

                    break;
                }
            }

            if( fuel_pumped ) {
                break;
            }
        }
    }
    if( !fuel_pumped ) {
        // Can't find any fuel, give up.
        debugmsg("Can't find any fuel, cancelling pumping.");
        p->cancel_activity();
        return;
    }
    p->pause();
}
Пример #7
0
std::vector<const item *> player::get_eligible_containers_for_crafting() const
{
    std::vector<const item *> conts;

    if( is_container_eligible_for_crafting( weapon, true ) ) {
        conts.push_back( &weapon );
    }
    for( const auto &it : worn ) {
        if( is_container_eligible_for_crafting( it, false ) ) {
            conts.push_back( &it );
        }
    }
    for( size_t i = 0; i < inv.size(); i++ ) {
        for( const auto &it : inv.const_stack( i ) ) {
            if( is_container_eligible_for_crafting( it, false ) ) {
                conts.push_back( &it );
            }
        }
    }

    // get all potential containers within PICKUP_RANGE tiles including vehicles
    for( const auto &loc : closest_tripoints_first( PICKUP_RANGE, pos() ) ) {
        if( g->m.accessible_items( pos(), loc, PICKUP_RANGE ) ) {
            for( const auto &it : g->m.i_at( loc ) ) {
                if( is_container_eligible_for_crafting( it, true ) ) {
                    conts.emplace_back( &it );
                }
            }
        }

        int part = -1;
        vehicle *veh = g->m.veh_at( loc, part );
        if( veh && part >= 0 ) {
            part = veh->part_with_feature( part, "CARGO" );
            if( part != -1 ) {
                for( const auto &it : veh->get_items( part ) ) {
                    if( is_container_eligible_for_crafting( it, false ) ) {
                        conts.emplace_back( &it );
                    }
                }
            }
        }
    }

    return conts;
}
Пример #8
0
item_location game::inv_map_splice(
    item_filter inv_filter, item_filter ground_filter, item_filter vehicle_filter,
    const std::string &title, int radius )
{
    inventory_selector inv_s( false, false, title );

    // first get matching items from the inventory
    u.inv.restack( &u );
    u.inv.sort();
    inv_s.make_item_list( u.inv.slice_filter_by( inv_filter ) );

    // items are stacked per tile considering vehicle and map tiles separately
    static const item_category ground_cat( "GROUND:",  _( "GROUND:" ),  -1000 );
    static const item_category nearby_cat( "NEARBY:",  _( "NEARBY:" ),  -2000 );
    static const item_category vehicle_cat( "VEHICLE:", _( "VEHICLE:" ), -3000 );

    // in the below loops identical items on the same tile are grouped into lists
    // each element of stacks represents one tile and is a vector of such lists
    std::vector<std::vector<std::list<item>>> stacks;

    // an indexed_invslice is created for each map or vehicle tile
    // each list of items created above for the tile will be added to it
    std::vector<indexed_invslice> slices;

    // inv_s.first_item will later contain the chosen item as a pointer to first item
    // of one of the above lists so use this as the key when storing the item location
    std::unordered_map<item *, item_location> opts;

    // the closest 10 items also have their location added to the invlets vector
    const char min_invlet = '0';
    const char max_invlet = '9';
    char cur_invlet = min_invlet;
    std::vector<item_location> invlets;

    for( const auto &pos : closest_tripoints_first( radius, g->u.pos() ) ) {
        // second get all matching items on the map within radius
        if( m.accessible_items( g->u.pos(), pos, radius ) ) {
            auto items = m.i_at( pos );

            // create a new slice and stack for the current map tile
            stacks.emplace_back();
            slices.emplace_back();

            // reserve sufficient capacity to ensure reallocation is not required
            auto &current_stack = stacks.back();
            current_stack.reserve( items.size() );

            for( item &it : items ) {
                if( ground_filter( it ) ) {
                    auto match = std::find_if( current_stack.begin(),
                    current_stack.end(), [&]( const std::list<item> &e ) {
                        return it.stacks_with( e.back() );
                    } );
                    if( match != current_stack.end() ) {
                        match->push_back( it );
                    } else {
                        // item doesn't stack with any previous so start new list and append to current indexed_invslice
                        current_stack.emplace_back( 1, it );
                        slices.back().emplace_back( &current_stack.back(), INT_MIN );
                        opts.emplace( &current_stack.back().front(), item_location::on_map( pos, &it ) );

                        if( cur_invlet <= max_invlet ) {
                            current_stack.back().front().invlet = cur_invlet++;
                            invlets.emplace_back( item_location::on_map( pos, &it ) );
                        } else {
                            current_stack.back().front().invlet = 0;
                        }
                    }
                }
            }
            inv_s.make_item_list( slices.back(), pos == g->u.pos() ? &ground_cat : &nearby_cat );
        }

        // finally get all matching items in vehicle cargo spaces
        int part = -1;
        vehicle *veh = m.veh_at( pos, part );
        if( veh && part >= 0 ) {
            part = veh->part_with_feature( part, "CARGO" );
            if( part != -1 ) {
                auto items = veh->get_items( part );

                // create a new slice and stack for the current vehicle part
                stacks.emplace_back();
                slices.emplace_back();

                // reserve sufficient capacity to ensure reallocation is not required
                auto &current_stack = stacks.back();
                current_stack.reserve( items.size() );

                for( item &it : items ) {
                    if( vehicle_filter( it ) ) {
                        auto match = std::find_if( current_stack.begin(),
                        current_stack.end(), [&]( const std::list<item> &e ) {
                            return it.stacks_with( e.back() );
                        } );
                        if( match != current_stack.end() ) {
                            match->push_back( it );
                        } else {
                            // item doesn't stack with any previous so start new list and append to current indexed_invslice
                            current_stack.emplace_back( 1, it );
                            slices.back().emplace_back( &current_stack.back(), INT_MIN );
                            opts.emplace( &current_stack.back().front(), item_location::on_vehicle( *veh,
                                          veh->parts[part].mount, &it ) );

                            if( cur_invlet <= max_invlet ) {
                                current_stack.back().front().invlet = cur_invlet++;
                                invlets.emplace_back( item_location::on_vehicle( *veh, veh->parts[part].mount, &it ) );
                            } else {
                                current_stack.back().front().invlet = 0;
                            }
                        }
                    }
                }
                inv_s.make_item_list( slices.back(), &vehicle_cat );
            }
        }
    }

    inv_s.prepare_paging();

    while( true ) {
        inv_s.display();
        const std::string action = inv_s.ctxt.handle_input();
        const long ch = inv_s.ctxt.get_raw_input().get_first_input();
        const int item_pos = g->u.invlet_to_position( ch );

        if( item_pos != INT_MIN ) {
            // Indexed item in inventory
            inv_s.set_to_drop( item_pos, 0 );
            return item_location::on_character( u, inv_s.first_item );

        } else if( ch >= min_invlet && ch <= max_invlet ) {
            // Indexed item on ground or in vehicle
            if( (long)invlets.size() > ch - min_invlet ) {
                return std::move( invlets[ch - min_invlet] );
            }

        } else if( inv_s.handle_movement( action ) ) {
            // continue with comparison below

        } else if( action == "QUIT" ) {
            return item_location::nowhere();

        } else if( action == "RIGHT" || action == "CONFIRM" ) {
            inv_s.set_selected_to_drop( 0 );

            // Item in inventory
            if( inv_s.get_selected_item_position() != INT_MIN ) {
                return item_location::on_character( u, inv_s.first_item );
            }
            // Item on ground or in vehicle
            auto it = opts.find( inv_s.first_item );
            if( it != opts.end() ) {
                return std::move( it->second );
            }

            return item_location::nowhere();
        }
    }
}
    const std::string container_id = "bottle_plastic";
    const std::string worn_id = "flask_hip";
    const int count = 5;

    REQUIRE( item( container_id ).is_container() );
    REQUIRE( item( worn_id ).is_container() );

    player &p = g->u;
    p.worn.clear();
    p.inv.clear();
    p.remove_weapon();
    p.wear_item( item( "backpack" ) ); // so we don't drop anything

    // check if all tiles within radius are loaded within current submap and passable
    auto suitable = []( const tripoint & pos, int radius ) {
        auto tiles = closest_tripoints_first( radius, pos );
        return std::all_of( tiles.begin(), tiles.end(), []( const tripoint & e ) {
            if( !g->m.inbounds( e ) ) {
                return false;
            }
            if( const optional_vpart_position vp = g->m.veh_at( e ) ) {
                g->m.destroy_vehicle( &vp->vehicle() );
            }
            g->m.i_clear( e );
            return g->m.passable( e );
        } );
    };

    // Move to ground level to avoid weirdnesses around being underground.
    p.setz( 0 );
    // move player randomly until we find a suitable position
Пример #10
0
std::vector<tripoint> map::route( const tripoint &f, const tripoint &t,
                                  const int bash, const int maxdist ) const
{
    /* TODO: If the origin or destination is out of bound, figure out the closest
     * in-bounds point and go to that, then to the real origin/destination.
     */

    int linet1 = 0, linet2 = 0;
    if( !inbounds( f ) || !inbounds( t ) ) {
        // Note: The creature needs to understand not-moving upwards
        // or else the plans can cause it to do so.
        if( sees( f, t, -1, linet1, linet2 ) ) {
            return line_to( f, t, linet1, linet2 );
        } else {
            std::vector<tripoint> empty;
            return empty;
        }
    }
    // First, check for a simple straight line on flat ground
    // Except when the player is on the line - we need to do regular pathing then
    const tripoint &pl_pos = g->u.pos();
    if( f.z == t.z && clear_path( f, t, -1, 2, 2, linet1, linet2 ) ) {
        const auto line_path = line_to( f, t, linet1, linet2 );
        if( pl_pos.z != f.z ) {
            // Player on different z-level, certainly not on the line
            return line_path;
        }

        if( std::find( line_path.begin(), line_path.end(), pl_pos ) == line_path.end() ) {
            return line_path;
        }
    }

    const int pad = 8;  // Should be much bigger - low value makes pathfinders dumb!
    int minx = std::min( f.x, t.x ) - pad;
    int miny = std::min( f.y, t.y ) - pad;
    int minz = std::min( f.z, t.z ); // TODO: Make this way bigger
    int maxx = std::max( f.x, t.x ) + pad;
    int maxy = std::max( f.y, t.y ) + pad;
    int maxz = std::max( f.z, t.z ); // Same TODO as above
    clip_to_bounds( minx, miny, minz );
    clip_to_bounds( maxx, maxy, maxz );

    pathfinder pf( minx, miny, maxx, maxy );
    pf.add_point( 0, 0, f, f );
    // Make NPCs not want to path through player
    // But don't make player pathing stop working
    if( f != pl_pos && t != pl_pos ) {
        pf.close_point( pl_pos );
    }

    bool done = false;

    do {
        auto cur = pf.get_next();

        const int parent_index = flat_index( cur.x, cur.y );
        auto &layer = pf.get_layer( cur.z );
        auto &cur_state = layer.state[parent_index];
        if( cur_state == ASL_CLOSED ) {
            continue;
        }

        if( layer.gscore[parent_index] > maxdist ) {
            // Shortest path would be too long, return empty vector
            return std::vector<tripoint>();
        }

        if( cur == t ) {
            done = true;
            break;
        }

        cur_state = ASL_CLOSED;
        std::vector<tripoint> neighbors = closest_tripoints_first( 1, cur );

        for( const auto &p : neighbors ) {
            const int index = flat_index( p.x, p.y );

            // TODO: Remove this and instead have sentinels at the edges
            if( p.x < minx || p.x >= maxx || p.y < miny || p.y >= maxy ) {
                continue;
            }

            if( layer.state[index] == ASL_CLOSED ) {
                continue;
            }

            int part = -1;
            const maptile &tile = maptile_at_internal( p );
            const auto &terrain = tile.get_ter_t();
            const auto &furniture = tile.get_furn_t();
            const vehicle *veh = veh_at_internal( p, part );

            const int cost = move_cost_internal( furniture, terrain, veh, part );
            // Don't calculate bash rating unless we intend to actually use it
            const int rating = ( bash == 0 || cost != 0 ) ? -1 :
                                 bash_rating_internal( bash, furniture, terrain, false, veh, part );

            if( cost == 0 && rating <= 0 && terrain.open.empty() ) {
                layer.state[index] = ASL_CLOSED; // Close it so that next time we won't try to calc costs
                continue;
            }

            int newg = layer.gscore[parent_index] + cost + ( (cur.x != p.x && cur.y != p.y ) ? 1 : 0);
            if( cost == 0 ) {
                // Handle all kinds of doors
                // Only try to open INSIDE doors from the inside

                if( !terrain.open.empty() &&
                    ( !terrain.has_flag( "OPENCLOSE_INSIDE" ) || !is_outside( cur ) ) ) {
                    newg += 4; // To open and then move onto the tile
                } else if( veh != nullptr ) {
                    part = veh->obstacle_at_part( part );
                    int dummy = -1;
                    if( !veh->part_flag( part, "OPENCLOSE_INSIDE" ) || veh_at_internal( cur, dummy ) == veh ) {
                        // Handle car doors, but don't try to path through curtains
                        newg += 10; // One turn to open, 4 to move there
                    } else {
                        // Car obstacle that isn't a door
                        newg += veh->parts[part].hp / bash + 8 + 4;
                    }
                } else if( rating > 1 ) {
                    // Expected number of turns to bash it down, 1 turn to move there
                    // and 2 turns of penalty not to trash everything just because we can
                    newg += ( 20 / rating ) + 2 + 4;
                } else if( rating == 1 ) {
                    // Desperate measures, avoid whenever possible
                    newg += 500;
                } else {
                    continue; // Unbashable and unopenable from here
                }
            }

            // If not visited, add as open
            // If visited, add it only if we can do so with better score
            if( layer.state[index] == ASL_NONE || newg < layer.gscore[index] ) {
                pf.add_point( newg, newg + 2 * rl_dist( p, t ), cur, p );
            }
        }

        if( !has_zlevels() ) {
            // The part below is only for z-level pathing
            continue;
        }

        const maptile &parent_tile = maptile_at_internal( cur );
        const auto &parent_terrain = parent_tile.get_ter_t();
        if( cur.z > minz && parent_terrain.has_flag( TFLAG_GOES_DOWN ) ) {
            tripoint dest( cur.x, cur.y, cur.z - 1 );
            dest = vertical_move_destination<TFLAG_GOES_UP>( *this, dest );
            if( inbounds( dest ) ) {
                auto &layer = pf.get_layer( dest.z );
                pf.add_point( layer.gscore[parent_index] + 2,
                              layer.score[parent_index] + 2 * rl_dist( dest, t ),
                              cur, dest );
            }
        }
        if( cur.z < maxz && parent_terrain.has_flag( TFLAG_GOES_UP ) ) {
            tripoint dest( cur.x, cur.y, cur.z + 1 );
            dest = vertical_move_destination<TFLAG_GOES_DOWN>( *this, dest );
            if( inbounds( dest ) ) {
                auto &layer = pf.get_layer( dest.z );
                pf.add_point( layer.gscore[parent_index] + 2,
                              layer.score[parent_index] + 2 * rl_dist( dest, t ),
                              cur, dest );
            }
        }
    } while( !done && !pf.empty() );

    std::vector<tripoint> ret;
    ret.reserve( rl_dist( f, t ) * 2 );
    if( done ) {
        tripoint cur = t;
        // Just to limit max distance, in case something weird happens
        for( int fdist = maxdist; fdist != 0; fdist-- ) {
            const int cur_index = flat_index( cur.x, cur.y );
            const auto &layer = pf.get_layer( cur.z );
            const tripoint &par = layer.parent[cur_index];
            if( cur == f ) {
                break;
            }

            ret.push_back( cur );
            // Jumps are acceptable on 1 z-level changes
            // This is because stairs teleport the player too
            if( rl_dist( cur, par ) > 1 && abs( cur.z - par.z ) != 1 ) {
                debugmsg( "Jump in our route! %d:%d:%d->%d:%d:%d",
                          cur.x, cur.y, cur.z, par.x, par.y, par.z );
                return ret;
            }

            cur = par;
        }

        std::reverse( ret.begin(), ret.end() );
    }

    return ret;
}
void try_fuel_fire( player_activity &act, player &p, const bool starting_fire )
{
    const tripoint pos = p.pos();
    auto adjacent = closest_tripoints_first( PICKUP_RANGE, pos );
    adjacent.erase( adjacent.begin() );

    cata::optional<tripoint> best_fire = starting_fire ? act.placement : find_best_fire( adjacent,
                                         pos );

    if( !best_fire || !g->m.accessible_items( *best_fire ) ) {
        return;
    }

    const auto refuel_spot = std::find_if( adjacent.begin(), adjacent.end(),
    [pos]( const tripoint & pt ) {
        // Hacky - firewood spot is a trap and it's ID-checked
        // TODO: Something cleaner than ID-checking a trap
        return g->m.tr_at( pt ).id == tr_firewood_source && g->m.has_items( pt ) &&
               g->m.accessible_items( pt ) && g->m.clear_path( pos, pt, PICKUP_RANGE, 1, 100 );
    } );
    if( refuel_spot == adjacent.end() ) {
        return;
    }

    // Special case: fire containers allow burning logs, so use them as fuel iif fire is contained
    bool contained = g->m.has_flag_furn( TFLAG_FIRE_CONTAINER, *best_fire );
    fire_data fd( 1, contained );
    time_duration fire_age = g->m.get_field_age( *best_fire, fd_fire );

    // Maybe TODO: - refuelling in the rain could use more fuel
    // First, simulate expected burn per turn, to see if we need more fuel
    auto fuel_on_fire = g->m.i_at( *best_fire );
    for( size_t i = 0; i < fuel_on_fire.size(); i++ ) {
        fuel_on_fire[i].simulate_burn( fd );
        // Uncontained fires grow below -50_minutes age
        if( !contained && fire_age < -40_minutes && fd.fuel_produced > 1.0f &&
            !fuel_on_fire[i].made_of( LIQUID ) ) {
            // Too much - we don't want a firestorm!
            // Put first item back to refuelling pile
            std::list<int> indices_to_remove{ static_cast<int>( i ) };
            std::list<int> quantities_to_remove{ 0 };
            move_items( p, *best_fire - pos, false, *refuel_spot - pos, false, indices_to_remove,
                        quantities_to_remove );
            return;
        }
    }

    // Enough to sustain the fire
    // TODO: It's not enough in the rain
    if( !starting_fire && ( fd.fuel_produced >= 1.0f || fire_age < 10_minutes ) ) {
        return;
    }

    // We need to move fuel from stash to fire
    auto potential_fuel = g->m.i_at( *refuel_spot );
    for( size_t i = 0; i < potential_fuel.size(); i++ ) {
        if( potential_fuel[i].made_of( LIQUID ) ) {
            continue;
        }

        float last_fuel = fd.fuel_produced;
        potential_fuel[i].simulate_burn( fd );
        if( fd.fuel_produced > last_fuel ) {
            std::list<int> indices{ static_cast<int>( i ) };
            std::list<int> quantities{ 0 };
            // Note: move_items handles messages (they're the generic "you drop x")
            move_items( p, *refuel_spot - p.pos(), false, *best_fire - p.pos(), false, indices,
                        quantities );
            return;
        }
    }
}
void minesweeper_game::new_level(WINDOW *w_minesweeper)
{
    iMaxY = getmaxy(w_minesweeper) - 2;
    iMaxX = getmaxx(w_minesweeper) - 2;

    werase(w_minesweeper);

    mLevel.clear();
    mLevelReveal.clear();

    auto set_num = [&](const std::string sType, int &iVal, const int iMin, const int iMax) {
        std::ostringstream ssTemp;
        ssTemp << _("Min:") << iMin << " " << _("Max:") << " " << iMax;

        do {
            if ( iVal < iMin || iVal > iMax ) {
                iVal = iMin;
            }

            iVal = std::atoi(string_input_popup(sType.c_str(), 5, to_string(iVal), ssTemp.str().c_str(), "", -1, true).c_str());
        } while( iVal < iMin || iVal > iMax);
    };

    uimenu difficulty;
    difficulty.text = _("Game Difficulty");
    difficulty.entries.push_back(uimenu_entry(0, true, 'b', _("Beginner")));
    difficulty.entries.push_back(uimenu_entry(1, true, 'i', _("Intermediate")));
    difficulty.entries.push_back(uimenu_entry(2, true, 'e', _("Expert")));
    difficulty.entries.push_back(uimenu_entry(3, true, 'c', _("Custom")));
    difficulty.query();

    switch (difficulty.ret) {
    case 0:
        iLevelY = 8;
        iLevelX = 8;
        iBombs = 10;
        break;
    case 1:
        iLevelY = 16;
        iLevelX = 16;
        iBombs = 40;
        break;
    case 2:
        iLevelY = 16;
        iLevelX = 30;
        iBombs = 99;
        break;
    case 3:
    default:
        iLevelY = iMinY;
        iLevelX = iMinX;

        set_num(_("Level width:"), iLevelX, iMinX, iMaxX);
        set_num(_("Level height:"), iLevelY, iMinY, iMaxY);

        iBombs = iLevelX * iLevelY * 0.1;

        set_num(_("Number of bombs:"), iBombs, iBombs, iLevelX * iLevelY * 0.6);
        break;
    }

    iOffsetX = ((iMaxX - iLevelX) / 2) + 1;
    iOffsetY = ((iMaxY - iLevelY) / 2) + 1;

    int iRandX;
    int iRandY;
    for ( int i = 0; i < iBombs; i++ ) {
        do {
            iRandX = rng(0, iLevelX - 1);
            iRandY = rng(0, iLevelY - 1);
        } while ( mLevel[iRandY][iRandX] == (int)bomb );

        mLevel[iRandY][iRandX] = (int)bomb;
    }

    for ( int y = 0; y < iLevelY; y++ ) {
        for ( int x = 0; x < iLevelX; x++ ) {
            if (mLevel[y][x] == (int)bomb) {
                const auto circle = closest_tripoints_first( 1, {x, y, 0} );

                for( const auto &p : circle ) {
                    if ( p.x >= 0 && p.x < iLevelX && p.y >= 0 && p.y < iLevelY ) {
                        if ( mLevel[p.y][p.x] != (int)bomb ) {
                            mLevel[p.y][p.x]++;
                        }
                    }
                }
            }
        }
    }

    for (int y = 0; y < iLevelY; y++) {
        mvwputch(w_minesweeper, iOffsetY + y, iOffsetX, c_white, std::string(iLevelX, '#'));
    }

    mvwputch(w_minesweeper, iOffsetY, iOffsetX, hilite(c_white), "#");

    draw_custom_border(w_minesweeper, true, true, true, true, true, true, true, true,
                       BORDER_COLOR, iOffsetY - 1, iLevelY + 2, iOffsetX - 1, iLevelX + 2);
}
int minesweeper_game::start_game()
{
    const int iCenterX = (TERMX > FULL_SCREEN_WIDTH) ? (TERMX - FULL_SCREEN_WIDTH) / 2 : 0;
    const int iCenterY = (TERMY > FULL_SCREEN_HEIGHT) ? (TERMY - FULL_SCREEN_HEIGHT) / 2 : 0;

    WINDOW *w_minesweeper_border = newwin(FULL_SCREEN_HEIGHT, FULL_SCREEN_WIDTH, iCenterY, iCenterX);
    WINDOW_PTR w_minesweeper_borderptr( w_minesweeper_border );

    WINDOW *w_minesweeper = newwin(FULL_SCREEN_HEIGHT - 2, FULL_SCREEN_WIDTH - 2, iCenterY + 1, iCenterX + 1);
    WINDOW_PTR w_minesweeperptr( w_minesweeper );

    draw_border(w_minesweeper_border);

    std::vector<std::string> shortcuts;
    shortcuts.push_back(_("<n>ew level"));
    shortcuts.push_back(_("<f>lag"));
    shortcuts.push_back(_("<q>uit"));

    int iWidth = 0;
    for( auto &shortcut : shortcuts ) {
        if ( iWidth > 0 ) {
            iWidth += 1;
        }
        iWidth += utf8_width(shortcut);
    }

    int iPos = FULL_SCREEN_WIDTH - iWidth - 1;
    for( auto &shortcut : shortcuts ) {
        shortcut_print(w_minesweeper_border, 0, iPos, c_white, c_ltgreen, shortcut);
        iPos += utf8_width(shortcut) + 1;
    }

    mvwputch(w_minesweeper_border, 0, 2, hilite(c_white), _("Minesweeper"));

    wrefresh(w_minesweeper_border);

    input_context ctxt("MINESWEEPER");
    ctxt.register_cardinal();
    ctxt.register_action("NEW");
    ctxt.register_action("FLAG");
    ctxt.register_action("CONFIRM");
    ctxt.register_action("QUIT");
    ctxt.register_action("HELP_KEYBINDINGS");

    static const std::array<int, 9> aColors = {{
        c_white,
        c_ltgray,
        c_cyan,
        c_blue,
        c_ltblue,
        c_green,
        c_magenta,
        c_red,
        c_yellow
    }};

    int iScore = 5;

    int iPlayerY = 0;
    int iPlayerX = 0;

    std::function<void (int, int)> rec_reveal = [&](const int y, const int x) {
        if ( mLevelReveal[y][x] == unknown || mLevelReveal[y][x] == flag ) {
            mLevelReveal[y][x] = seen;

            if ( mLevel[y][x] == 0 ) {
                const auto circle = closest_tripoints_first( 1, {x, y, 0} );

                for( const auto &p : circle ) {
                    if ( p.x >= 0 && p.x < iLevelX && p.y >= 0 && p.y < iLevelY ) {
                        if ( mLevelReveal[p.y][p.x] != seen ) {
                            rec_reveal(p.y, p.x);
                        }
                    }
                }

                mvwputch(w_minesweeper, iOffsetY + y, iOffsetX + x, c_black, " ");

            } else {
                mvwputch(w_minesweeper, iOffsetY + y, iOffsetX + x,
                         (x == iPlayerX && y == iPlayerY) ? hilite(aColors[mLevel[y][x]]) : aColors[mLevel[y][x]],
                         to_string(mLevel[y][x]));
            }
        }
    };

    int iDirY, iDirX;

    std::string action = "NEW";

    do {
        if (action == "NEW") {
            new_level(w_minesweeper);

            iPlayerY = 0;
            iPlayerX = 0;
        }

        wrefresh(w_minesweeper);

        if (check_win()) {
            popup_top(_("Congratulations, you won!"));

            iScore = 30;

            action = "QUIT";
        } else {
            action = ctxt.handle_input();
        }

        if (ctxt.get_direction(iDirX, iDirY, action)) {
            if ( iPlayerX + iDirX >= 0 && iPlayerX + iDirX < iLevelX && iPlayerY + iDirY >= 0 && iPlayerY + iDirY < iLevelY ) {

                std::string sGlyph;
                nc_color cColor;

                for ( int i=0; i < 2; i++ ) {
                    cColor = c_white;
                    if ( mLevelReveal[iPlayerY][iPlayerX] == flag ) {
                        sGlyph = "!";
                        cColor = c_yellow;
                    } else if ( mLevelReveal[iPlayerY][iPlayerX] == seen ) {
                        if ( mLevel[iPlayerY][iPlayerX] == 0 ) {
                            sGlyph = " ";
                            cColor = c_black;
                        } else {
                            sGlyph = to_string(mLevel[iPlayerY][iPlayerX]);
                            cColor = aColors[mLevel[iPlayerY][iPlayerX]];
                        }
                    } else {
                        sGlyph = '#';
                    }

                    mvwputch(w_minesweeper, iOffsetY + iPlayerY, iOffsetX + iPlayerX, (i == 0) ? cColor : hilite(cColor), sGlyph.c_str());

                    if ( i == 0 ) {
                        iPlayerX += iDirX;
                        iPlayerY += iDirY;
                    }
                }
            }
        } else if (action == "FLAG") {
            if ( mLevelReveal[iPlayerY][iPlayerX] == unknown ) {
                mLevelReveal[iPlayerY][iPlayerX] = flag;
                mvwputch(w_minesweeper, iOffsetY + iPlayerY, iOffsetX + iPlayerX, hilite(c_yellow), "!");

            } else if ( mLevelReveal[iPlayerY][iPlayerX] == flag ) {
                mLevelReveal[iPlayerY][iPlayerX] = unknown;
                mvwputch(w_minesweeper, iOffsetY + iPlayerY, iOffsetX + iPlayerX, hilite(c_white), "#");
            }
        } else if (action == "CONFIRM") {
            if ( mLevelReveal[iPlayerY][iPlayerX] != seen ) {
                if ( mLevel[iPlayerY][iPlayerX] == (int)bomb ) {
                    for ( int y = 0; y < iLevelY; y++ ) {
                        for ( int x = 0; x < iLevelX; x++ ) {
                            if (mLevel[y][x] == (int)bomb) {
                                mvwputch(w_minesweeper, iOffsetY + y, iOffsetX + x, hilite(c_red), (mLevelReveal[y][x] == flag) ? "!" : "*" );
                            }
                        }
                    }

                    wrefresh(w_minesweeper);

                    popup_top(_("Boom, you're dead! Better luck next time."));
                    action = "QUIT";

                } else if ( mLevelReveal[iPlayerY][iPlayerX] == unknown ) {
                    rec_reveal(iPlayerY, iPlayerX);
                }
            }
        }

    } while (action != "QUIT");

    return iScore;
}
item_location game::inv_map_splice(
    item_filter inv_filter, item_filter ground_filter, item_filter vehicle_filter,
    const std::string &title, int radius, const std::string &none_message )
{
    u.inv.restack( &u );
    u.inv.sort();

    inventory_selector inv_s( u, inv_filter );

    std::list<item_category> categories;
    int rank = -1000;

    // items are stacked per tile considering vehicle and map tiles separately
    // in the below loops identical items on the same tile are grouped into lists
    // each element of stacks represents one tile and is a vector of such lists
    std::vector<std::vector<std::list<item>>> stacks;

    // an indexed_invslice is created for each map or vehicle tile
    // each list of items created above for the tile will be added to it
    std::vector<indexed_invslice> slices;

    // of one of the above lists so use this as the key when storing the item location
    std::unordered_map<item *, item_location> opts;

    // the closest 10 items also have their location added to the invlets vector
    const char min_invlet = '0';
    const char max_invlet = '9';
    char cur_invlet = min_invlet;

    for( const auto &pos : closest_tripoints_first( radius, g->u.pos() ) ) {
        // second get all matching items on the map within radius
        if( m.accessible_items( g->u.pos(), pos, radius ) ) {
            auto items = m.i_at( pos );

            // create a new slice and stack for the current map tile
            stacks.emplace_back();
            slices.emplace_back();

            // reserve sufficient capacity to ensure reallocation is not required
            auto &current_stack = stacks.back();
            current_stack.reserve( items.size() );

            for( item &it : items ) {
                if( ground_filter( it ) ) {
                    auto match = std::find_if( current_stack.begin(),
                    current_stack.end(), [&]( const std::list<item> &e ) {
                        return it.stacks_with( e.back() );
                    } );
                    if( match != current_stack.end() ) {
                        match->push_back( it );
                    } else {
                        // item doesn't stack with any previous so start new list and append to current indexed_invslice
                        current_stack.emplace_back( 1, it );
                        slices.back().emplace_back( &current_stack.back(), INT_MIN );
                        opts.emplace( &current_stack.back().front(), item_location( pos, &it ) );

                        current_stack.back().front().invlet = ( cur_invlet <= max_invlet ) ? cur_invlet++ : 0;
                    }
                }
            }
            std::string name = trim( std::string( _( "GROUND" ) ) + " " + direction_suffix( g->u.pos(), pos ) );
            categories.emplace_back( name, name, rank++ );
            inv_s.add_entries( slices.back(), &categories.back() );
        }

        // finally get all matching items in vehicle cargo spaces
        int part = -1;
        vehicle *veh = m.veh_at( pos, part );
        if( veh && part >= 0 ) {
            part = veh->part_with_feature( part, "CARGO" );
            if( part != -1 ) {
                auto items = veh->get_items( part );

                // create a new slice and stack for the current vehicle part
                stacks.emplace_back();
                slices.emplace_back();

                // reserve sufficient capacity to ensure reallocation is not required
                auto &current_stack = stacks.back();
                current_stack.reserve( items.size() );

                for( item &it : items ) {
                    if( vehicle_filter( it ) ) {
                        auto match = std::find_if( current_stack.begin(),
                        current_stack.end(), [&]( const std::list<item> &e ) {
                            return it.stacks_with( e.back() );
                        } );
                        if( match != current_stack.end() ) {
                            match->push_back( it );
                        } else {
                            // item doesn't stack with any previous so start new list and append to current indexed_invslice
                            current_stack.emplace_back( 1, it );
                            slices.back().emplace_back( &current_stack.back(), INT_MIN );
                            opts.emplace( &current_stack.back().front(), item_location( vehicle_cursor( *veh, part ), &it ) );

                            current_stack.back().front().invlet = ( cur_invlet <= max_invlet ) ? cur_invlet++ : 0;
                        }
                    }
                }
                std::string name = trim( std::string( _( "VEHICLE" ) )  + " " + direction_suffix( g->u.pos(), pos ) );
                categories.emplace_back( name, name, rank-- );
                inv_s.add_entries( slices.back(), &categories.back() );
            }
        }
    }

    if( inv_s.empty() ) {
        const std::string msg = ( none_message.empty() ) ? _( "You don't have the necessary item at hand." ) : none_message;
        popup( msg, PF_GET_KEY );
        return item_location();
    }
    return inv_s.execute_pick_map( title, opts );
}