void draw_bionics_titlebar( const catacurses::window &window, player *p, bionic_menu_mode mode ) { werase( window ); const int pwr_str_pos = right_print( window, 0, 1, c_white, string_format( _( "Power: %i/%i" ), p->power_level, p->max_power_level ) ); std::string desc; if( mode == REASSIGNING ) { desc = _( "Reassigning.\nSelect a bionic to reassign or press SPACE to cancel." ); } else if( mode == ACTIVATING ) { desc = _( "<color_green>Activating</color> <color_yellow>!</color> to examine, <color_yellow>=</color> to reassign, <color_yellow>TAB</color> to switch tabs." ); } else if( mode == EXAMINING ) { desc = _( "<color_light_blue>Examining</color> <color_yellow>!</color> to activate, <color_yellow>=</color> to reassign, <color_yellow>TAB</color> to switch tabs." ); } fold_and_print( window, 0, 1, pwr_str_pos, c_white, desc ); wrefresh( window ); }
// Anchors top-right static void draw_can_craft_indicator( WINDOW *w, const int margin_y, const recipe &rec ) { // Erase previous text // @fixme replace this hack by proper solution (based on max width of possible content) right_print( w, margin_y + 1, 1, c_black, " " ); // Draw text right_print( w, margin_y, 1, c_ltgray, "%s", _( "can craft:" ) ); if( g->u.lighting_craft_speed_multiplier( rec ) == 0.0f ) { right_print( w, margin_y + 1, 1, i_red, "%s", _( "too dark" ) ); } else if( !g->u.has_morale_to_craft() ) { right_print( w, margin_y + 1, 1, i_red, "%s", _( "too sad" ) ); } else if( g->u.lighting_craft_speed_multiplier( rec ) < 1.0f ) { right_print( w, margin_y + 1, 1, i_yellow, _( "slow %d%%" ), int( g->u.lighting_craft_speed_multiplier( rec ) * 100 ) ); } else { right_print( w, margin_y + 1, 1, i_green, "%s", _( "yes" ) ); } }
// Anchors top-right static void draw_can_craft_indicator( const catacurses::window &w, const int margin_y, const recipe &rec ) { // Erase previous text // @todo: fixme replace this hack by proper solution (based on max width of possible content) right_print( w, margin_y + 1, 1, c_black, " " ); // Draw text right_print( w, margin_y, 1, c_light_gray, _( "can craft:" ) ); if( g->u.lighting_craft_speed_multiplier( rec ) <= 0.0f ) { right_print( w, margin_y + 1, 1, i_red, _( "too dark" ) ); } else if( g->u.crafting_speed_multiplier( rec ) <= 0.0f ) { // Technically not always only too sad, but must be too sad right_print( w, margin_y + 1, 1, i_red, _( "too sad" ) ); } else if( g->u.crafting_speed_multiplier( rec ) < 1.0f ) { right_print( w, margin_y + 1, 1, i_yellow, string_format( _( "slow %d%%" ), int( g->u.crafting_speed_multiplier( rec ) * 100 ) ) ); } else { right_print( w, margin_y + 1, 1, i_green, _( "yes" ) ); } }
static void draw_hidden_amount( const catacurses::window &w, const int margin_y, int amount ) { if( amount > 0 ) { right_print( w, margin_y, 14, c_light_gray, string_format( _( "%s hidden" ), amount ) ); } }
// 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 ) { switch( interact_with_vehicle( veh, pos, veh_root_part ) ) { case DONE: return; case ITEMS_FROM_CARGO: cargo_part = veh->part_with_feature( veh_root_part, "CARGO", false ); from_vehicle = cargo_part >= 0; break; case ITEMS_FROM_GROUND: // Nothing to change, default is to pick from ground anyway. if( g->m.has_flag( "SEALED", pos ) ) { return; } break; } } if( !from_vehicle ) { bool isEmpty = ( g->m.i_at( pos ).empty() ); // Hide the pickup window if this is a toilet and there's nothing here // but water. if( ( !isEmpty ) && g->m.furn( pos ) == f_toilet ) { isEmpty = true; for( auto maybe_water : g->m.i_at( pos ) ) { if( maybe_water.typeId() != "water" ) { isEmpty = false; break; } } } if( isEmpty && ( min != -1 || !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.rbegin(), vehitems.rend(), here.begin() ); } else { auto mapitems = g->m.i_at( pos ); here.resize( mapitems.size() ); std::copy( mapitems.rbegin(), mapitems.rend(), 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<pickup_count> getitem( here.size() ); int maxitems = here.size(); maxitems = ( maxitems < minmaxitems ? minmaxitems : ( maxitems > maxmaxitems ? maxmaxitems : maxitems ) ); int itemcount = 0; if( min == -1 ) { //Auto Pickup, select matching items if( !select_autopickup_items( 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 ); std::string action; long raw_input_char = ' '; input_context ctxt( "PICKUP" ); ctxt.register_action( "UP" ); ctxt.register_action( "DOWN" ); ctxt.register_action( "RIGHT" ); ctxt.register_action( "LEFT" ); ctxt.register_action( "NEXT_TAB", _( "Next page" ) ); ctxt.register_action( "PREV_TAB", _( "Previous page" ) ); ctxt.register_action( "SCROLL_UP" ); ctxt.register_action( "SCROLL_DOWN" ); ctxt.register_action( "CONFIRM" ); ctxt.register_action( "SELECT_ALL" ); ctxt.register_action( "QUIT", _( "Cancel" ) ); ctxt.register_action( "ANY_INPUT" ); ctxt.register_action( "HELP_KEYBINDINGS" ); int start = 0, cur_it; player pl_copy = g->u; pl_copy.set_fake( true ); bool update = true; mvwprintw( w_pickup, 0, 0, _( "PICK UP" ) ); int selected = 0; int iScrollPos = 0; if( g->was_fullscreen ) { g->draw_ter(); } // Now print the two lists; those on the ground and about to be added to inv // Continue until we hit return or space do { const std::string pickup_chars = ctxt.get_available_single_char_hotkeys( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:;" ); int idx = -1; for( int i = 1; i < pickupH; i++ ) { mvwprintw( w_pickup, i, 0, " " ); } if( action == "ANY_INPUT" && raw_input_char >= '0' && raw_input_char <= '9' ) { int raw_input_char_value = ( char )raw_input_char - '0'; itemcount *= 10; itemcount += raw_input_char_value; if( itemcount < 0 ) { itemcount = 0; } } else if( action == "SCROLL_UP" ) { iScrollPos--; } else if( action == "SCROLL_DOWN" ) { iScrollPos++; } else if( action == "PREV_TAB" ) { if( start > 0 ) { start -= maxitems; } else { start = ( int )( ( here.size() - 1 ) / maxitems ) * maxitems; } selected = start; mvwprintw( w_pickup, maxitems + 2, 0, " " ); } else if( action == "NEXT_TAB" ) { if( start + maxitems < ( int )here.size() ) { start += maxitems; } else { start = 0; } iScrollPos = 0; selected = start; mvwprintw( w_pickup, maxitems + 2, pickupH, " " ); } else if( action == "UP" ) { selected--; iScrollPos = 0; if( selected < 0 ) { selected = here.size() - 1; start = ( int )( here.size() / maxitems ) * maxitems; if( start >= ( int )here.size() ) { start -= maxitems; } } else if( selected < start ) { start -= maxitems; } } else if( action == "DOWN" ) { selected++; iScrollPos = 0; if( selected >= ( int )here.size() ) { selected = 0; start = 0; } else if( selected >= start + maxitems ) { start += maxitems; } } else if( selected >= 0 && ( ( action == "RIGHT" && !getitem[selected] ) || ( action == "LEFT" && getitem[selected] ) ) ) { idx = selected; } else if( action == "ANY_INPUT" && raw_input_char == '`' ) { 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 if( action == "ANY_INPUT" ) { idx = ( raw_input_char <= 127 ) ? pickup_chars.find( raw_input_char ) : -1; iScrollPos = 0; } if( idx >= 0 && idx < ( int )here.size() ) { if( getitem[idx] ) { if( here[idx].count_by_charges() ) { if( getitem[idx].count == 0 ) { pl_copy.inv.find_item( getitem[idx].position ).charges -= here[idx].charges; } else { pl_copy.inv.find_item( getitem[idx].position ).charges -= getitem[idx].count; } } else { unsigned stack_size = pl_copy.inv.const_stack( getitem[idx].position ).size(); pl_copy.i_rem( getitem[idx].position ); //if the stack_was emptied, removing the item invalidated later positions- fix them if( stack_size == 1 ) { for( unsigned i = 0; i < here.size(); i++ ) { if( getitem[i] && getitem[i].position > getitem[idx].position ) { getitem[i].position--; } } } } } //end if getitem[idx] if( itemcount != 0 || getitem[idx].count == 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; } getitem[idx].count = itemcount; itemcount = 0; } // Note: this might not change the value of getitem[idx] at all! getitem[idx].pick = ( action == "RIGHT" ? true : ( action == "LEFT" ? false : !getitem[idx] ) ); if( action != "RIGHT" && action != "LEFT" ) { selected = idx; start = ( int )( idx / maxitems ) * maxitems; } if( getitem[idx] ) { item temp = here[idx]; if( getitem[idx].count != 0 && getitem[idx].count < here[idx].charges ) { temp.charges = getitem[idx].count; } item *added = &( pl_copy.i_add( temp ) ); getitem[idx].position = pl_copy.inv.position_by_item( added ); } else { getitem[idx].count = 0; } update = true; } 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, iScrollPos, true, true ); } draw_custom_border( w_item_info, false ); mvwprintw( w_item_info, 0, 2, "< " ); trim_and_print( w_item_info, 0, 4, itemsW - 8, c_white, "%s >", here[selected].display_name().c_str() ); wrefresh( w_item_info ); if( action == "SELECT_ALL" ) { int count = 0; for( size_t i = 0; i < here.size(); i++ ) { if( getitem[i] ) { count++; } else { item *added = &( pl_copy.i_add( here[i] ) ); getitem[i].position = pl_copy.inv.position_by_item( added ); } getitem[i].pick = true; } if( count == ( int )here.size() ) { for( size_t i = 0; i < here.size(); i++ ) { getitem[i].pick = false; } pl_copy = g->u; pl_copy.set_fake( true ); } 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 if( cur_it < ( int )pickup_chars.size() + ( int )pickup_chars.size() * ( int )pickup_chars.size() ) { int p = cur_it - pickup_chars.size(); int p1 = p / pickup_chars.size(); int p2 = p % pickup_chars.size(); mvwprintz( w_pickup, 1 + ( cur_it % maxitems ), 0, icolor, "`%c%c", char( pickup_chars[p1] ), char( pickup_chars[p2] ) ); } else { mvwputch( w_pickup, 1 + ( cur_it % maxitems ), 0, icolor, ' ' ); } if( getitem[cur_it] ) { if( getitem[cur_it].count == 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( "%s %s", here[cur_it].symbol().c_str(), item_name.c_str() ); } trim_and_print( w_pickup, 1 + ( cur_it % maxitems ), 6, pickupW - 4, icolor, "%s", item_name.c_str() ); } } mvwprintw( w_pickup, maxitems + 1, 0, _( "[%s] Unmark" ), ctxt.get_desc( "LEFT", 1 ).c_str() ); center_print( w_pickup, maxitems + 1, c_ltgray, _( "[%s] Help" ), ctxt.get_desc( "HELP_KEYBINDINGS", 1 ).c_str() ); right_print( w_pickup, maxitems + 1, 0, c_ltgray, _( "[%s] Mark" ), ctxt.get_desc( "RIGHT", 1 ).c_str() ); mvwprintw( w_pickup, maxitems + 2, 0, _( "[%s] Prev" ), ctxt.get_desc( "PREV_TAB", 1 ).c_str() ); center_print( w_pickup, maxitems + 2, c_ltgray, _( "[%s] All" ), ctxt.get_desc( "SELECT_ALL", 1 ).c_str() ); right_print( w_pickup, maxitems + 2, 0, c_ltgray, _( "[%s] Next" ), ctxt.get_desc( "NEXT_TAB", 1 ).c_str() ); if( update ) { // Update weight & volume information update = false; for( int i = 9; i < pickupW; ++i ) { mvwaddch( w_pickup, 0, i, ' ' ); } mvwprintz( w_pickup, 0, 9, ( pl_copy.weight_carried() > g->u.weight_capacity() ? c_red : c_white ), _( "Wgt %.1f" ), convert_weight( pl_copy.weight_carried() ) + 0.05 ); // +0.05 to round up wprintz( w_pickup, c_white, "/%.1f", convert_weight( g->u.weight_capacity() ) ); mvwprintz( w_pickup, 0, 24, ( pl_copy.volume_carried() > g->u.volume_capacity() ? c_red : c_white ), _( "Vol %d" ), pl_copy.volume_carried() ); wprintz( w_pickup, c_white, "/%d", g->u.volume_capacity() ); } wrefresh( w_pickup ); action = ctxt.handle_input(); raw_input_char = ctxt.get_raw_input().get_first_input(); } while( action != "QUIT" && action != "CONFIRM" ); bool item_selected = false; // Check if we have selected an item. for( auto selection : getitem ) { if( selection ) { item_selected = true; } } if( action != "CONFIRM" || !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; } std::reverse( getitem.begin(), getitem.end() ); 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( getitem[i].count ); } } g->reenter_fullscreen(); }
// Pick up items at (pos). void Pickup::pick_up( const tripoint &pos, int min ) { int cargo_part = -1; const optional_vpart_position vp = g->m.veh_at( pos ); vehicle *const veh = veh_pointer_or_null( vp ); bool from_vehicle = false; if( min != -1 ) { switch( interact_with_vehicle( veh, pos, vp ? vp->part_index() : -1 ) ) { case DONE: return; case ITEMS_FROM_CARGO: { const cata::optional<vpart_reference> carg = vp.part_with_feature( "CARGO", false ); cargo_part = carg ? carg->part_index() : -1; } from_vehicle = cargo_part >= 0; break; case ITEMS_FROM_GROUND: // Nothing to change, default is to pick from ground anyway. if( g->m.has_flag( "SEALED", pos ) ) { return; } break; } } if( !from_vehicle ) { bool isEmpty = ( g->m.i_at( pos ).empty() ); // Hide the pickup window if this is a toilet and there's nothing here // but water. if( ( !isEmpty ) && g->m.furn( pos ) == f_toilet ) { isEmpty = true; for( auto maybe_water : g->m.i_at( pos ) ) { if( maybe_water.typeId() != "water" ) { isEmpty = false; break; } } } if( isEmpty && ( min != -1 || !get_option<bool>( "AUTO_PICKUP_ADJACENT" ) ) ) { return; } } // which items are we grabbing? std::vector<item> here; if( from_vehicle ) { auto vehitems = veh->get_items( cargo_part ); here.resize( vehitems.size() ); std::copy( vehitems.begin(), vehitems.end(), here.begin() ); } else { auto mapitems = g->m.i_at( pos ); here.resize( mapitems.size() ); std::copy( mapitems.begin(), mapitems.end(), here.begin() ); } if( min == -1 ) { // Recursively pick up adjacent items if that option is on. if( get_option<bool>( "AUTO_PICKUP_ADJACENT" ) && g->u.pos() == pos ) { //Autopickup adjacent direction adjacentDir[8] = {NORTH, NORTHEAST, EAST, SOUTHEAST, SOUTH, SOUTHWEST, WEST, NORTHWEST}; for( auto &elem : adjacentDir ) { tripoint apos = tripoint( direction_XY( elem ), 0 ); apos += pos; pick_up( apos, min ); } } // Bail out if this square cannot be auto-picked-up if( g->check_zone( zone_type_id( "NO_AUTO_PICKUP" ), pos ) ) { return; } else if( g->m.has_flag( "SEALED", pos ) ) { return; } } // Not many items, just grab them if( ( int )here.size() <= min && min != -1 ) { g->u.assign_activity( activity_id( "ACT_PICKUP" ) ); g->u.activity.placement = pos - g->u.pos(); g->u.activity.values.push_back( from_vehicle ); // Only one item means index is 0. g->u.activity.values.push_back( 0 ); // auto-pickup means pick up all. g->u.activity.values.push_back( 0 ); return; } std::vector<std::list<item_idx>> stacked_here; for( size_t i = 0; i < here.size(); i++ ) { item &it = here[i]; bool found_stack = false; for( auto &stack : stacked_here ) { if( stack.begin()->_item.stacks_with( it ) ) { item_idx el = { it, i }; stack.push_back( el ); found_stack = true; break; } } if( !found_stack ) { std::list<item_idx> newstack; newstack.push_back( { it, i } ); stacked_here.push_back( newstack ); } } std::reverse( stacked_here.begin(), stacked_here.end() ); if( min != -1 ) { // don't bother if we're just autopickuping g->temp_exit_fullscreen(); } bool sideStyle = use_narrow_sidebar(); // Otherwise, we have Autopickup, 2 or more items and should list them, etc. int maxmaxitems = sideStyle ? TERMY : getmaxy( g->w_messages ) - 3; int itemsH = std::min( 25, TERMY / 2 ); int pickupBorderRows = 3; // The pickup list may consume the entire terminal, minus space needed for its // header/footer and the item info window. int minleftover = itemsH + pickupBorderRows; if( maxmaxitems > TERMY - minleftover ) { maxmaxitems = TERMY - minleftover; } const int minmaxitems = sideStyle ? 6 : 9; std::vector<pickup_count> getitem( stacked_here.size() ); int maxitems = stacked_here.size(); maxitems = ( maxitems < minmaxitems ? minmaxitems : ( maxitems > maxmaxitems ? maxmaxitems : maxitems ) ); int itemcount = 0; if( min == -1 ) { //Auto Pickup, select matching items if( !select_autopickup_items( stacked_here, getitem ) ) { // If we didn't find anything, bail out now. return; } } else { int pickupH = maxitems + pickupBorderRows; int pickupW = getmaxx( g->w_messages ); int pickupY = VIEW_OFFSET_Y; int pickupX = getbegx( g->w_messages ); int itemsW = pickupW; int itemsY = sideStyle ? pickupY + pickupH : TERMY - itemsH; int itemsX = pickupX; catacurses::window w_pickup = catacurses::newwin( pickupH, pickupW, pickupY, pickupX ); catacurses::window w_item_info = catacurses::newwin( itemsH, itemsW, itemsY, itemsX ); std::string action; long raw_input_char = ' '; input_context ctxt( "PICKUP" ); ctxt.register_action( "UP" ); ctxt.register_action( "DOWN" ); ctxt.register_action( "RIGHT" ); ctxt.register_action( "LEFT" ); ctxt.register_action( "NEXT_TAB", _( "Next page" ) ); ctxt.register_action( "PREV_TAB", _( "Previous page" ) ); ctxt.register_action( "SCROLL_UP" ); ctxt.register_action( "SCROLL_DOWN" ); ctxt.register_action( "CONFIRM" ); ctxt.register_action( "SELECT_ALL" ); ctxt.register_action( "QUIT", _( "Cancel" ) ); ctxt.register_action( "ANY_INPUT" ); ctxt.register_action( "HELP_KEYBINDINGS" ); ctxt.register_action( "FILTER" ); int start = 0; int cur_it = 0; bool update = true; mvwprintw( w_pickup, 0, 0, _( "PICK UP" ) ); int selected = 0; int iScrollPos = 0; std::string filter; std::string new_filter; std::vector<int> matches;//Indexes of items that match the filter bool filter_changed = true; if( g->was_fullscreen ) { g->draw_ter(); } // Now print the two lists; those on the ground and about to be added to inv // Continue until we hit return or space do { const std::string pickup_chars = ctxt.get_available_single_char_hotkeys( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:;" ); int idx = -1; for( int i = 1; i < pickupH; i++ ) { mvwprintw( w_pickup, i, 0, " " ); } if( action == "ANY_INPUT" && raw_input_char >= '0' && raw_input_char <= '9' ) { int raw_input_char_value = ( char )raw_input_char - '0'; itemcount *= 10; itemcount += raw_input_char_value; if( itemcount < 0 ) { itemcount = 0; } } else if( action == "SCROLL_UP" ) { iScrollPos--; } else if( action == "SCROLL_DOWN" ) { iScrollPos++; } else if( action == "PREV_TAB" ) { if( start > 0 ) { start -= maxitems; } else { start = ( int )( ( matches.size() - 1 ) / maxitems ) * maxitems; } selected = start; mvwprintw( w_pickup, maxitems + 2, 0, " " ); } else if( action == "NEXT_TAB" ) { if( start + maxitems < ( int )matches.size() ) { start += maxitems; } else { start = 0; } iScrollPos = 0; selected = start; mvwprintw( w_pickup, maxitems + 2, pickupH, " " ); } else if( action == "UP" ) { selected--; iScrollPos = 0; if( selected < 0 ) { selected = matches.size() - 1; start = ( int )( matches.size() / maxitems ) * maxitems; if( start >= ( int )matches.size() ) { start -= maxitems; } } else if( selected < start ) { start -= maxitems; } } else if( action == "DOWN" ) { selected++; iScrollPos = 0; if( selected >= ( int )matches.size() ) { selected = 0; start = 0; } else if( selected >= start + maxitems ) { start += maxitems; } } else if( selected >= 0 && selected < int( matches.size() ) && ( ( action == "RIGHT" && !getitem[matches[selected]].pick ) || ( action == "LEFT" && getitem[matches[selected]].pick ) ) ) { idx = selected; } else if( action == "FILTER" ) { new_filter = filter; string_input_popup popup; popup .title( _( "Set filter" ) ) .width( 30 ) .edit( new_filter ); if( !popup.canceled() ) { filter_changed = true; } else { wrefresh( g->w_terrain ); } } else if( action == "ANY_INPUT" && raw_input_char == '`' ) { std::string ext = string_input_popup() .title( _( "Enter 2 letters (case sensitive):" ) ) .width( 3 ) .max_length( 2 ) .query_string(); if( ext.size() == 2 ) { int p1 = pickup_chars.find( ext.at( 0 ) ); int p2 = pickup_chars.find( ext.at( 1 ) ); if( p1 != -1 && p2 != -1 ) { idx = pickup_chars.size() + ( p1 * pickup_chars.size() ) + p2; } } } else if( action == "ANY_INPUT" ) { idx = ( raw_input_char <= 127 ) ? pickup_chars.find( raw_input_char ) : -1; iScrollPos = 0; } if( idx >= 0 && idx < ( int )matches.size() ) { size_t true_idx = matches[idx]; if( itemcount != 0 || getitem[true_idx].count == 0 ) { item &temp = stacked_here[true_idx].begin()->_item; int amount_available = temp.count_by_charges() ? temp.charges : stacked_here[true_idx].size(); if( itemcount >= amount_available ) { itemcount = 0; } getitem[true_idx].count = itemcount; itemcount = 0; } // Note: this might not change the value of getitem[idx] at all! getitem[true_idx].pick = ( action == "RIGHT" ? true : ( action == "LEFT" ? false : !getitem[true_idx].pick ) ); if( action != "RIGHT" && action != "LEFT" ) { selected = idx; start = ( int )( idx / maxitems ) * maxitems; } if( !getitem[true_idx].pick ) { getitem[true_idx].count = 0; } update = true; } if( filter_changed ) { matches.clear(); while( matches.empty() ) { auto filter_func = item_filter_from_string( new_filter ); for( size_t index = 0; index < stacked_here.size(); index++ ) { if( filter_func( stacked_here[index].begin()->_item ) ) { matches.push_back( index ); } } if( matches.empty() ) { popup( _( "Your filter returned no results" ) ); wrefresh( g->w_terrain ); // The filter must have results, or simply be emptied or canceled, // as this screen can't be reached without there being // items available string_input_popup popup; popup .title( _( "Set filter" ) ) .width( 30 ) .edit( new_filter ); if( popup.canceled() ) { new_filter = filter; filter_changed = false; } } } if( filter_changed ) { filter = new_filter; filter_changed = false; selected = 0; start = 0; iScrollPos = 0; } wrefresh( g->w_terrain ); } item &selected_item = stacked_here[matches[selected]].begin()->_item; werase( w_item_info ); if( selected >= 0 && selected <= ( int )stacked_here.size() - 1 ) { std::vector<iteminfo> vThisItem; std::vector<iteminfo> vDummy; selected_item.info( true, vThisItem ); draw_item_info( w_item_info, "", "", vThisItem, vDummy, iScrollPos, true, true ); } draw_custom_border( w_item_info, false ); mvwprintw( w_item_info, 0, 2, "< " ); trim_and_print( w_item_info, 0, 4, itemsW - 8, c_white, "%s >", selected_item.display_name().c_str() ); wrefresh( w_item_info ); if( action == "SELECT_ALL" ) { int count = 0; for( auto i : matches ) { if( getitem[i].pick ) { count++; } getitem[i].pick = true; } if( count == ( int )stacked_here.size() ) { for( size_t i = 0; i < stacked_here.size(); i++ ) { getitem[i].pick = false; } } update = true; } for( cur_it = start; cur_it < start + maxitems; cur_it++ ) { mvwprintw( w_pickup, 1 + ( cur_it % maxitems ), 0, " " ); if( cur_it < ( int )matches.size() ) { int true_it = matches[cur_it]; item &this_item = stacked_here[ true_it ].begin()->_item; nc_color icolor = this_item.color_in_inventory(); if( cur_it == selected ) { icolor = hilite( icolor ); } if( cur_it < ( int )pickup_chars.size() ) { mvwputch( w_pickup, 1 + ( cur_it % maxitems ), 0, icolor, char( pickup_chars[cur_it] ) ); } else if( cur_it < ( int )pickup_chars.size() + ( int )pickup_chars.size() * ( int )pickup_chars.size() ) { int p = cur_it - pickup_chars.size(); int p1 = p / pickup_chars.size(); int p2 = p % pickup_chars.size(); mvwprintz( w_pickup, 1 + ( cur_it % maxitems ), 0, icolor, "`%c%c", char( pickup_chars[p1] ), char( pickup_chars[p2] ) ); } else { mvwputch( w_pickup, 1 + ( cur_it % maxitems ), 0, icolor, ' ' ); } if( getitem[true_it].pick ) { if( getitem[true_it].count == 0 ) { wprintz( w_pickup, c_light_blue, " + " ); } else { wprintz( w_pickup, c_light_blue, " # " ); } } else { wprintw( w_pickup, " - " ); } std::string item_name; if( stacked_here[true_it].begin()->_item.ammo_type() == "money" ) { //Count charges //TODO: transition to the item_location system used for the inventory unsigned long charges_total = 0; for( const auto item : stacked_here[true_it] ) { charges_total += item._item.charges; } //Picking up none or all the cards in a stack if( !getitem[true_it].pick || getitem[true_it].count == 0 ) { item_name = stacked_here[true_it].begin()->_item.display_money( stacked_here[true_it].size(), charges_total ); } else { unsigned long charges = 0; int c = getitem[true_it].count; for( auto it = stacked_here[true_it].begin(); it != stacked_here[true_it].end() && c > 0; ++it, --c ) { charges += it->_item.charges; } item_name = string_format( _( "%s of %s" ), stacked_here[true_it].begin()->_item.display_money( getitem[true_it].count, charges ), format_money( charges_total ) ); } } else { item_name = this_item.display_name( stacked_here[true_it].size() ); } if( stacked_here[true_it].size() > 1 ) { item_name = string_format( "%d %s", stacked_here[true_it].size(), item_name.c_str() ); } if( get_option<bool>( "ITEM_SYMBOLS" ) ) { item_name = string_format( "%s %s", this_item.symbol().c_str(), item_name.c_str() ); } trim_and_print( w_pickup, 1 + ( cur_it % maxitems ), 6, pickupW - 4, icolor, item_name ); } } mvwprintw( w_pickup, maxitems + 1, 0, _( "[%s] Unmark" ), ctxt.get_desc( "LEFT", 1 ).c_str() ); center_print( w_pickup, maxitems + 1, c_light_gray, string_format( _( "[%s] Help" ), ctxt.get_desc( "HELP_KEYBINDINGS", 1 ).c_str() ) ); right_print( w_pickup, maxitems + 1, 0, c_light_gray, string_format( _( "[%s] Mark" ), ctxt.get_desc( "RIGHT", 1 ).c_str() ) ); mvwprintw( w_pickup, maxitems + 2, 0, _( "[%s] Prev" ), ctxt.get_desc( "PREV_TAB", 1 ).c_str() ); center_print( w_pickup, maxitems + 2, c_light_gray, string_format( _( "[%s] All" ), ctxt.get_desc( "SELECT_ALL", 1 ).c_str() ) ); right_print( w_pickup, maxitems + 2, 0, c_light_gray, string_format( _( "[%s] Next" ), ctxt.get_desc( "NEXT_TAB", 1 ).c_str() ) ); if( update ) { // Update weight & volume information update = false; for( int i = 9; i < pickupW; ++i ) { mvwaddch( w_pickup, 0, i, ' ' ); } units::mass weight_picked_up = 0; units::volume volume_picked_up = 0; for( size_t i = 0; i < getitem.size(); i++ ) { if( getitem[i].pick ) { item temp = stacked_here[i].begin()->_item; if( temp.count_by_charges() && getitem[i].count < temp.charges && getitem[i].count != 0 ) { temp.charges = getitem[i].count; } int num_picked = std::min( stacked_here[i].size(), getitem[i].count == 0 ? stacked_here[i].size() : getitem[i].count ); weight_picked_up += temp.weight() * num_picked; volume_picked_up += temp.volume() * num_picked; } } auto weight_predict = g->u.weight_carried() + weight_picked_up; auto volume_predict = g->u.volume_carried() + volume_picked_up; mvwprintz( w_pickup, 0, 9, weight_predict > g->u.weight_capacity() ? c_red : c_white, _( "Wgt %.1f" ), round_up( convert_weight( weight_predict ), 1 ) ); wprintz( w_pickup, c_white, "/%.1f", round_up( convert_weight( g->u.weight_capacity() ), 1 ) ); std::string fmted_volume_predict = format_volume( volume_predict ); mvwprintz( w_pickup, 0, 24, volume_predict > g->u.volume_capacity() ? c_red : c_white, _( "Vol %s" ), fmted_volume_predict.c_str() ); std::string fmted_volume_capacity = format_volume( g->u.volume_capacity() ); wprintz( w_pickup, c_white, "/%s", fmted_volume_capacity.c_str() ); }; wrefresh( w_pickup ); action = ctxt.handle_input(); raw_input_char = ctxt.get_raw_input().get_first_input(); } while( action != "QUIT" && action != "CONFIRM" ); bool item_selected = false; // Check if we have selected an item. for( auto selection : getitem ) { if( selection.pick ) { item_selected = true; } } if( action != "CONFIRM" || !item_selected ) { w_pickup = catacurses::window(); w_item_info = catacurses::window(); add_msg( _( "Never mind." ) ); g->reenter_fullscreen(); g->refresh_all(); return; } } // At this point we've selected our items, register an activity to pick them up. g->u.assign_activity( activity_id( "ACT_PICKUP" ) ); g->u.activity.placement = pos - g->u.pos(); g->u.activity.values.push_back( from_vehicle ); if( min == -1 ) { // Auto pickup will need to auto resume since there can be several of them on the stack. g->u.activity.auto_resume = true; } std::vector<std::pair<int, int>> pick_values; for( size_t i = 0; i < stacked_here.size(); i++ ) { const auto &selection = getitem[i]; if( !selection.pick ) { continue; } const auto &stack = stacked_here[i]; // Note: items can be both charged and stacked // For robustness, let's assume they can be both in the same stack bool pick_all = selection.count == 0; size_t count = selection.count; for( const item_idx &it : stack ) { if( !pick_all && count == 0 ) { break; } if( it._item.count_by_charges() ) { size_t num_picked = std::min( ( size_t )it._item.charges, count ); pick_values.push_back( { static_cast<int>( it.idx ), static_cast<int>( num_picked ) } ); count -= num_picked; } else { size_t num_picked = 1; pick_values.push_back( { static_cast<int>( it.idx ), 0 } ); count -= num_picked; } } } // The pickup activity picks up items last-to-first from its values list, so make sure the // higher indices are at the end. std::sort( pick_values.begin(), pick_values.end() ); for( auto &it : pick_values ) { g->u.activity.values.push_back( it.first ); g->u.activity.values.push_back( it.second ); } g->reenter_fullscreen(); }
void string_input_popup::create_window() { nc_color title_color = c_light_red; nc_color desc_color = c_green; int titlesize = utf8_width( _title ); // Occupied horizontal space if( _max_length <= 0 ) { _max_length = _width; } // 2 for border (top and bottom) and 1 for the input text line. int w_height = 2 + 1; // |"w_width = width + titlesize (this text) + 5": _____ | int w_width = FULL_SCREEN_WIDTH; if( _width <= 0 ) { _width = std::max( 5, FULL_SCREEN_WIDTH - titlesize - 5 ); // Default if unspecified } else { _width = std::min( FULL_SCREEN_WIDTH - 20, _width ); w_width = _width + titlesize + 5; } std::vector<std::string> title_split = { _title }; if( w_width > FULL_SCREEN_WIDTH ) { // Out of horizontal space- wrap the title titlesize = FULL_SCREEN_WIDTH - _width - 5; w_width = FULL_SCREEN_WIDTH; for( int wraplen = w_width - 2; wraplen >= titlesize; wraplen-- ) { title_split = foldstring( _title, wraplen ); if( int( title_split.back().size() ) <= titlesize ) { break; } } w_height += int( title_split.size() ) - 1; } std::vector<std::string> descformatted; if( !_description.empty() ) { const int twidth = std::min( utf8_width( remove_color_tags( _description ) ), w_width - 4 ); descformatted = foldstring( _description, twidth ); w_height += descformatted.size(); } // length of title + border (left) + space _startx = titlesize + 2; // Below the description and below the top border _starty = 1 + descformatted.size(); if( _max_length <= 0 ) { _max_length = 1024; } _endx = w_width - 3; _position = -1; const int w_y = ( TERMY - w_height ) / 2; const int w_x = std::max( ( TERMX - w_width ) / 2, 0 ); w = catacurses::newwin( w_height, w_width, w_y, w_x ); draw_border( w ); for( size_t i = 0; i < descformatted.size(); ++i ) { trim_and_print( w, 1 + i, 1, w_width - 2, desc_color, descformatted[i] ); } for( int i = 0; i < int( title_split.size() ) - 1; i++ ) { mvwprintz( w, _starty++, i + 1, title_color, title_split[i] ); } right_print( w, _starty, w_width - titlesize - 1, title_color, title_split.back() ); _starty = w_height - 2; // The ____ looks better at the bottom right when the title folds }
void Messages::dialog::show() { werase( w ); draw_border( w, border_color ); scrollbar() .offset_x( 0 ) .offset_y( border_width ) .content_size( folded_filtered.size() ) .viewport_pos( offset ) .viewport_size( max_lines ) .apply( w ); // Range of window lines to print size_t line_from = 0, line_to; if( offset < folded_filtered.size() ) { line_to = std::min( max_lines, folded_filtered.size() - offset ); } else { line_to = 0; } if( !log_from_top ) { // Always print from new to old std::swap( line_from, line_to ); } std::string prev_time_str; bool printing_range = false; for( size_t line = line_from; line != line_to; ) { // Decrement here if printing from bottom to get the correct line number if( !log_from_top ) { --line; } const size_t folded_ind = offset + line; const size_t msg_ind = folded_all[folded_filtered[folded_ind]].first; const game_message &msg = player_messages.history( msg_ind ); nc_color col = msgtype_to_color( msg.type, false ); // Print current line print_colored_text( w, border_width + line, border_width + time_width + padding_width, col, col, folded_all[folded_filtered[folded_ind]].second ); // Generate aligned time string const time_point msg_time = msg.timestamp_in_turns; const std::string time_str = to_string_clipped( calendar::turn - msg_time, clipped_align::right ); if( time_str != prev_time_str ) { // Time changed, print time string prev_time_str = time_str; right_print( w, border_width + line, border_width + msg_width + padding_width, time_color, time_str ); printing_range = false; } else { // Print line brackets to mark ranges of time if( printing_range ) { const size_t last_line = log_from_top ? line - 1 : line + 1; wattron( w, bracket_color ); mvwaddch( w, border_width + last_line, border_width + time_width - 1, LINE_XOXO ); wattroff( w, bracket_color ); } wattron( w, bracket_color ); mvwaddch( w, border_width + line, border_width + time_width - 1, log_from_top ? LINE_XXOO : LINE_OXXO ); wattroff( w, bracket_color ); printing_range = true; } // Decrement for !log_from_top is done at the beginning if( log_from_top ) { ++line; } } if( filtering ) { wrefresh( w ); // Print the help text werase( w_filter_help ); draw_border( w_filter_help, border_color ); for( size_t line = 0; line < help_text.size(); ++line ) { nc_color col = filter_help_color; print_colored_text( w_filter_help, border_width + line, border_width, col, col, help_text[line] ); } mvwprintz( w_filter_help, w_fh_height - 1, border_width, border_color, "< " ); mvwprintz( w_filter_help, w_fh_height - 1, w_fh_width - border_width - 2, border_color, " >" ); wrefresh( w_filter_help ); // This line is preventing this method from being const filter.query( false, true ); // Draw only } else { if( filter_str.empty() ) { mvwprintz( w, w_height - 1, border_width, border_color, _( "< Press %s to filter, %s to reset >" ), ctxt.get_desc( "FILTER" ), ctxt.get_desc( "RESET_FILTER" ) ); } else { mvwprintz( w, w_height - 1, border_width, border_color, "< %s >", filter_str ); mvwprintz( w, w_height - 1, border_width + 2, filter_color, "%s", filter_str ); } wrefresh( w ); } }
void Messages::display_messages() { catacurses::window w = catacurses::newwin( FULL_SCREEN_HEIGHT, FULL_SCREEN_WIDTH, ( TERMY > FULL_SCREEN_HEIGHT ) ? ( TERMY - FULL_SCREEN_HEIGHT ) / 2 : 0, ( TERMX > FULL_SCREEN_WIDTH ) ? ( TERMX - FULL_SCREEN_WIDTH ) / 2 : 0 ); input_context ctxt( "MESSAGE_LOG" ); ctxt.register_action( "UP", _( "Scroll up" ) ); ctxt.register_action( "DOWN", _( "Scroll down" ) ); ctxt.register_action( "QUIT" ); ctxt.register_action( "HELP_KEYBINDINGS" ); /* Right-Aligning Time Epochs For Readability * ========================================== * Given display_messages(); right-aligns epochs, we must declare one quick variable first: * max_padlength refers to the length of the LONGEST possible unit of time returned by to_string_clipped() any language has to offer. * This variable is, for now, set to '10', which seems most reasonable. * * The reason right-aligned epochs don't use a "shortened version" (e.g. only showing the first letter) boils down to: * 1. The first letter of every time unit being unique is a property that might not carry across to other languages. * 2. Languages where displayed characters change depending on the characters preceding/following it will become unclear. * 3. Some polymorphic languages might not be able to appropriately convey meaning with one character (FRS is not a linguist- correct her on this if needed.) * * This right padlength is then incorporated into a so-called 'epoch_format' which, in turn, will be used to format the correct epoch. * If an external language introduces time units longer than 10 characters in size, consider altering these variables. * The game (likely) shan't segfault, though the text may appear a bit messed up if these variables aren't set properly. */ const int max_padlength = 10; /* Dealing With Screen Extremities * =============================== * 'maxlength' corresponds to the most extreme length a log message may be before foldstring() wraps it around to two or more lines. * The numbers subtracted from FULL_SCREEN_WIDTH are - in order: * '-2' the characters reserved for the borders of the box, both on the left and right side. * '-1' the leftmost guide character that's drawn on screen. * '-4' the padded three-digit number each epoch starts with. * '-max_padlength' the characters of space that are allocated to time units (e.g. "years", "minutes", etc.) * * 'bottom' works much like 'maxlength', but instead it refers to the amount of lines that the message box may hold. */ const int maxlength = FULL_SCREEN_WIDTH - 2 - 1 - 4 - max_padlength; const int bottom = FULL_SCREEN_HEIGHT - 2; const int msg_count = size(); /* Dealing With Scroll Direction * ============================= * Much like how the sidebar can have variable scroll direction, so will the message box. * To properly differentiate the two methods of displaying text, we will label them NEWEST-TOP, and OLDEST-TOP. This labeling should be self explanatory. * * Note that 'offset' tracks only our current position in the list; it shan't at all affect the manner in which the messages are drawn. * Messages are always drawn top-to-bottom. If NEWEST-TOP is used, then the top line (line=1) corresponds to the newest message. The one further down the second-newest, etc. * If the OLDEST-TOP method is used, then the top line (line=1) corresponds to the oldest message, and the bottom one to the newest. * The 'for (;;)' block below is nearly completely method-agnostic, save for the `player_messages.impl_->history(i)` call. * * In case of NEWEST-TOP, the 'i' variable easily enough corresponds to the newest message. * In case of OLDEST-TOP, the 'i' variable must be flipped- meaning the highest value of 'i' returns the result for the lowest value of 'i', etc. * To achieve this, the 'flip_message' variable is set to either the value of 'msg_count', or '0'. This variable is then subtracted from 'i' in each call to player_messages.impl_->history(); * * 'offset' refers to the corresponding message that will be displayed at the very TOP of the message box window. * NEWEST-TOP: 'offset' starts simply at '0' - the very top of the window. * OLDEST-TOP: 'offset' is set to the maximum value it could possibly be. That is- 'msg_count-bottom'. This way, the screen starts with the scrollbar all the way down. * 'retrieve_history' refers to the line that should be displayed- this is either 'i' if it's NEWEST-TOP, or a flipped version of 'i' if it's OLDEST-TOP. */ int offset = log_from_top ? 0 : ( msg_count - bottom ); const int flip = log_from_top ? 0 : msg_count - 1; for( ;; ) { werase( w ); draw_border( w ); mvwprintz( w, bottom + 1, 32, c_red, _( "Press %s to return" ), ctxt.get_desc( "QUIT" ).c_str() ); draw_scrollbar( w, offset, bottom, msg_count, 1, 0, c_white, true ); int line = 1; int lasttime = -1; for( int i = offset; i < msg_count; ++i ) { const int retrieve_history = abs( i - flip ); if( line > bottom ) { break; // This statement makes it so that no non-existent messages are printed (which usually results in a segfault) } else if( retrieve_history >= msg_count ) { continue; } const game_message &m = player_messages.impl_->history( retrieve_history ); const calendar timepassed = calendar::turn - m.timestamp_in_turns; std::string long_ago = to_string_clipped( time_duration::from_turns( timepassed ) ); nc_color col = msgtype_to_color( m.type, false ); // Here we separate the unit and amount from one another so that they can be properly padded when they're drawn on the screen. // Note that the very first character of 'unit' is often a space (except for languages where the time unit directly follows the number.) const auto amount_len = long_ago.find_first_not_of( "0123456789" ); std::string amount = long_ago.substr( 0, amount_len ); std::string unit = long_ago.substr( amount_len ); if( timepassed.get_turn() != lasttime ) { right_print( w, line, 2, c_light_blue, string_format( _( "%-3s%-10s" ), amount.c_str(), unit.c_str() ) ); lasttime = timepassed.get_turn(); } nc_color col_out = col; for( const std::string &folded : foldstring( m.get_with_count(), maxlength ) ) { if( line > bottom ) { break; } print_colored_text( w, line, 2, col_out, col, folded ); // So-called special "markers"- alternating '=' and '-'s at the edges of te message window so players can properly make sense of which message belongs to which time interval. // The '+offset%4' in the calculation makes it so that the markings scroll along with the messages. // On lines divisible by 4, draw a dark gray '-' at both horizontal extremes of the window. if( ( line + offset % 4 ) % 4 == 0 ) { mvwprintz( w, line, 1, c_dark_gray, "-" ); mvwprintz( w, line, FULL_SCREEN_WIDTH - 2, c_dark_gray, "-" ); // On lines divisible by 2 (but not 4), draw a light gray '=' at the horizontal extremes of the window. } else if( ( line + offset % 4 ) % 2 == 0 ) { mvwprintz( w, line, 1, c_light_gray, "=" ); mvwprintz( w, line, FULL_SCREEN_WIDTH - 2, c_light_gray, "=" ); } // Only now are we done with this line: line++; } } if( offset < msg_count - bottom ) { mvwprintz( w, bottom + 1, 5, c_magenta, "vvv" ); } if( offset > 0 ) { mvwprintz( w, bottom + 1, FULL_SCREEN_WIDTH - 8, c_magenta, "^^^" ); } wrefresh( w ); const std::string &action = ctxt.handle_input(); if( action == "DOWN" && offset < msg_count - bottom ) { offset++; } else if( action == "UP" && offset > 0 ) { offset--; } else if( action == "QUIT" ) { break; } } player_messages.impl_->curmes = calendar::turn.get_turn(); }