// Pick up items at (pos). void Pickup::pick_up( const tripoint &pos, int min ) { int veh_root_part = 0; int cargo_part = -1; vehicle *veh = g->m.veh_at (pos, veh_root_part); bool from_vehicle = false; if( min != -1 ) { cargo_part = interact_with_vehicle( veh, pos, veh_root_part ); from_vehicle = cargo_part >= 0; if( cargo_part == -2 ) { // -2 indicates that we already interacted with the vehicle. return; } } if (g->m.has_flag("SEALED", pos)) { return; } //min == -1 is Autopickup if (!g->u.can_pickup(min != -1)) { // no message on autopickup (-1) return; } 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 || !OPTIONS["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) { if( g->check_zone( "NO_AUTO_PICKUP", pos ) ) { here.clear(); } // Recursively pick up adjacent items if that option is on. if( OPTIONS["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; if( g->m.has_flag( "SEALED", apos ) ) { continue; } if( g->check_zone( "NO_AUTO_PICKUP", apos ) ) { continue; } pick_up( apos, min ); } } } // Not many items, just grab them if ((int)here.size() <= min && min != -1) { g->u.assign_activity( ACT_PICKUP, 0 ); 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; } if(min != -1) { // don't bother if we're just autopickup-ing 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<bool> getitem; getitem.resize(here.size(), false); int maxitems = here.size(); maxitems = (maxitems < minmaxitems ? minmaxitems : (maxitems > maxmaxitems ? maxmaxitems : maxitems )); int itemcount = 0; std::map<int, unsigned int> pickup_count; // Count of how many we'll pick up from each stack if (min == -1) { //Auto Pickup, select matching items if( !select_autopickup_items( 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; WINDOW *w_pickup = newwin(pickupH, pickupW, pickupY, pickupX); WINDOW *w_item_info = newwin(itemsH, itemsW, itemsY, itemsX); WINDOW_PTR w_pickupptr( w_pickup ); WINDOW_PTR w_item_infoptr( w_item_info ); int ch = ' '; int start = 0, cur_it; int new_weight = g->u.weight_carried(), new_volume = g->u.volume_carried(); bool update = true; mvwprintw(w_pickup, 0, 0, _("PICK UP")); int selected = 0; int last_selected = -1; 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 { static const std::string pickup_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:;"; int idx = -1; for (int i = 1; i < pickupH; i++) { mvwprintw(w_pickup, i, 0, " "); } if (ch >= '0' && ch <= '9') { ch = (char)ch - '0'; itemcount *= 10; itemcount += ch; if( itemcount < 0 ) { itemcount = 0; } } else if ( ch == '<' || ch == KEY_PPAGE ) { if ( start > 0 ) { start -= maxitems; } else { start = (int)( (here.size()-1) / maxitems ) * maxitems; } selected = start; mvwprintw(w_pickup, maxitems + 2, 0, " "); } else if ( ch == '>' || ch == KEY_NPAGE ) { if ( start + maxitems < (int)here.size() ) { start += maxitems; } else { start = 0; } selected = start; mvwprintw(w_pickup, maxitems + 2, pickupH, " "); } else if ( ch == KEY_UP ) { selected--; if ( selected < 0 ) { selected = here.size() - 1; start = (int)( here.size() / maxitems ) * maxitems; if (start >= (int)here.size()) { start -= maxitems; } } else if ( selected < start ) { start -= maxitems; } } else if ( ch == KEY_DOWN ) { selected++; if ( selected >= (int)here.size() ) { selected = 0; start = 0; } else if ( selected >= start + maxitems ) { start += maxitems; } } else if ( selected >= 0 && ( ( ch == KEY_RIGHT && !getitem[selected]) || ( ch == KEY_LEFT && getitem[selected] ) ) ) { idx = selected; } else if ( ch == '`' ) { std::string ext = string_input_popup( _("Enter 2 letters (case sensitive):"), 3, "", "", "", 2); 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 { idx = ( ch <= 127 ) ? pickup_chars.find(ch) : -1; } if( idx >= 0 && idx < (int)here.size()) { if( getitem[idx] ) { if( pickup_count[idx] != 0 && (int)pickup_count[idx] < here[idx].charges ) { item temp = here[idx]; temp.charges = pickup_count[idx]; new_weight -= temp.weight(); new_volume -= temp.volume(); } else { new_weight -= here[idx].weight(); new_volume -= here[idx].volume(); } } if (itemcount != 0 || pickup_count[idx] == 0) { if (itemcount >= here[idx].charges || !here[idx].count_by_charges()) { // Ignore the count if we pickup the whole stack anyway // or something that is not counted by charges (tools) itemcount = 0; } pickup_count[idx] = itemcount; itemcount = 0; } // Note: this might not change the value of getitem[idx] at all! getitem[idx] = ( ch == KEY_RIGHT ? true : ( ch == KEY_LEFT ? false : !getitem[idx] ) ); if ( ch != KEY_RIGHT && ch != KEY_LEFT) { selected = idx; start = (int)( idx / maxitems ) * maxitems; } if (getitem[idx]) { if (pickup_count[idx] != 0 && (int)pickup_count[idx] < here[idx].charges) { item temp = here[idx]; temp.charges = pickup_count[idx]; new_weight += temp.weight(); new_volume += temp.volume(); } else { new_weight += here[idx].weight(); new_volume += here[idx].volume(); } } else { pickup_count[idx] = 0; } update = true; } if ( selected != last_selected ) { last_selected = selected; werase(w_item_info); if ( selected >= 0 && selected <= (int)here.size() - 1 ) { std::vector<iteminfo> vThisItem, vDummy; here[selected].info(true, vThisItem); draw_item_info(w_item_info, "", vThisItem, vDummy, 0, true, true); } draw_border(w_item_info); mvwprintw(w_item_info, 0, 2, "< "); trim_and_print(w_item_info, 0, 4, itemsW - 8, c_white, "%s >", here[selected].display_name().c_str()); wrefresh(w_item_info); } if (ch == ',') { int count = 0; for (size_t i = 0; i < here.size(); i++) { if (getitem[i]) { count++; } else { new_weight += here[i].weight(); new_volume += here[i].volume(); } getitem[i] = true; } if (count == (int)here.size()) { for (size_t i = 0; i < here.size(); i++) { getitem[i] = false; } new_weight = g->u.weight_carried(); new_volume = g->u.volume_carried(); } update = true; } for (cur_it = start; cur_it < start + maxitems; cur_it++) { mvwprintw(w_pickup, 1 + (cur_it % maxitems), 0, " "); if (cur_it < (int)here.size()) { nc_color icolor = here[cur_it].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 { 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])); } if (getitem[cur_it]) { if (pickup_count[cur_it] == 0) { wprintz(w_pickup, c_ltblue, " + "); } else { wprintz(w_pickup, c_ltblue, " # "); } } else { wprintw(w_pickup, " - "); } std::string item_name = here[cur_it].display_name(); if (OPTIONS["ITEM_SYMBOLS"]) { item_name = string_format("%c %s", here[cur_it].symbol(), item_name.c_str()); } trim_and_print(w_pickup, 1 + (cur_it % maxitems), 6, pickupW - 4, icolor, "%s", item_name.c_str()); } } int pw = pickupW; const char *unmark = _("[left] Unmark"); const char *scroll = _("[up/dn] Scroll"); const char *mark = _("[right] Mark"); mvwprintw(w_pickup, maxitems + 1, 0, unmark); mvwprintw(w_pickup, maxitems + 1, (pw - std::strlen(scroll)) / 2, scroll); mvwprintw(w_pickup, maxitems + 1, pw - std::strlen(mark), mark); const char *prev = _("[pgup] Prev"); const char *all = _("[,] All"); const char *next = _("[pgdn] Next"); mvwprintw(w_pickup, maxitems + 2, 0, prev); mvwprintw(w_pickup, maxitems + 2, (pw - std::strlen(all)) / 2, all); mvwprintw(w_pickup, maxitems + 2, pw - std::strlen(next), next); if (update) { // Update weight & volume information update = false; for (int i = 9; i < pickupW; ++i) { mvwaddch(w_pickup, 0, i, ' '); } mvwprintz(w_pickup, 0, 9, (new_weight > g->u.weight_capacity() ? c_red : c_white), _("Wgt %.1f"), g->u.convert_weight(new_weight) + 0.05); // +0.05 to round up wprintz(w_pickup, c_white, "/%.1f", g->u.convert_weight(g->u.weight_capacity())); mvwprintz(w_pickup, 0, 24, (new_volume > g->u.volume_capacity() ? c_red : c_white), _("Vol %d"), new_volume); wprintz(w_pickup, c_white, "/%d", g->u.volume_capacity()); } wrefresh(w_pickup); ch = (int)getch(); } while (ch != ' ' && ch != '\n' && ch != KEY_ENTER && ch != KEY_ESCAPE); bool item_selected = false; // Check if we have selected an item. for( auto selection : getitem ) { if( selection ) { item_selected = true; } } if( (ch != '\n' && ch != KEY_ENTER) || !item_selected ) { w_pickupptr.reset(); w_item_infoptr.reset(); 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( ACT_PICKUP, 0 ); 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; } for (size_t i = 0; i < here.size(); i++) { if( getitem[i] ) { g->u.activity.values.push_back( i ); g->u.activity.values.push_back( pickup_count[i] ); } } g->reenter_fullscreen(); }
std::vector<overmap *> overmapbuffer::get_overmaps_near( const point &p, const int radius ) { return get_overmaps_near( tripoint( p.x, p.y, 0 ), radius ); }
tripoint overmapbuffer::om_to_sm_copy(const tripoint& p) { return tripoint(p.x * 2 * OMAPX, p.y * 2 * OMAPX, p.z); }
tripoint overmapbuffer::omt_to_seg_copy(const tripoint& p) { return tripoint(divide(p.x, 32), divide(p.y, 32), p.z); }
std::vector<tripoint> map::route( const tripoint &f, const tripoint &t, const int bash, const int maxdist, const std::set<tripoint> &pre_closed ) 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. */ std::vector<tripoint> ret; if( !inbounds( f ) ) { return ret; } if( !inbounds( t ) ) { tripoint clipped = t; clip_to_bounds( clipped ); return route( f, clipped, bash, maxdist ); } // First, check for a simple straight line on flat ground // Except when the line contains a pre-closed tile - we need to do regular pathing then if( f.z == t.z && clear_path( f, t, -1, 2, 2 ) ) { const auto line_path = line_to( f, t ); const std::set<tripoint> sorted_line( line_path.begin(), line_path.end() ); if( is_disjoint( sorted_line, pre_closed ) ) { return line_path; } } const int pad = 16; // 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 ); // Make NPCs not want to path through player // But don't make player pathing stop working for( const auto &p : pre_closed ) { if( p.x >= minx && p.x < maxx && p.y >= miny && p.y < maxy ) { pf.close_point( p ); } } // Start and end must not be closed pf.unclose_point( f ); pf.unclose_point( t ); pf.add_point( 0, 0, f, f ); 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; const auto &pf_cache = get_pathfinding_cache_ref( cur.z ); const auto cur_special = pf_cache.special[cur.x][cur.y]; // 7 3 5 // 1 . 2 // 6 4 8 constexpr std::array<int, 8> x_offset{{ -1, 1, 0, 0, 1, -1, -1, 1 }}; constexpr std::array<int, 8> y_offset{{ 0, 0, -1, 1, -1, 1, -1, 1 }}; for( size_t i = 0; i < 8; i++ ) { const tripoint p( cur.x + x_offset[i], cur.y + y_offset[i], cur.z ); 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; } // Penalize for diagonals or the path will look "unnatural" int newg = layer.gscore[parent_index] + ( ( cur.x != p.x && cur.y != p.y ) ? 1 : 0 ); const auto p_special = pf_cache.special[p.x][p.y]; constexpr auto non_normal = PF_SLOW | PF_WALL | PF_VEHICLE | PF_TRAP; // @todo De-uglify, de-huge-n if( !( p_special & non_normal ) ) { // Boring flat dirt - the most common case above the ground newg += 2; } else { 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 && veh == nullptr ) { layer.state[index] = ASL_CLOSED; // Close it so that next time we won't try to calc costs continue; } newg += cost; if( cost == 0 ) { // Handle all kinds of doors // Only try to open INSIDE doors from the inside if( terrain.open && ( !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, VPFLAG_OPENABLE ) && ( !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 if( part != -1 && bash > 0 ) { // Car obstacle that isn't a door // Or there is no car obstacle, but the car is wedged into an obstacle, // in which case part == -1 newg += 2 * veh->parts[part].hp / bash + 8 + 4; } else { if( !veh->part_flag( part, VPFLAG_OPENABLE ) ) { // Won't be openable, don't try from other sides layer.state[index] = ASL_CLOSED; } continue; } } else if( rating > 1 ) { // Expected number of turns to bash it down, 1 turn to move there // and 5 turns of penalty not to trash everything just because we can newg += ( 20 / rating ) + 2 + 10; } else if( rating == 1 ) { // Desperate measures, avoid whenever possible newg += 500; } else { continue; // Unbashable and unopenable from here } } if( p_special & PF_TRAP ) { const auto &ter_trp = terrain.trap.obj(); const auto &trp = ter_trp.is_benign() ? tile.get_trap_t() : ter_trp; if( !trp.is_benign() ) { // For now make them detect all traps if( has_zlevels() && terrain.has_flag( TFLAG_NO_FLOOR ) ) { // Special case - ledge in z-levels // Warning: really expensive, needs a cache if( valid_move( p, tripoint( p.x, p.y, p.z - 1 ), false, true ) ) { tripoint below( p.x, p.y, p.z - 1 ); if( !has_flag( TFLAG_NO_FLOOR, below ) ) { // Otherwise this would have been a huge fall auto &layer = pf.get_layer( p.z - 1 ); // From cur, not p, because we won't be walking on air pf.add_point( layer.gscore[parent_index] + 10, layer.score[parent_index] + 10 + 2 * rl_dist( below, t ), cur, below ); } // Close p, because we won't be walking on it layer.state[index] = ASL_CLOSED; continue; } } else { // Otherwise it's walkable newg += 500; } } } } // 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() || !( cur_special & PF_UPDOWN ) ) { // 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 ); } } if( cur.z < maxz && parent_terrain.has_flag( TFLAG_RAMP ) && valid_move( cur, tripoint( cur.x, cur.y, cur.z + 1 ), false, true ) ) { auto &layer = pf.get_layer( cur.z + 1 ); for( size_t it = 0; it < 8; it++ ) { const tripoint above( cur.x + x_offset[it], cur.y + y_offset[it], cur.z + 1 ); pf.add_point( layer.gscore[parent_index] + 4, layer.score[parent_index] + 4 + 2 * rl_dist( above, t ), cur, above ); } } } while( !done && !pf.empty() ); 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; }
/* * Drawing-related functions */ void Creature::draw( const catacurses::window &w, int player_x, int player_y, bool inverted ) const { draw( w, tripoint( player_x, player_y, posz() ), inverted ); }
void board_up( map &m, const tripoint &start, const tripoint &end ) { std::vector<tripoint> furnitures1; std::vector<tripoint> furnitures2; std::vector<tripoint> boardables; tripoint p; p.z = m.get_abs_sub().z; int &x = p.x; int &y = p.y; int &z = p.z; for( x = start.x; x < end.x; x++ ) { for( y = start.y; y < end.y; y++ ) { bool must_board_around = false; const ter_id t = m.ter( x, y ); if( t == t_window_domestic || t == t_window || t == t_window_no_curtains ) { // Windows are always to the outside and must be boarded must_board_around = true; m.ter_set( p, t_window_boarded ); } else if( t == t_door_c || t == t_door_locked || t == t_door_c_peep ) { // Only board up doors that lead to the outside if( m.is_outside( tripoint( x + 1, y, z ) ) || m.is_outside( tripoint( x - 1, y, z ) ) || m.is_outside( tripoint( x, y + 1, z ) ) || m.is_outside( tripoint( x, y - 1, z ) ) ) { m.ter_set( p, t_door_boarded ); must_board_around = true; } else { // internal doors are opened instead m.ter_set( p, t_door_o ); } } if( must_board_around ) { // Board up the surroundings of the door/window add_boardable( m, tripoint( x + 1, y, z ), boardables ); add_boardable( m, tripoint( x - 1, y, z ), boardables ); add_boardable( m, tripoint( x, y + 1, z ), boardables ); add_boardable( m, tripoint( x, y - 1, z ), boardables ); add_boardable( m, tripoint( x + 1, y + 1, z ), boardables ); add_boardable( m, tripoint( x - 1, y + 1, z ), boardables ); add_boardable( m, tripoint( x + 1, y - 1, z ), boardables ); add_boardable( m, tripoint( x - 1, y - 1, z ), boardables ); } } } // Find all furniture that can be used to board up some place for( x = start.x; x < end.x; x++ ) { for( y = start.y; y < end.y; y++ ) { if( std::find( boardables.begin(), boardables.end(), p ) != boardables.end() ) { continue; } if( !m.has_furn( p ) ) { continue; } // If the furniture is movable and the character can move it, use it to barricade // g->u is workable here as NPCs by definition are not starting the game. (Let's hope.) ///\EFFECT_STR determines what furniture might be used as a starting area barricade if( m.furn_at( p ).move_str_req > 0 && m.furn_at( p ).move_str_req < g->u.get_str() ) { if( m.furn_at( p ).movecost == 0 ) { // Obstacles are better, prefer them furnitures1.push_back( p ); } else { furnitures2.push_back( p ); } } } } while( ( !furnitures1.empty() || !furnitures2.empty() ) && !boardables.empty() ) { const tripoint fp = random_entry_removed( furnitures1.empty() ? furnitures2 : furnitures1 ); const tripoint bp = random_entry_removed( boardables ); m.furn_set( bp, m.furn( fp ) ); m.furn_set( fp, f_null ); auto destination_items = m.i_at( bp ); for( auto moved_item : m.i_at( fp ) ) { destination_items.push_back( moved_item ); } m.i_clear( fp ); } }
// We're reading in way too many entities here to mess around with creating sub-objects and // seeking around in them, so we're using the json streaming API. submap *mapbuffer::unserialize_submaps( const tripoint &p ) { // Map the tripoint to the submap quad that stores it. const tripoint om_addr = overmapbuffer::sm_to_omt_copy( p ); const tripoint segment_addr = overmapbuffer::omt_to_seg_copy( om_addr ); std::stringstream quad_path; quad_path << world_generator->active_world->world_path << "/maps/" << segment_addr.x << "." << segment_addr.y << "." << segment_addr.z << "/" << om_addr.x << "." << om_addr.y << "." << om_addr.z << ".map"; std::ifstream fin( quad_path.str().c_str() ); if( !fin.is_open() ) { // If it doesn't exist, trigger generating it. return NULL; } JsonIn jsin( fin ); jsin.start_array(); while( !jsin.end_array() ) { std::unique_ptr<submap> sm(new submap()); tripoint submap_coordinates; jsin.start_object(); bool rubpow_update = false; while( !jsin.end_object() ) { std::string submap_member_name = jsin.get_member_name(); if( submap_member_name == "version" ) { if (jsin.get_int() < 22) { rubpow_update = true; } } else if( submap_member_name == "coordinates" ) { jsin.start_array(); int locx = jsin.get_int(); int locy = jsin.get_int(); int locz = jsin.get_int(); jsin.end_array(); submap_coordinates = tripoint( locx, locy, locz ); } else if( submap_member_name == "turn_last_touched" ) { sm->turn_last_touched = jsin.get_int(); } else if( submap_member_name == "temperature" ) { sm->temperature = jsin.get_int(); } else if( submap_member_name == "terrain" ) { // TODO: try block around this to error out if we come up short? jsin.start_array(); // Small duplication here so that the update check is only performed once if (rubpow_update) { std::string ter_string; item rock = item("rock", 0); item chunk = item("steel_chunk", 0); for( int j = 0; j < SEEY; j++ ) { for( int i = 0; i < SEEX; i++ ) { ter_string = jsin.get_string(); if (ter_string == "t_rubble") { sm->ter[i][j] = termap[ "t_dirt" ].loadid; sm->frn[i][j] = furnmap[ "f_rubble" ].loadid; sm->itm[i][j].push_back( rock ); sm->itm[i][j].push_back( rock ); } else if (ter_string == "t_wreckage"){ sm->ter[i][j] = termap[ "t_dirt" ].loadid; sm->frn[i][j] = furnmap[ "f_wreckage" ].loadid; sm->itm[i][j].push_back( chunk ); sm->itm[i][j].push_back( chunk ); } else if (ter_string == "t_ash"){ sm->ter[i][j] = termap[ "t_dirt" ].loadid; sm->frn[i][j] = furnmap[ "f_ash" ].loadid; } else if (ter_string == "t_pwr_sb_support_l"){ sm->ter[i][j] = termap[ "t_support_l" ].loadid; } else if (ter_string == "t_pwr_sb_switchgear_l"){ sm->ter[i][j] = termap[ "t_switchgear_l" ].loadid; } else if (ter_string == "t_pwr_sb_switchgear_s"){ sm->ter[i][j] = termap[ "t_switchgear_s" ].loadid; } else { sm->ter[i][j] = terfind( ter_string ); } } } } else { for( int j = 0; j < SEEY; j++ ) { for( int i = 0; i < SEEX; i++ ) { sm->ter[i][j] = terfind( jsin.get_string() ); } } } jsin.end_array(); } else if( submap_member_name == "radiation" ) { int rad_cell = 0; jsin.start_array(); while( !jsin.end_array() ) { int rad_strength = jsin.get_int(); int rad_num = jsin.get_int(); for( int i = 0; i < rad_num; ++i ) { // A little array trick here, assign to it as a 1D array. // If it's not in bounds we're kinda hosed anyway. sm->set_radiation(0, rad_cell, rad_strength); rad_cell++; } } } else if( submap_member_name == "furniture" ) { jsin.start_array(); while( !jsin.end_array() ) { jsin.start_array(); int i = jsin.get_int(); int j = jsin.get_int(); sm->frn[i][j] = furnmap[ jsin.get_string() ].loadid; jsin.end_array(); } } else if( submap_member_name == "items" ) { jsin.start_array(); while( !jsin.end_array() ) { int i = jsin.get_int(); int j = jsin.get_int(); jsin.start_array(); while( !jsin.end_array() ) { item tmp; jsin.read( tmp ); if( tmp.is_emissive() ) { sm->update_lum_add(tmp, i, j); } tmp.visit_items( [ &sm, i, j ]( item *it ) { for( auto& e: it->magazine_convert() ) { sm->itm[i][j].push_back( e ); } return VisitResponse::NEXT; } ); sm->itm[i][j].push_back( tmp ); if( tmp.needs_processing() ) { sm->active_items.add( std::prev(sm->itm[i][j].end()), point( i, j ) ); } } } } else if( submap_member_name == "traps" ) { jsin.start_array(); while( !jsin.end_array() ) { jsin.start_array(); int i = jsin.get_int(); int j = jsin.get_int(); // TODO: jsin should support returning an id like jsin.get_id<trap>() sm->trp[i][j] = trap_str_id( jsin.get_string() ); jsin.end_array(); } } else if( submap_member_name == "fields" ) { jsin.start_array(); while( !jsin.end_array() ) { // Coordinates loop int i = jsin.get_int(); int j = jsin.get_int(); jsin.start_array(); while( !jsin.end_array() ) { int type = jsin.get_int(); int density = jsin.get_int(); int age = jsin.get_int(); if (sm->fld[i][j].findField(field_id(type)) == NULL) { sm->field_count++; } sm->fld[i][j].addField(field_id(type), density, age); } } } else if( submap_member_name == "graffiti" ) { jsin.start_array(); while( !jsin.end_array() ) { jsin.start_array(); int i = jsin.get_int(); int j = jsin.get_int(); sm->set_graffiti( i, j, jsin.get_string() ); jsin.end_array(); } } else if(submap_member_name == "cosmetics") { jsin.start_array(); while (!jsin.end_array()) { jsin.start_array(); int i = jsin.get_int(); int j = jsin.get_int(); jsin.read(sm->cosmetics[i][j]); jsin.end_array(); } } else if( submap_member_name == "spawns" ) { jsin.start_array(); while( !jsin.end_array() ) { jsin.start_array(); const mtype_id type = mtype_id( jsin.get_string() ); // TODO: json should know how to read an string_id int count = jsin.get_int(); int i = jsin.get_int(); int j = jsin.get_int(); int faction_id = jsin.get_int(); int mission_id = jsin.get_int(); bool friendly = jsin.get_bool(); std::string name = jsin.get_string(); jsin.end_array(); spawn_point tmp( type, count, i, j, faction_id, mission_id, friendly, name ); sm->spawns.push_back( tmp ); } } else if( submap_member_name == "vehicles" ) { jsin.start_array(); while( !jsin.end_array() ) { vehicle *tmp = new vehicle(); jsin.read( *tmp ); sm->vehicles.push_back( tmp ); } } else if( submap_member_name == "computers" ) { std::string computer_data = jsin.get_string(); sm->comp.load_data( computer_data ); } else if( submap_member_name == "camp" ) { std::string camp_data = jsin.get_string(); sm->camp.load_data( camp_data ); } else { jsin.skip_value(); } } if( !add_submap( submap_coordinates, sm ) ) { debugmsg( "submap %d,%d,%d was already loaded", submap_coordinates.x, submap_coordinates.y, submap_coordinates.z ); } } if( submaps.count( p ) == 0 ) { debugmsg("file %s did not contain the expected submap %d,%d,%d", quad_path.str().c_str(), p.x, p.y, p.z); return NULL; } return submaps[ p ]; }
bool mapbuffer::add_submap( int x, int y, int z, submap *sm ) { return add_submap( tripoint( x, y, z ), sm ); }
// We're reading in way too many entities here to mess around with creating sub-objects and // seeking around in them, so we're using the json streaming API. submap *mapbuffer::unserialize_submaps( const tripoint &p ) { // Map the tripoint to the submap quad that stores it. const tripoint om_addr = overmapbuffer::sm_to_omt_copy( p ); const tripoint segment_addr = overmapbuffer::omt_to_seg_copy( om_addr ); std::stringstream quad_path; quad_path << world_generator->active_world->world_path << "/maps/" << segment_addr.x << "." << segment_addr.y << "." << segment_addr.z << "/" << om_addr.x << "." << om_addr.y << "." << om_addr.z << ".map"; std::ifstream fin( quad_path.str().c_str() ); if( !fin.is_open() ) { // If it doesn't exist, trigger generating it. return NULL; } JsonIn jsin( fin ); jsin.start_array(); while( !jsin.end_array() ) { std::unique_ptr<submap> sm(new submap()); tripoint submap_coordinates; jsin.start_object(); while( !jsin.end_object() ) { std::string submap_member_name = jsin.get_member_name(); if( submap_member_name == "version" ) { // We aren't using the version number for anything at the moment. jsin.skip_value(); } else if( submap_member_name == "coordinates" ) { jsin.start_array(); int locx = jsin.get_int(); int locy = jsin.get_int(); int locz = jsin.get_int(); jsin.end_array(); submap_coordinates = tripoint( locx, locy, locz ); } else if( submap_member_name == "turn_last_touched" ) { sm->turn_last_touched = jsin.get_int(); } else if( submap_member_name == "temperature" ) { sm->temperature = jsin.get_int(); } else if( submap_member_name == "terrain" ) { // TODO: try block around this to error out if we come up short? jsin.start_array(); for( int j = 0; j < SEEY; j++ ) { for( int i = 0; i < SEEX; i++ ) { sm->ter[i][j] = termap[ jsin.get_string() ].loadid; } } jsin.end_array(); } else if( submap_member_name == "radiation" ) { int rad_cell = 0; jsin.start_array(); while( !jsin.end_array() ) { int rad_strength = jsin.get_int(); int rad_num = jsin.get_int(); for( int i = 0; i < rad_num; ++i ) { // A little array trick here, assign to it as a 1D array. // If it's not in bounds we're kinda hosed anyway. sm->set_radiation(0, rad_cell, rad_strength); rad_cell++; } } } else if( submap_member_name == "furniture" ) { jsin.start_array(); while( !jsin.end_array() ) { jsin.start_array(); int i = jsin.get_int(); int j = jsin.get_int(); sm->frn[i][j] = furnmap[ jsin.get_string() ].loadid; jsin.end_array(); } } else if( submap_member_name == "items" ) { jsin.start_array(); while( !jsin.end_array() ) { int i = jsin.get_int(); int j = jsin.get_int(); jsin.start_array(); while( !jsin.end_array() ) { item tmp; jsin.read( tmp ); sm->itm[i][j].push_back( tmp ); } } } else if( submap_member_name == "traps" ) { jsin.start_array(); while( !jsin.end_array() ) { jsin.start_array(); int i = jsin.get_int(); int j = jsin.get_int(); sm->trp[i][j] = trapmap[ jsin.get_string() ]; jsin.end_array(); } } else if( submap_member_name == "fields" ) { jsin.start_array(); while( !jsin.end_array() ) { // Coordinates loop int i = jsin.get_int(); int j = jsin.get_int(); jsin.start_array(); while( !jsin.end_array() ) { int type = jsin.get_int(); int density = jsin.get_int(); int age = jsin.get_int(); if (sm->fld[i][j].findField(field_id(type)) == NULL) { sm->field_count++; } sm->fld[i][j].addField(field_id(type), density, age); } } } else if( submap_member_name == "griffiti" ) { jsin.start_array(); while( !jsin.end_array() ) { jsin.start_array(); int i = jsin.get_int(); int j = jsin.get_int(); sm->set_graffiti(i, j, graffiti( jsin.get_string() )); jsin.end_array(); } } else if(submap_member_name == "cosmetics") { jsin.start_array(); while (!jsin.end_array()) { jsin.start_array(); int i = jsin.get_int(); int j = jsin.get_int(); jsin.read(sm->cosmetics[i][j]); jsin.end_array(); } } else if( submap_member_name == "spawns" ) { jsin.start_array(); while( !jsin.end_array() ) { jsin.start_array(); std::string type = jsin.get_string(); int count = jsin.get_int(); int i = jsin.get_int(); int j = jsin.get_int(); int faction_id = jsin.get_int(); int mission_id = jsin.get_int(); bool friendly = jsin.get_bool(); std::string name = jsin.get_string(); jsin.end_array(); spawn_point tmp( type, count, i, j, faction_id, mission_id, friendly, name ); sm->spawns.push_back( tmp ); } } else if( submap_member_name == "vehicles" ) { jsin.start_array(); while( !jsin.end_array() ) { vehicle *tmp = new vehicle(); jsin.read( *tmp ); sm->vehicles.push_back( tmp ); } } else if( submap_member_name == "computers" ) { std::string computer_data = jsin.get_string(); sm->comp.load_data( computer_data ); } else if( submap_member_name == "camp" ) { std::string camp_data = jsin.get_string(); sm->camp.load_data( camp_data ); } else { jsin.skip_value(); } } if( !add_submap( submap_coordinates, sm ) ) { debugmsg( "submap %d,%d,%d was alread loaded", submap_coordinates.x, submap_coordinates.y, submap_coordinates.z ); } } if( submaps.count( p ) == 0 ) { debugmsg("file %s did not contain the expected submap %d,%d,%d", quad_path.str().c_str(), p.x, p.y, p.z); return NULL; } return submaps[ p ]; }
void map::generate_lightmap( const int zlev ) { auto &map_cache = get_cache( zlev ); auto &lm = map_cache.lm; auto &sm = map_cache.sm; auto &outside_cache = map_cache.outside_cache; std::memset(lm, 0, sizeof(lm)); std::memset(sm, 0, sizeof(sm)); /* Bulk light sources wastefully cast rays into neighbors; a burning hospital can produce significant slowdown, so for stuff like fire and lava: * Step 1: Store the position and luminance in buffer via add_light_source, for efficient checking of neighbors. * Step 2: After everything else, iterate buffer and apply_light_source only in non-redundant directions * Step 3: ???? * Step 4: Profit! */ auto &light_source_buffer = map_cache.light_source_buffer; std::memset(light_source_buffer, 0, sizeof(light_source_buffer)); constexpr int dir_x[] = { 0, -1 , 1, 0 }; // [0] constexpr int dir_y[] = { -1, 0 , 0, 1 }; // [1][X][2] constexpr int dir_d[] = { 90, 0, 180, 270 }; // [3] const float natural_light = g->natural_light_level( zlev ); const float inside_light = (natural_light > LIGHT_SOURCE_BRIGHT) ? LIGHT_AMBIENT_LOW + 1.0 : LIGHT_AMBIENT_MINIMAL; // Apply sunlight, first light source so just assign for( int sx = 0; sx < LIGHTMAP_CACHE_X; ++sx ) { for( int sy = 0; sy < LIGHTMAP_CACHE_Y; ++sy ) { // In bright light indoor light exists to some degree if( !outside_cache[sx][sy] ) { lm[sx][sy] = inside_light; } else { lm[sx][sy] = natural_light; } } } apply_character_light( g->u ); for( auto &n : g->active_npc ) { apply_character_light( *n ); } // Traverse the submaps in order for (int smx = 0; smx < my_MAPSIZE; ++smx) { for (int smy = 0; smy < my_MAPSIZE; ++smy) { auto const cur_submap = get_submap_at_grid( smx, smy, zlev ); for (int sx = 0; sx < SEEX; ++sx) { for (int sy = 0; sy < SEEY; ++sy) { const int x = sx + smx * SEEX; const int y = sy + smy * SEEY; const tripoint p( x, y, zlev ); // Project light into any openings into buildings. if (natural_light > LIGHT_SOURCE_BRIGHT && !outside_cache[p.x][p.y]) { // Apply light sources for external/internal divide for(int i = 0; i < 4; ++i) { if (INBOUNDS(p.x + dir_x[i], p.y + dir_y[i]) && outside_cache[p.x + dir_x[i]][p.y + dir_y[i]]) { lm[p.x][p.y] = natural_light; if (light_transparency( p ) > LIGHT_TRANSPARENCY_SOLID) { apply_directional_light( p, dir_d[i], natural_light ); } } } } if( cur_submap->lum[sx][sy] && has_items( p ) ) { auto items = i_at( p ); add_light_from_items( p, items.begin(), items.end() ); } const ter_id terrain = cur_submap->ter[sx][sy]; if (terrain == t_lava) { add_light_source( p, 50 ); } else if (terrain == t_console) { add_light_source( p, 10 ); } else if (terrain == t_utility_light) { add_light_source( p, 240 ); } for( auto &fld : cur_submap->fld[sx][sy] ) { const field_entry *cur = &fld.second; // TODO: [lightmap] Attach light brightness to fields switch(cur->getFieldType()) { case fd_fire: if (3 == cur->getFieldDensity()) { add_light_source( p, 160 ); } else if (2 == cur->getFieldDensity()) { add_light_source( p, 60 ); } else { add_light_source( p, 20 ); } break; case fd_fire_vent: case fd_flame_burst: add_light_source( p, 20 ); break; case fd_electricity: case fd_plasma: if (3 == cur->getFieldDensity()) { add_light_source( p, 20 ); } else if (2 == cur->getFieldDensity()) { add_light_source( p, 4 ); } else { // Kinda a hack as the square will still get marked. apply_light_source( p, LIGHT_SOURCE_LOCAL ); } break; case fd_incendiary: if (3 == cur->getFieldDensity()) { add_light_source( p, 160 ); } else if (2 == cur->getFieldDensity()) { add_light_source( p, 60 ); } else { add_light_source( p, 20 ); } break; case fd_laser: apply_light_source( p, 4 ); break; case fd_spotlight: add_light_source( p, 80 ); break; case fd_dazzling: add_light_source( p, 5 ); break; default: //Suppress warnings break; } } } } } } for (size_t i = 0; i < g->num_zombies(); ++i) { auto &critter = g->zombie(i); if(critter.is_hallucination()) { continue; } const tripoint &mp = critter.pos(); if( inbounds( mp ) ) { if (critter.has_effect( effect_onfire)) { apply_light_source( mp, 8 ); } // TODO: [lightmap] Attach natural light brightness to creatures // TODO: [lightmap] Allow creatures to have light attacks (ie: eyebot) // TODO: [lightmap] Allow creatures to have facing and arc lights if (critter.type->luminance > 0) { apply_light_source( mp, critter.type->luminance ); } } } // Apply any vehicle light sources VehicleList vehs = get_vehicles(); for( auto &vv : vehs ) { vehicle *v = vv.v; if(v->lights_on) { int dir = v->face.dir(); float veh_luminance = 0.0; float iteration = 1.0; std::vector<int> light_indices = v->all_parts_with_feature(VPFLAG_CONE_LIGHT); for( auto &light_indice : light_indices ) { veh_luminance += ( v->part_info( light_indice ).bonus / iteration ); iteration = iteration * 1.1; } if (veh_luminance > LL_LIT) { for( auto &light_indice : light_indices ) { tripoint pp = tripoint( vv.x, vv.y, vv.z ) + v->parts[light_indice].precalc[0]; if( inbounds( pp ) ) { add_light_source( pp, SQRT_2 ); // Add a little surrounding light apply_light_arc( pp, dir + v->parts[light_indice].direction, veh_luminance, 45 ); } } } } if(v->overhead_lights_on) { std::vector<int> light_indices = v->all_parts_with_feature(VPFLAG_CIRCLE_LIGHT); for( auto &light_indice : light_indices ) { if( ( calendar::turn % 2 && v->part_info( light_indice ).has_flag( VPFLAG_ODDTURN ) ) || ( !( calendar::turn % 2 ) && v->part_info( light_indice ).has_flag( VPFLAG_EVENTURN ) ) || ( !v->part_info( light_indice ).has_flag( VPFLAG_EVENTURN ) && !v->part_info( light_indice ).has_flag( VPFLAG_ODDTURN ) ) ) { tripoint pp = tripoint( vv.x, vv.y, vv.z ) + v->parts[light_indice].precalc[0]; if(inbounds( pp )) { add_light_source( pp, v->part_info( light_indice ).bonus ); } } } } // why reinvent the [lightmap] wheel if(v->dome_lights_on) { std::vector<int> light_indices = v->all_parts_with_feature(VPFLAG_DOME_LIGHT); for( auto &light_indice : light_indices ) { tripoint pp = tripoint( vv.x, vv.y, vv.z ) + v->parts[light_indice].precalc[0]; if( inbounds( pp )) { add_light_source( pp, v->part_info( light_indice ).bonus ); } } } if(v->aisle_lights_on) { std::vector<int> light_indices = v->all_parts_with_feature(VPFLAG_AISLE_LIGHT); for( auto &light_indice : light_indices ) { tripoint pp = tripoint( vv.x, vv.y, vv.z ) + v->parts[light_indice].precalc[0]; if( inbounds( pp )) { add_light_source( pp, v->part_info( light_indice ).bonus ); } } } if(v->has_atomic_lights) { // atomic light is always on std::vector<int> light_indices = v->all_parts_with_feature(VPFLAG_ATOMIC_LIGHT); for( auto &light_indice : light_indices ) { tripoint pp = tripoint( vv.x, vv.y, vv.z ) + v->parts[light_indice].precalc[0]; if(inbounds( pp )) { add_light_source( pp, v->part_info( light_indice ).bonus ); } } } for( size_t p = 0; p < v->parts.size(); ++p ) { tripoint pp = tripoint( vv.x, vv.y, vv.z ) + v->parts[p].precalc[0]; if( !inbounds( pp ) ) { continue; } if( v->part_flag( p, VPFLAG_CARGO ) && !v->part_flag( p, "COVERED" ) ) { add_light_from_items( pp, v->get_items(p).begin(), v->get_items(p).end() ); } } } /* Now that we have position and intensity of all bulk light sources, apply_ them This may seem like extra work, but take a 12x12 raging inferno: unbuffered: (12^2)*(160*4) = apply_light_ray x 92160 buffered: (12*4)*(160) = apply_light_ray x 7680 */ const tripoint cache_start( 0, 0, zlev ); const tripoint cache_end( LIGHTMAP_CACHE_X, LIGHTMAP_CACHE_Y, zlev ); for( const tripoint &p : points_in_rectangle( cache_start, cache_end ) ) { if( light_source_buffer[p.x][p.y] > 0.0 ) { apply_light_source( p, light_source_buffer[p.x][p.y] ); } } if (g->u.has_active_bionic("bio_night") ) { for( const tripoint &p : points_in_rectangle( cache_start, cache_end ) ) { if( rl_dist( p, g->u.pos() ) < 15 ) { lm[p.x][p.y] = LIGHT_AMBIENT_MINIMAL; } } } }
void mutation_branch::load( JsonObject &jsobj ) { const std::string id = jsobj.get_string( "id" ); mutation_branch &new_mut = mutation_data[id]; JsonArray jsarr; new_mut.name = _(jsobj.get_string("name").c_str()); new_mut.description = _(jsobj.get_string("description").c_str()); new_mut.points = jsobj.get_int("points"); new_mut.visibility = jsobj.get_int("visibility", 0); new_mut.ugliness = jsobj.get_int("ugliness", 0); new_mut.startingtrait = jsobj.get_bool("starting_trait", false); new_mut.mixed_effect = jsobj.get_bool("mixed_effect", false); new_mut.activated = jsobj.get_bool("active", false); new_mut.starts_active = jsobj.get_bool("starts_active", false); new_mut.destroys_gear = jsobj.get_bool("destroys_gear", false); new_mut.allow_soft_gear = jsobj.get_bool("allow_soft_gear", false); new_mut.cost = jsobj.get_int("cost", 0); new_mut.cooldown = jsobj.get_int("time",0); new_mut.hunger = jsobj.get_bool("hunger",false); new_mut.thirst = jsobj.get_bool("thirst",false); new_mut.fatigue = jsobj.get_bool("fatigue",false); new_mut.valid = jsobj.get_bool("valid", true); new_mut.purifiable = jsobj.get_bool("purifiable", true); for( auto & s : jsobj.get_string_array( "initial_ma_styles" ) ) { new_mut.initial_ma_styles.push_back( matype_id( s ) ); } JsonArray bodytemp_array = jsobj.get_array( "bodytemp_modifiers" ); if( bodytemp_array.has_more() ) { new_mut.bodytemp_min = bodytemp_array.get_int( 0 ); new_mut.bodytemp_max = bodytemp_array.get_int( 1 ); } new_mut.bodytemp_sleep = jsobj.get_int( "bodytemp_sleep", 0 ); new_mut.threshold = jsobj.get_bool("threshold", false); new_mut.profession = jsobj.get_bool("profession", false); auto vr = jsobj.get_array( "vitamin_rates" ); while( vr.has_more() ) { auto pair = vr.next_array(); new_mut.vitamin_rates[ vitamin_id( pair.get_string( 0 ) ) ] = pair.get_int( 1 ); } load_mutation_mods(jsobj, "passive_mods", new_mut.mods); /* Not currently supported due to inability to save active mutation state load_mutation_mods(jsobj, "active_mods", new_mut.mods); */ new_mut.prereqs = jsobj.get_string_array( "prereqs" ); // Helps to be able to have a trait require more than one other trait // (Individual prereq-lists are "OR", not "AND".) // Traits shoud NOT appear in both lists for a given mutation, unless // you want that trait to satisfy both requirements. // These are additional to the first list. new_mut.prereqs2 = jsobj.get_string_array( "prereqs2" ); // Dedicated-purpose prereq slot for Threshold mutations // Stuff like Huge might fit in more than one mutcat post-threshold, so yeah new_mut.threshreq = jsobj.get_string_array( "threshreq" ); new_mut.cancels = jsobj.get_string_array( "cancels" ); new_mut.replacements = jsobj.get_string_array( "changes_to" ); new_mut.additions = jsobj.get_string_array( "leads_to" ); new_mut.flags = jsobj.get_tags( "flags" ); jsarr = jsobj.get_array("category"); while (jsarr.has_more()) { std::string s = jsarr.next_string(); new_mut.category.push_back(s); mutations_category[s].push_back(id); } jsarr = jsobj.get_array("wet_protection"); while (jsarr.has_more()) { JsonObject jo = jsarr.next_object(); std::string part_id = jo.get_string("part"); int ignored = jo.get_int("ignored", 0); int neutral = jo.get_int("neutral", 0); int good = jo.get_int("good", 0); tripoint protect = tripoint(ignored, neutral, good); new_mut.protection[get_body_part_token( part_id )] = protect; } jsarr = jsobj.get_array("encumbrance_always"); while (jsarr.has_more()) { JsonArray jo = jsarr.next_array(); std::string part_id = jo.next_string(); int enc = jo.next_int(); new_mut.encumbrance_always[get_body_part_token( part_id )] = enc; } jsarr = jsobj.get_array("encumbrance_covered"); while (jsarr.has_more()) { JsonArray jo = jsarr.next_array(); std::string part_id = jo.next_string(); int enc = jo.next_int(); new_mut.encumbrance_covered[get_body_part_token( part_id )] = enc; } jsarr = jsobj.get_array("restricts_gear"); while( jsarr.has_more() ) { new_mut.restricts_gear.insert( get_body_part_token( jsarr.next_string() ) ); } jsarr = jsobj.get_array( "armor" ); while( jsarr.has_more() ) { JsonObject jo = jsarr.next_object(); auto parts = jo.get_tags( "parts" ); std::set<body_part> bps; for( const std::string &part_string : parts ) { if( part_string == "ALL" ) { // Shorthand, since many muts protect whole body for( size_t i = 0; i < num_bp; i++ ) { bps.insert( static_cast<body_part>( i ) ); } } else { bps.insert( get_body_part_token( part_string ) ); } } resistances res = load_resistances_instance( jo ); for( body_part bp : bps ) { new_mut.armor[ bp ] = res; } } }
void tutorial_game::post_action(action_id act) { switch (act) { case ACTION_RELOAD: if (g->u.weapon.is_gun() && !tutorials_seen[LESSON_GUN_FIRE]) { g->summon_mon("mon_zombie", tripoint(g->u.posx(), g->u.posy() - 6, g->u.posz())); g->summon_mon("mon_zombie", tripoint(g->u.posx() + 2, g->u.posy() - 5, g->u.posz())); g->summon_mon("mon_zombie", tripoint(g->u.posx() - 2, g->u.posy() - 5, g->u.posz())); add_message(LESSON_GUN_FIRE); } break; case ACTION_OPEN: add_message(LESSON_CLOSE); break; case ACTION_CLOSE: add_message(LESSON_SMASH); break; case ACTION_USE: if (g->u.has_amount("grenade_act", 1)) add_message(LESSON_ACT_GRENADE); for (int x = g->u.posx() - 1; x <= g->u.posx() + 1; x++) { for (int y = g->u.posy() - 1; y <= g->u.posy() + 1; y++) { if (g->m.tr_at({x, y, g->u.posz()}).id == trap_str_id( "tr_bubblewrap" )) add_message(LESSON_ACT_BUBBLEWRAP); } } break; case ACTION_EAT: if (g->u.last_item == "codeine") add_message(LESSON_TOOK_PAINKILLER); else if (g->u.last_item == "cig") add_message(LESSON_TOOK_CIG); else if (g->u.last_item == "water") add_message(LESSON_DRANK_WATER); break; case ACTION_WEAR: { item it( g->u.last_item, 0 ); if (it.is_armor()) { if (it.get_coverage() >= 2 || it.get_thickness() >= 2) add_message(LESSON_WORE_ARMOR); if (it.get_storage() >= 20) add_message(LESSON_WORE_STORAGE); if (it.get_env_resist() >= 2) add_message(LESSON_WORE_MASK); } } break; case ACTION_WIELD: if (g->u.weapon.is_gun()) add_message(LESSON_GUN_LOAD); break; case ACTION_EXAMINE: add_message(LESSON_INTERACT); // Fall through to... case ACTION_PICKUP: { item it( g->u.last_item, 0 ); if (it.is_armor()) add_message(LESSON_GOT_ARMOR); else if (it.is_gun()) add_message(LESSON_GOT_GUN); else if (it.is_ammo()) add_message(LESSON_GOT_AMMO); else if (it.is_tool()) add_message(LESSON_GOT_TOOL); else if (it.is_food()) add_message(LESSON_GOT_FOOD); else if (it.is_weap()) add_message(LESSON_GOT_WEAPON); } break; default: //TODO: add more actions here break; } }
void game::draw_custom_explosion( const tripoint &, const std::map<tripoint, nc_color> &all_area ) { if( test_mode ) { return; // avoid segfault from null tilecontext in tests } constexpr explosion_neighbors all_neighbors = N_NORTH | N_SOUTH | N_WEST | N_EAST; // We will "shell" the explosion area // Each phase will strip a single layer of points // A layer contains all points that have less than 4 neighbors in cardinal directions // Layers will first be generated, then drawn in inverse order // Start by getting rid of everything except current z-level std::map<tripoint, explosion_tile> neighbors; #if defined(TILES) if( !use_tiles ) { for( const auto &pr : all_area ) { const tripoint relative_point = relative_view_pos( u, pr.first ); if( relative_point.z == 0 ) { neighbors[pr.first] = explosion_tile{ N_NO_NEIGHBORS, pr.second }; } } } else { // In tiles mode, the coordinates have to be absolute const tripoint view_center = relative_view_pos( u, u.pos() ); for( const auto &pr : all_area ) { // Relative point is only used for z level check const tripoint relative_point = relative_view_pos( u, pr.first ); if( relative_point.z == view_center.z ) { neighbors[pr.first] = explosion_tile{ N_NO_NEIGHBORS, pr.second }; } } } #else for( const auto &pr : all_area ) { const tripoint relative_point = relative_view_pos( u, pr.first ); if( relative_point.z == 0 ) { neighbors[pr.first] = explosion_tile{ N_NO_NEIGHBORS, pr.second }; } } #endif // Searches for a neighbor, sets the neighborhood flag on current point and on the neighbor const auto set_neighbors = [&]( const tripoint & pos, explosion_neighbors & ngh, explosion_neighbors here, explosion_neighbors there ) { if( ( ngh & here ) == N_NO_NEIGHBORS ) { auto other = neighbors.find( pos ); if( other != neighbors.end() ) { ngh = ngh | here; other->second.neighborhood = other->second.neighborhood | there; } } }; // If the point we are about to remove has a neighbor in a given direction // unset that neighbor's flag that our current point is its neighbor const auto unset_neighbor = [&]( const tripoint & pos, const explosion_neighbors ngh, explosion_neighbors here, explosion_neighbors there ) { if( ( ngh & here ) != N_NO_NEIGHBORS ) { auto other = neighbors.find( pos ); if( other != neighbors.end() ) { other->second.neighborhood = ( other->second.neighborhood | there ) ^ there; } } }; // Find all neighborhoods for( auto &pr : neighbors ) { const tripoint &pt = pr.first; explosion_neighbors &ngh = pr.second.neighborhood; set_neighbors( tripoint( pt.x - 1, pt.y, pt.z ), ngh, N_WEST, N_EAST ); set_neighbors( tripoint( pt.x + 1, pt.y, pt.z ), ngh, N_EAST, N_WEST ); set_neighbors( tripoint( pt.x, pt.y - 1, pt.z ), ngh, N_NORTH, N_SOUTH ); set_neighbors( tripoint( pt.x, pt.y + 1, pt.z ), ngh, N_SOUTH, N_NORTH ); } // We need to save the layers because we will draw them in reverse order std::list< std::map<tripoint, explosion_tile> > layers; while( !neighbors.empty() ) { std::map<tripoint, explosion_tile> layer; bool changed = false; // Find a layer that can be drawn for( const auto &pr : neighbors ) { if( pr.second.neighborhood != all_neighbors ) { changed = true; layer.insert( pr ); } } if( !changed ) { // An error, but a minor one - let it slide return; } // Remove the layer from the area to process for( const auto &pr : layer ) { const tripoint &pt = pr.first; const explosion_neighbors ngh = pr.second.neighborhood; unset_neighbor( tripoint( pt.x - 1, pt.y, pt.z ), ngh, N_WEST, N_EAST ); unset_neighbor( tripoint( pt.x + 1, pt.y, pt.z ), ngh, N_EAST, N_WEST ); unset_neighbor( tripoint( pt.x, pt.y - 1, pt.z ), ngh, N_NORTH, N_SOUTH ); unset_neighbor( tripoint( pt.x, pt.y + 1, pt.z ), ngh, N_SOUTH, N_NORTH ); neighbors.erase( pr.first ); } layers.push_front( std::move( layer ) ); } #if defined(TILES) if( !use_tiles ) { draw_custom_explosion_curses( *this, layers ); return; } explosion_animation anim; // We need to draw all explosions up to now std::map<tripoint, explosion_tile> combined_layer; for( const auto &layer : layers ) { combined_layer.insert( layer.begin(), layer.end() ); tilecontext->init_custom_explosion_layer( combined_layer ); if( is_layer_visible( layer ) ) { anim.progress(); } } tilecontext->void_custom_explosion(); #else draw_custom_explosion_curses( *this, layers ); #endif }
bool Creature::sees( const int tx, const int ty ) const { return sees( tripoint( tx, ty, posz() ) ); }
bool mapbuffer::add_submap( int x, int y, int z, std::unique_ptr<submap> &sm ) { return add_submap( tripoint( x, y, z ), sm ); }
bool Creature::sees( const point t ) const { return sees( tripoint( t, posz() ) ); }
submap *mapbuffer::lookup_submap(int x, int y, int z) { return lookup_submap( tripoint( x, y, z ) ); }
void monster::plan( const mfactions &factions ) { // Bots are more intelligent than most living stuff bool smart_planning = has_flag( MF_PRIORITIZE_TARGETS ); Creature *target = nullptr; // 8.6f is rating for tank drone 60 tiles away, moose 16 or boomer 33 float dist = !smart_planning ? 1000 : 8.6f; bool fleeing = false; bool docile = friendly != 0 && has_effect( effect_docile ); bool angers_hostile_weak = type->anger.find( MTRIG_HOSTILE_WEAK ) != type->anger.end(); int angers_hostile_near = ( type->anger.find( MTRIG_HOSTILE_CLOSE ) != type->anger.end() ) ? 5 : 0; int fears_hostile_near = ( type->fear.find( MTRIG_HOSTILE_CLOSE ) != type->fear.end() ) ? 5 : 0; bool group_morale = has_flag( MF_GROUP_MORALE ) && morale < type->morale; bool swarms = has_flag( MF_SWARMS ); auto mood = attitude(); // If we can see the player, move toward them or flee. if( friendly == 0 && sees( g->u ) ) { dist = rate_target( g->u, dist, smart_planning ); fleeing = fleeing || is_fleeing( g->u ); target = &g->u; if( dist <= 5 ) { anger += angers_hostile_near; morale -= fears_hostile_near; } } else if( friendly != 0 && !docile ) { // Target unfriendly monsters, only if we aren't interacting with the player. for( int i = 0, numz = g->num_zombies(); i < numz; i++ ) { monster &tmp = g->zombie( i ); if( tmp.friendly == 0 ) { float rating = rate_target( tmp, dist, smart_planning ); if( rating < dist ) { target = &tmp; dist = rating; } } } } if( docile ) { if( friendly != 0 && target != nullptr ) { set_dest( target->pos() ); } return; } for( size_t i = 0; i < g->active_npc.size(); i++ ) { npc &who = *g->active_npc[i]; auto faction_att = faction.obj().attitude( who.get_monster_faction() ); if( faction_att == MFA_NEUTRAL || faction_att == MFA_FRIENDLY ) { continue; } float rating = rate_target( who, dist, smart_planning ); bool fleeing_from = is_fleeing( who ); // Switch targets if closer and hostile or scarier than current target if( ( rating < dist && fleeing ) || ( rating < dist && attitude( &who ) == MATT_ATTACK ) || ( !fleeing && fleeing_from ) ) { target = &who; dist = rating; } fleeing = fleeing || fleeing_from; if( rating <= 5 ) { anger += angers_hostile_near; morale -= fears_hostile_near; } } fleeing = fleeing || ( mood == MATT_FLEE ); if( friendly == 0 ) { for( const auto &fac : factions ) { auto faction_att = faction.obj().attitude( fac.first ); if( faction_att == MFA_NEUTRAL || faction_att == MFA_FRIENDLY ) { continue; } for( int i : fac.second ) { // mon indices monster &mon = g->zombie( i ); float rating = rate_target( mon, dist, smart_planning ); if( rating < dist ) { target = &mon; dist = rating; } if( rating <= 5 ) { anger += angers_hostile_near; morale -= fears_hostile_near; } } } } // Friendly monsters here // Avoid for hordes of same-faction stuff or it could get expensive const auto actual_faction = friendly == 0 ? faction : mfaction_str_id( "player" ); auto const &myfaction_iter = factions.find( actual_faction ); if( myfaction_iter == factions.end() ) { DebugLog( D_ERROR, D_GAME ) << disp_name() << " tried to find faction " << actual_faction.id().str() << " which wasn't loaded in game::monmove"; swarms = false; group_morale = false; } swarms = swarms && target == nullptr; // Only swarm if we have no target if( group_morale || swarms ) { for( const int i : myfaction_iter->second ) { monster &mon = g->zombie( i ); float rating = rate_target( mon, dist, smart_planning ); if( group_morale && rating <= 10 ) { morale += 10 - rating; } if( swarms ) { if( rating < 5 ) { // Too crowded here wander_pos.x = posx() * rng( 1, 3 ) - mon.posx(); wander_pos.y = posy() * rng( 1, 3 ) - mon.posy(); wandf = 2; target = nullptr; // Swarm to the furthest ally you can see } else if( rating < INT_MAX && rating > dist && wandf <= 0 ) { target = &mon; dist = rating; } } } } if( target != nullptr ) { tripoint dest = target->pos(); auto att_to_target = attitude_to( *target ); if( att_to_target == Attitude::A_HOSTILE && !fleeing ) { set_dest( dest ); } else if( fleeing ) { set_dest( tripoint( posx() * 2 - dest.x, posy() * 2 - dest.y, posz() ) ); } if( angers_hostile_weak && att_to_target != Attitude::A_FRIENDLY ) { int hp_per = target->hp_percentage(); if( hp_per <= 70 ) { anger += 10 - int( hp_per / 10 ); } } } else if( friendly > 0 && one_in( 3 ) ) { // Grow restless with no targets friendly--; } else if( friendly < 0 && sees( g->u ) ) { if( rl_dist( pos(), g->u.pos() ) > 2 ) { set_dest( g->u.pos() ); } else { unset_dest(); } } }
void tutorial_game::post_action( action_id act ) { switch( act ) { case ACTION_RELOAD: if( g->u.weapon.is_gun() && !tutorials_seen[LESSON_GUN_FIRE] ) { g->summon_mon( mon_zombie, tripoint( g->u.posx(), g->u.posy() - 6, g->u.posz() ) ); g->summon_mon( mon_zombie, tripoint( g->u.posx() + 2, g->u.posy() - 5, g->u.posz() ) ); g->summon_mon( mon_zombie, tripoint( g->u.posx() - 2, g->u.posy() - 5, g->u.posz() ) ); add_message( LESSON_GUN_FIRE ); } break; case ACTION_OPEN: add_message( LESSON_CLOSE ); break; case ACTION_CLOSE: add_message( LESSON_SMASH ); break; case ACTION_USE: if( g->u.has_amount( "grenade_act", 1 ) ) { add_message( LESSON_ACT_GRENADE ); } for( const tripoint &dest : g->m.points_in_radius( g->u.pos(), 1 ) ) { if( g->m.tr_at( dest ).id == trap_str_id( "tr_bubblewrap" ) ) { add_message( LESSON_ACT_BUBBLEWRAP ); } } break; case ACTION_EAT: if( g->u.last_item == "codeine" ) { add_message( LESSON_TOOK_PAINKILLER ); } else if( g->u.last_item == "cig" ) { add_message( LESSON_TOOK_CIG ); } else if( g->u.last_item == "water" ) { add_message( LESSON_DRANK_WATER ); } break; case ACTION_WEAR: { item it( g->u.last_item, 0 ); if( it.is_armor() ) { if( it.get_coverage() >= 2 || it.get_thickness() >= 2 ) { add_message( LESSON_WORE_ARMOR ); } if( it.get_storage() >= units::from_liter( 5 ) ) { add_message( LESSON_WORE_STORAGE ); } if( it.get_env_resist() >= 2 ) { add_message( LESSON_WORE_MASK ); } } } break; case ACTION_WIELD: if( g->u.weapon.is_gun() ) { add_message( LESSON_GUN_LOAD ); } break; case ACTION_EXAMINE: add_message( LESSON_INTERACT ); /* fallthrough */ case ACTION_PICKUP: { item it( g->u.last_item, 0 ); if( it.is_armor() ) { add_message( LESSON_GOT_ARMOR ); } else if( it.is_gun() ) { add_message( LESSON_GOT_GUN ); } else if( it.is_ammo() ) { add_message( LESSON_GOT_AMMO ); } else if( it.is_tool() ) { add_message( LESSON_GOT_TOOL ); } else if( it.is_food() ) { add_message( LESSON_GOT_FOOD ); } else if( it.is_melee() ) { add_message( LESSON_GOT_WEAPON ); } } break; default: //TODO: add more actions here break; } }
tripoint overmapbuffer::sm_to_ms_copy(const tripoint& p) { return tripoint(p.x * SEEX, p.y * SEEY, p.z); }
#include "catch/catch.hpp" #include "game.h" #include "map.h" #include "vehicle.h" #include "veh_type.h" #include "player.h" TEST_CASE( "destroy_grabbed_vehicle_section" ) { GIVEN( "A vehicle grabbed by the player" ) { tripoint test_origin( 60, 60, 0 ); g->u.setpos( test_origin ); tripoint vehicle_origin = test_origin + tripoint( 1, 1, 0 ); vehicle *veh_ptr = g->m.add_vehicle( vproto_id( "bicycle" ), vehicle_origin, -90 ); REQUIRE( veh_ptr != nullptr ); tripoint grab_point = test_origin + tripoint( 1, 0, 0 ); g->u.grab_type = OBJECT_VEHICLE; g->u.grab_point = grab_point; WHEN( "The vehicle section grabbed by the player is destroyed" ) { g->m.destroy( grab_point ); THEN( "The player's grab is released" ) { CHECK( g->u.grab_type == OBJECT_NONE ); CHECK( g->u.grab_point == tripoint_zero ); } } } }
bool fungal_effects::spread_fungus( const tripoint &p ) { int growth = 1; for( int i = p.x - 1; i <= p.x + 1; i++ ) { for( int j = p.y - 1; j <= p.y + 1; j++ ) { if( i == p.x && j == p.y ) { continue; } if( m.has_flag( "FUNGUS", tripoint( i, j, p.z ) ) ) { growth += 1; } } } bool converted = false; if( !m.has_flag_ter( "FUNGUS", p ) ) { // Terrain conversion if( m.has_flag_ter( "DIGGABLE", p ) ) { if( x_in_y( growth * 10, 100 ) ) { m.ter_set( p, t_fungus ); converted = true; } } else if( m.has_flag( "FLAT", p ) ) { if( m.has_flag( TFLAG_INDOORS, p ) ) { if( x_in_y( growth * 10, 500 ) ) { m.ter_set( p, t_fungus_floor_in ); converted = true; } } else if( m.has_flag( TFLAG_SUPPORTS_ROOF, p ) ) { if( x_in_y( growth * 10, 1000 ) ) { m.ter_set( p, t_fungus_floor_sup ); converted = true; } } else { if( x_in_y( growth * 10, 2500 ) ) { m.ter_set( p, t_fungus_floor_out ); converted = true; } } } else if( m.has_flag( "SHRUB", p ) ) { if( x_in_y( growth * 10, 200 ) ) { m.ter_set( p, t_shrub_fungal ); converted = true; } else if( x_in_y( growth, 1000 ) ) { m.ter_set( p, t_marloss ); converted = true; } } else if( m.has_flag( "THIN_OBSTACLE", p ) ) { if( x_in_y( growth * 10, 150 ) ) { m.ter_set( p, t_fungus_mound ); converted = true; } } else if( m.has_flag( "YOUNG", p ) ) { if( x_in_y( growth * 10, 500 ) ) { m.ter_set( p, t_tree_fungal_young ); converted = true; } } else if( m.has_flag( "WALL", p ) ) { if( x_in_y( growth * 10, 5000 ) ) { converted = true; m.ter_set( p, t_fungus_wall ); } } // Furniture conversion if( converted ) { if( m.has_flag( "FLOWER", p ) ) { m.furn_set( p, f_flower_fungal ); } else if( m.has_flag( "ORGANIC", p ) ) { if( m.furn( p ).obj().movecost == -10 ) { m.furn_set( p, f_fungal_mass ); } else { m.furn_set( p, f_fungal_clump ); } } else if( m.has_flag( "PLANT", p ) ) { // Replace the (already existing) seed m.i_at( p )[0] = item( "fungal_seeds", calendar::turn ); } } return true; } else { // Everything is already fungus if( growth == 9 ) { return false; } for( int i = p.x - 1; i <= p.x + 1; i++ ) { for( int j = p.y - 1; j <= p.y + 1; j++ ) { tripoint dest( i, j, p.z ); // One spread on average if( !m.has_flag( "FUNGUS", dest ) && one_in( 9 - growth ) ) { //growth chance is 100 in X simplified if( m.has_flag( "DIGGABLE", dest ) ) { m.ter_set( dest, t_fungus ); converted = true; } else if( m.has_flag( "FLAT", dest ) ) { if( m.has_flag( TFLAG_INDOORS, dest ) ) { if( one_in( 5 ) ) { m.ter_set( dest, t_fungus_floor_in ); converted = true; } } else if( m.has_flag( TFLAG_SUPPORTS_ROOF, dest ) ) { if( one_in( 10 ) ) { m.ter_set( dest, t_fungus_floor_sup ); converted = true; } } else { if( one_in( 25 ) ) { m.ter_set( dest, t_fungus_floor_out ); converted = true; } } } else if( m.has_flag( "SHRUB", dest ) ) { if( one_in( 2 ) ) { m.ter_set( dest, t_shrub_fungal ); converted = true; } else if( one_in( 25 ) ) { m.ter_set( dest, t_marloss ); converted = true; } } else if( m.has_flag( "THIN_OBSTACLE", dest ) ) { if( x_in_y( 10, 15 ) ) { m.ter_set( dest, t_fungus_mound ); converted = true; } } else if( m.has_flag( "YOUNG", dest ) ) { if( one_in( 5 ) ) { if( m.get_field_strength( p, fd_fungal_haze ) != 0 ) { if( one_in( 8 ) ) { // young trees are vulnerable m.ter_set( dest, t_fungus ); gm.summon_mon( mon_fungal_blossom, p ); if( gm.u.sees( p ) ) { add_msg( m_warning, _( "The young tree blooms forth into a fungal blossom!" ) ); } } else if( one_in( 4 ) ) { m.ter_set( dest, t_marloss_tree ); } } else { m.ter_set( dest, t_tree_fungal_young ); } converted = true; } } else if( m.has_flag( "TREE", dest ) ) { if( one_in( 10 ) ) { if( m.get_field_strength( p, fd_fungal_haze ) != 0 ) { if( one_in( 10 ) ) { m.ter_set( dest, t_fungus ); gm.summon_mon( mon_fungal_blossom, p ); if( gm.u.sees( p ) ) { add_msg( m_warning, _( "The tree blooms forth into a fungal blossom!" ) ); } } else if( one_in( 6 ) ) { m.ter_set( dest, t_marloss_tree ); } } else { m.ter_set( dest, t_tree_fungal ); } converted = true; } } else if( m.has_flag( "WALL", dest ) ) { if( one_in( 50 ) ) { converted = true; m.ter_set( dest, t_fungus_wall ); } } if( converted ) { if( m.has_flag( "FLOWER", dest ) ) { m.furn_set( dest, f_flower_fungal ); } else if( m.has_flag( "ORGANIC", dest ) ) { if( m.furn( dest ).obj().movecost == -10 ) { m.furn_set( dest, f_fungal_mass ); } else { m.furn_set( dest, f_fungal_clump ); } } else if( m.has_flag( "PLANT", dest ) ) { // Replace the (already existing) seed m.i_at( p )[0] = item( "fungal_seeds", calendar::turn ); } } } } } return false; } }
/** * Generate textual weather forecast for the specified radio tower. */ std::string weather_forecast( point const &abs_sm_pos ) { std::ostringstream weather_report; // Local conditions const auto cref = overmap_buffer.closest_city( tripoint( abs_sm_pos, 0 ) ); const std::string city_name = cref ? cref.city->name : std::string( _( "middle of nowhere" ) ); // Current time weather_report << string_format( _("The current time is %s Eastern Standard Time. At %s in %s, it was %s. The temperature was %s. "), calendar::turn.print_time().c_str(), calendar::turn.print_time(true).c_str(), city_name.c_str(), weather_data(g->weather).name.c_str(), print_temperature(g->temperature).c_str() ); //weather_report << ", the dewpoint ???, and the relative humidity ???. "; //weather_report << "The wind was <direction> at ? mi/km an hour. "; //weather_report << "The pressure was ??? in/mm and steady/rising/falling."; // Regional conditions (simulated by choosing a random range containing the current conditions). // Adjusted for weather volatility based on how many weather changes are coming up. //weather_report << "Across <region>, skies ranged from <cloudiest> to <clearest>. "; // TODO: Add fake reports for nearby cities // TODO: weather forecast // forecasting periods are divided into 12-hour periods, day (6-18) and night (19-5) // Accumulate percentages for each period of various weather statistics, and report that // (with fuzz) as the weather chances. // int weather_proportions[NUM_WEATHER_TYPES] = {0}; double high = -100.0; double low = 100.0; point const abs_ms_pos = sm_to_ms_copy( abs_sm_pos ); // TODO wind direction and speed int last_hour = calendar::turn - ( calendar::turn % HOURS(1) ); for(int d = 0; d < 6; d++) { weather_type forecast = WEATHER_NULL; for(calendar i(last_hour + 7200 * d); i < last_hour + 7200 * (d + 1); i += 600) { w_point w = g->weather_gen->get_weather( abs_ms_pos, i ); forecast = std::max(forecast, g->weather_gen->get_weather_conditions(w)); high = std::max(high, w.temperature); low = std::min(low, w.temperature); } std::string day; bool started_at_night; calendar c(last_hour + 7200 * d); if(d == 0 && c.is_night()) { day = _("Tonight"); started_at_night = true; } else { day = _("Today"); started_at_night = false; } if(d > 0 && ((started_at_night && !(d % 2)) || (!started_at_night && d % 2))) { day = rmp_format(_("<Mon Night>%s Night"), c.day_of_week().c_str()); } else { day = c.day_of_week(); } weather_report << string_format( _("%s... %s. Highs of %s. Lows of %s. "), day.c_str(), weather_data(forecast).name.c_str(), print_temperature(high).c_str(), print_temperature(low).c_str() ); } return weather_report.str(); }
bool overmapbuffer::reveal(const point ¢er, int radius, int z) { return reveal( tripoint( center, z ), radius ); }
void player::activate_mutation( const std::string &mut ) { const auto &mdata = mutation_branch::get( mut ); 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 && hunger >= 700) || (mdata.thirst && thirst >= 260) || (mdata.fatigue && fatigue >= 575)) { // Insufficient Foo to *maintain* operation is handled in player::suffer add_msg(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){ hunger += cost; } if (mdata.thirst){ thirst += cost; } if (mdata.fatigue){ fatigue += cost; } tdata.powered = true; // Handle stat changes from activation apply_mods(mut, true); recalc_sight_limits(); } if( mut == "WEB_WEAVER" ) { g->m.add_field(pos(), fd_web, 1, 0); add_msg(_("You start spinning web with your spinnerets!")); } else if (mut == "BURROW"){ if (g->u.is_underwater()) { add_msg_if_player(m_info, _("You can't do that while underwater.")); tdata.powered = false; return; } int dirx, diry; if (!choose_adjacent(_("Burrow where?"), dirx, diry)) { tdata.powered = false; return; } if (dirx == g->u.posx() && diry == g->u.posy()) { 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; } int turns; if (g->m.is_bashable(dirx, diry) && g->m.has_flag("SUPPORTS_ROOF", dirx, diry) && g->m.ter(dirx, diry) != t_tree) { // Takes about 100 minutes (not quite two hours) base time. // Being better-adapted to the task means that skillful Survivors can do it almost twice as fast. turns = (100000 - 5000 * g->u.skillLevel("carpentry")); } else if (g->m.move_cost(dirx, diry) == 2 && g->get_levz() == 0 && g->m.ter(dirx, diry) != t_dirt && g->m.ter(dirx, diry) != t_grass) { turns = 18000; } else { add_msg_if_player(m_info, _("You can't burrow there.")); tdata.powered = false; return; } g->u.assign_activity(ACT_BURROW, turns, -1, 0); g->u.activity.placement = tripoint(dirx, diry,0); add_msg_if_player(_("You tear into the %s with your teeth and claws."), g->m.tername(dirx, diry).c_str()); tdata.powered = false; return; // handled when the activity finishes } else if (mut == "SLIMESPAWNER") { std::vector<tripoint> valid; for (int x = posx() - 1; x <= posx() + 1; x++) { for (int y = posy() - 1; y <= posy() + 1; y++) { tripoint dest(x, y, posz()); if (g->is_empty(dest)) { valid.push_back( dest ); } } } // Oops, no room to divide! if (valid.size() == 0) { add_msg(m_bad, _("You focus, but are too hemmed in to birth a new slimespring!")); tdata.powered = false; return; } add_msg(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 (g->summon_mon("mon_player_blob", target)) { monster *slime = g->monster_at( target ); slime->friendly = -1; } } //~ Usual enthusiastic slimespring small voices! :D if (one_in(3)) { add_msg(m_good, _("wow! you look just like me! we should look out for each other!")); } else if (one_in(2)) { add_msg(m_good, _("come on, big me, let's go!")); } else { add_msg(m_good, _("we're a team, we've got this!")); } tdata.powered = false; return; } else if (mut == "SHOUT1") { sounds::sound(pos(), 10 + 2 * str_cur, _("You shout loudly!")); tdata.powered = false; return; } else if (mut == "SHOUT2"){ sounds::sound(pos(), 15 + 3 * str_cur, _("You scream loudly!")); tdata.powered = false; return; } else if (mut == "SHOUT3"){ sounds::sound(pos(), 20 + 4 * str_cur, _("You let out a piercing howl!")); tdata.powered = false; return; } else if ((mut == "NAUSEA") || (mut == "VOMITOUS") ){ vomit(); tdata.powered = false; return; } else if (mut == "M_FERTILE"){ spores(); tdata.powered = false; return; } else if (mut == "M_BLOOM"){ blossoms(); tdata.powered = false; return; } else if (mut == "VINES3"){ item newit("vine_30", calendar::turn, false); if (!can_pickVolume(newit.volume())) { //Accounts for result_mult add_msg(_("You detach a vine but don't have room to carry it, so you drop it.")); g->m.add_item_or_charges(posx(), posy(), newit); } else if (!can_pickWeight(newit.weight(), !OPTIONS["DANGEROUS_PICKUPS"])) { add_msg(_("Your freshly-detached vine is too heavy to carry, so you drop it.")); g->m.add_item_or_charges(posx(), posy(), newit); } else { inv.assign_empty_invlet(newit); newit = i_add(newit); add_msg(m_info, "%c - %s", newit.invlet == 0 ? ' ' : newit.invlet, newit.tname().c_str()); } tdata.powered = false; return; } }
tripoint overmapbuffer::sm_to_om_copy(const tripoint& p) { return tripoint(divide(p.x, 2 * OMAPX), divide(p.y, 2 * OMAPY), p.z); }
/* * Drawing-related functions */ void Creature::draw(WINDOW *w, int player_x, int player_y, bool inverted) const { draw( w, tripoint( player_x, player_y, posz() ), inverted ); }
tripoint overmapbuffer::ms_to_sm_copy(const tripoint& p) { return tripoint(divide(p.x, SEEX), divide(p.y, SEEY), p.z); }
#include "catch/catch.hpp" #include "game.h" #include "map.h" #include "player.h" #include "vehicle.h" #include "enums.h" #include "type_id.h" TEST_CASE( "vehicle_split_section" ) { for( int dir = 0; dir < 360; dir += 15 ) { CHECK( !g->u.in_vehicle ); const tripoint test_origin( 15, 15, 0 ); g->u.setpos( test_origin ); tripoint vehicle_origin = tripoint( 10, 10, 0 ); VehicleList vehs = g->m.get_vehicles(); vehicle *veh_ptr; for( auto &vehs_v : vehs ) { veh_ptr = vehs_v.v; g->m.destroy_vehicle( veh_ptr ); } g->refresh_all(); REQUIRE( g->m.get_vehicles().empty() ); veh_ptr = g->m.add_vehicle( vproto_id( "cross_split_test" ), vehicle_origin, dir, 0, 0 ); REQUIRE( veh_ptr != nullptr ); std::set<tripoint> original_points = veh_ptr->get_points( true ); g->m.destroy( vehicle_origin ); veh_ptr->part_removal_cleanup(); REQUIRE( veh_ptr->get_parts_at( vehicle_origin, "", part_status_flag::available ).empty() );