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 ) ); }
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 ); } } }
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() ); } }
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 ); }
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; }
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(); }
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; }
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 ¤t_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( ¤t_stack.back(), INT_MIN ); opts.emplace( ¤t_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 ¤t_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( ¤t_stack.back(), INT_MIN ); opts.emplace( ¤t_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
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 ¤t_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( ¤t_stack.back(), INT_MIN ); opts.emplace( ¤t_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 ¤t_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( ¤t_stack.back(), INT_MIN ); opts.emplace( ¤t_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 ); }