From 9e5eeb2f98143eb554f8de28378ea617d1905bd4 Mon Sep 17 00:00:00 2001 From: mqrause Date: Sun, 13 Feb 2022 17:46:45 +0100 Subject: [PATCH] v menu rewrite --- data/raw/keybindings.json | 33 +- src/game.cpp | 185 +++----- src/game.h | 15 +- src/handle_action.cpp | 2 +- src/list_view.cpp | 116 +++++ src/list_view.h | 38 ++ src/list_view_draw_element.cpp | 114 +++++ src/map_item_stack.cpp | 139 ++++-- src/map_item_stack.h | 56 ++- src/output.cpp | 8 +- src/output.h | 1 + src/surroundings_menu.cpp | 840 +++++++++++++++++++++++++++++++++ src/surroundings_menu.h | 193 ++++++++ 13 files changed, 1536 insertions(+), 204 deletions(-) create mode 100644 src/list_view.cpp create mode 100644 src/list_view.h create mode 100644 src/list_view_draw_element.cpp create mode 100644 src/surroundings_menu.cpp create mode 100644 src/surroundings_menu.h diff --git a/data/raw/keybindings.json b/data/raw/keybindings.json index 9bef5beefda35..df7da9cf69479 100644 --- a/data/raw/keybindings.json +++ b/data/raw/keybindings.json @@ -2721,22 +2721,29 @@ }, { "type": "keybinding", - "id": "SCROLL_ITEM_INFO_UP", - "category": "LIST_ITEMS", + "category": "LIST_SURROUNDINGS", + "id": "FILTER", + "name": "Filter", + "bindings": [ { "input_method": "keyboard_any", "key": "/" }, { "input_method": "keyboard_code", "key": "KEYPAD_DIVIDE" } ] + }, + { + "type": "keybinding", + "id": "SCROLL_SURROUNDINGS_INFO_UP", + "category": "LIST_SURROUNDINGS", "name": "Scroll item info up", "bindings": [ { "input_method": "keyboard_char", "key": "<" }, { "input_method": "keyboard_code", "key": ",", "mod": [ "shift" ] } ] }, { "type": "keybinding", - "id": "SCROLL_ITEM_INFO_DOWN", - "category": "LIST_ITEMS", + "id": "SCROLL_SURROUNDINGS_INFO_DOWN", + "category": "LIST_SURROUNDINGS", "name": "Scroll item info down", "bindings": [ { "input_method": "keyboard_char", "key": ">" }, { "input_method": "keyboard_code", "key": ".", "mod": [ "shift" ] } ] }, { "type": "keybinding", "id": "COMPARE", - "category": "LIST_ITEMS", + "category": "LIST_SURROUNDINGS", "name": "Compare", "bindings": [ { "input_method": "keyboard_char", "key": "I" }, @@ -2749,7 +2756,7 @@ { "type": "keybinding", "id": "EXAMINE", - "category": "LIST_ITEMS", + "category": "LIST_SURROUNDINGS", "name": "Examine", "bindings": [ { "input_method": "keyboard_any", "key": "e" }, @@ -2760,7 +2767,7 @@ { "type": "keybinding", "id": "PRIORITY_INCREASE", - "category": "LIST_ITEMS", + "category": "LIST_SURROUNDINGS", "name": "Increase priority", "bindings": [ { "input_method": "keyboard_char", "key": "+" }, @@ -2771,14 +2778,14 @@ { "type": "keybinding", "id": "PRIORITY_DECREASE", - "category": "LIST_ITEMS", + "category": "LIST_SURROUNDINGS", "name": "Decrease priority", "bindings": [ { "input_method": "keyboard_any", "key": "-" }, { "input_method": "keyboard_code", "key": "KEYPAD_MINUS" } ] }, { "type": "keybinding", "id": "SORT", - "category": "LIST_ITEMS", + "category": "LIST_SURROUNDINGS", "name": "Change sort order", "bindings": [ { "input_method": "keyboard_any", "key": "s" }, @@ -2789,28 +2796,28 @@ { "type": "keybinding", "id": "SAFEMODE_BLACKLIST_ADD", - "category": "LIST_MONSTERS", + "category": "LIST_SURROUNDINGS", "name": "Add to safemode blacklist", "bindings": [ { "input_method": "keyboard_any", "key": "a" } ] }, { "type": "keybinding", "id": "SAFEMODE_BLACKLIST_REMOVE", - "category": "LIST_MONSTERS", + "category": "LIST_SURROUNDINGS", "name": "Remove from safemode blacklist", "bindings": [ { "input_method": "keyboard_any", "key": "r" } ] }, { "type": "keybinding", "id": "look", - "category": "LIST_MONSTERS", + "category": "LIST_SURROUNDINGS", "name": "look around", "bindings": [ { "input_method": "keyboard_any", "key": "x" } ] }, { "type": "keybinding", "id": "fire", - "category": "LIST_MONSTERS", + "category": "LIST_SURROUNDINGS", "name": { "ctxt": "verb", "str": "fire" }, "bindings": [ { "input_method": "keyboard_any", "key": "f" } ] }, diff --git a/src/game.cpp b/src/game.cpp index a2795bea7eb06..fbbc2d0392019 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -6896,7 +6896,7 @@ look_around_result game::look_around( const bool show_window, tripoint ¢er, action = ctxt.handle_input(); } if( action == "LIST_ITEMS" ) { - list_items_monsters(); + show_surroundings_menu(); } else if( action == "TOGGLE_FAST_SCROLL" ) { fast_scroll = !fast_scroll; } else if( action == "toggle_pixel_minimap" ) { @@ -7061,41 +7061,41 @@ look_around_result game::look_around( look_around_params looka_params ) looka_params.select_zone, looka_params.peeking ); } -std::vector game::find_nearby_items( int iRadius ) -{ - std::map temp_items; - std::vector ret; - std::vector item_order; - - if( u.is_blind() ) { - return ret; - } - - for( auto &points_p_it : closest_points_first( u.pos(), iRadius ) ) { - if( points_p_it.y >= u.posy() - iRadius && points_p_it.y <= u.posy() + iRadius && - u.sees( points_p_it ) && - m.sees_some_items( points_p_it, u ) ) { - - for( item &elem : m.i_at( points_p_it ) ) { - const std::string name = elem.tname(); - const tripoint relative_pos = points_p_it - u.pos(); - - if( std::find( item_order.begin(), item_order.end(), name ) == item_order.end() ) { - item_order.push_back( name ); - temp_items[name] = map_item_stack( &elem, relative_pos ); - } else { - temp_items[name].add_at_pos( &elem, relative_pos ); - } - } - } - } - - for( auto &elem : item_order ) { - ret.push_back( temp_items[elem] ); - } - - return ret; -} +//std::vector game::find_nearby_items( int iRadius ) +//{ +// std::map temp_items; +// std::vector ret; +// std::vector item_order; +// +// if( u.is_blind() ) { +// return ret; +// } +// +// for( auto &points_p_it : closest_points_first( u.pos(), iRadius ) ) { +// if( points_p_it.y >= u.posy() - iRadius && points_p_it.y <= u.posy() + iRadius && +// u.sees( points_p_it ) && +// m.sees_some_items( points_p_it, u ) ) { +// +// for( item &elem : m.i_at( points_p_it ) ) { +// const std::string name = elem.tname(); +// const tripoint relative_pos = points_p_it - u.pos(); +// +// if( std::find( item_order.begin(), item_order.end(), name ) == item_order.end() ) { +// item_order.push_back( name ); +// temp_items[name] = map_item_stack( &elem, relative_pos ); +// } else { +// temp_items[name].add_at_pos( &elem, relative_pos ); +// } +// } +// } +// } +// +// for( auto &elem : item_order ) { +// ret.push_back( temp_items[elem] ); +// } +// +// return ret; +//} void draw_trail( const tripoint &start, const tripoint &end, const bool bDrawX ) { @@ -7133,49 +7133,6 @@ void game::draw_trail_to_square( const tripoint &t, bool bDrawX ) ::draw_trail( u.pos(), u.pos() + t, bDrawX ); } -static void centerlistview( const tripoint &active_item_position, int ui_width ) -{ - Character &u = get_avatar(); - if( get_option( "SHIFT_LIST_ITEM_VIEW" ) != "false" ) { - u.view_offset.z = active_item_position.z; - if( get_option( "SHIFT_LIST_ITEM_VIEW" ) == "centered" ) { - u.view_offset.x = active_item_position.x; - u.view_offset.y = active_item_position.y; - } else { - point pos( active_item_position.xy() + point( POSX, POSY ) ); - - // item/monster list UI is on the right, so get the difference between its width - // and the width of the sidebar on the right (if any) - int sidebar_right_adjusted = ui_width - panel_manager::get_manager().get_width_right(); - // if and only if that difference is greater than zero, use that as offset - int right_offset = sidebar_right_adjusted > 0 ? sidebar_right_adjusted : 0; - - // Convert offset to tile counts, calculate adjusted terrain window width - // This lets us account for possible differences in terrain width between - // the normal sidebar and the list-all-whatever display. - to_map_font_dim_width( right_offset ); - int terrain_width = TERRAIN_WINDOW_WIDTH - right_offset; - - if( pos.x < 0 ) { - u.view_offset.x = pos.x; - } else if( pos.x >= terrain_width ) { - u.view_offset.x = pos.x - ( terrain_width - 1 ); - } else { - u.view_offset.x = 0; - } - - if( pos.y < 0 ) { - u.view_offset.y = pos.y; - } else if( pos.y >= TERRAIN_WINDOW_HEIGHT ) { - u.view_offset.y = pos.y - ( TERRAIN_WINDOW_HEIGHT - 1 ); - } else { - u.view_offset.y = 0; - } - } - } - -} - #if defined(TILES) static constexpr int MAXIMUM_ZOOM_LEVEL = 4; #endif @@ -7377,25 +7334,17 @@ void game::reset_item_list_state( const catacurses::window &window, int height, } } -void game::list_items_monsters() +void game::show_surroundings_menu() { // Search whole reality bubble because each function internally verifies // the visibilty of the items / monsters in question. std::vector mons = u.get_visible_creatures( 60 ); - const std::vector items = find_nearby_items( 60 ); + const std::vector> items;// = find_nearby_items(60); - if( mons.empty() && items.empty() ) { + /*if( mons.empty() && items.empty() ) { add_msg( m_info, _( "You don't see any items or monsters around you!" ) ); return; - } - - std::sort( mons.begin(), mons.end(), [&]( const Creature * lhs, const Creature * rhs ) { - const Creature::Attitude att_lhs = lhs->attitude_to( u ); - const Creature::Attitude att_rhs = rhs->attitude_to( u ); - - return att_lhs < att_rhs || ( att_lhs == att_rhs - && rl_dist( u.pos(), lhs->pos() ) < rl_dist( u.pos(), rhs->pos() ) ); - } ); + }*/ // If the current list is empty, switch to the non-empty list if( uistate.vmenu_show_items ) { @@ -7407,23 +7356,41 @@ void game::list_items_monsters() } temp_exit_fullscreen(); - game::vmenu_ret ret; - while( true ) { - ret = uistate.vmenu_show_items ? list_items( items ) : list_monsters( mons ); - if( ret == game::vmenu_ret::CHANGE_TAB ) { - uistate.vmenu_show_items = !uistate.vmenu_show_items; + + cata::optional path_start = u.pos(); + cata::optional path_end = cata::nullopt; + surroundings_menu vmenu( + 55, + std::min( 25, TERMY / 2 ), + u, + m, + path_end, + [&]() { + invalidate_main_ui_adaptor(); + }, + [&]( bool z_in ) { + if( z_in ) { + zoom_in(); } else { - break; + zoom_out(); } - } + mark_main_ui_adaptor_resize(); + }, + [&]() { + look_around(); + } ); + + shared_ptr_fast trail_cb = create_trail_callback( path_start, path_end, true ); + add_draw_callback( trail_cb ); - if( ret == game::vmenu_ret::FIRE ) { + if( vmenu.execute() == surroundings_menu_ret::fire ) { avatar_action::fire_wielded_weapon( u ); } + reenter_fullscreen(); } -game::vmenu_ret game::list_items( const std::vector &item_list ) +/*vmenu_ret game::list_items(const std::vector& item_list) { std::vector ground_items = item_list; int iInfoHeight = 0; @@ -7868,7 +7835,7 @@ game::vmenu_ret game::list_items( const std::vector &item_list ) mark_main_ui_adaptor_resize(); } else if( action == "NEXT_TAB" || action == "PREV_TAB" ) { u.view_offset = stored_view_offset; - return game::vmenu_ret::CHANGE_TAB; + return vmenu_ret::nexttab; } active_pos = tripoint_zero; @@ -7907,10 +7874,10 @@ game::vmenu_ret game::list_items( const std::vector &item_list ) } while( action != "QUIT" ); u.view_offset = stored_view_offset; - return game::vmenu_ret::QUIT; -} + return vmenu_ret::quit; +}*/ -game::vmenu_ret game::list_monsters( const std::vector &monster_list ) +surroundings_menu_ret game::list_monsters( const std::vector &monster_list ) { const int iInfoHeight = 15; const int width = 55; @@ -7945,7 +7912,7 @@ game::vmenu_ret game::list_monsters( const std::vector &monster_list TERMY - iInfoHeight ) ); if( cCurMon ) { - centerlistview( iActivePos, width ); + //centerlistview( iActivePos, width ); } ui.position( point( offsetX, 0 ), point( width, TERMY ) ); @@ -8200,7 +8167,7 @@ game::vmenu_ret game::list_monsters( const std::vector &monster_list } } else if( action == "NEXT_TAB" || action == "PREV_TAB" ) { u.view_offset = stored_view_offset; - return game::vmenu_ret::CHANGE_TAB; + return surroundings_menu_ret::nexttab; } else if( action == "zoom_in" ) { zoom_in(); mark_main_ui_adaptor_resize(); @@ -8233,14 +8200,14 @@ game::vmenu_ret game::list_monsters( const std::vector &monster_list u.last_target = shared_from( *cCurMon ); u.recoil = MAX_RECOIL; u.view_offset = stored_view_offset; - return game::vmenu_ret::FIRE; + return surroundings_menu_ret::fire; } } if( iActive >= 0 && static_cast( iActive ) < monster_list.size() ) { cCurMon = monster_list[iActive]; iActivePos = cCurMon->pos() - u.pos(); - centerlistview( iActivePos, width ); + //centerlistview( iActivePos, width ); trail_start = u.pos(); trail_end = cCurMon->pos(); // Actually accessed from the terrain overlay callback `trail_cb` in the @@ -8262,7 +8229,7 @@ game::vmenu_ret game::list_monsters( const std::vector &monster_list u.view_offset = stored_view_offset; - return game::vmenu_ret::QUIT; + return surroundings_menu_ret::quit; } void game::unload_container() diff --git a/src/game.h b/src/game.h index eb7afaad6b765..945e0e8cb1758 100644 --- a/src/game.h +++ b/src/game.h @@ -34,6 +34,7 @@ #include "type_id.h" #include "uistate.h" #include "units_fwd.h" +#include "surroundings_menu.h" #include "weather.h" class Character; @@ -790,19 +791,13 @@ class game const vproto_id &id, const point_abs_omt &origin, int min_distance, int max_distance, const std::vector &omt_search_types = {} ); // V Menu Functions and helpers: - void list_items_monsters(); // Called when you invoke the `V`-menu + void show_surroundings_menu(); // Called when you invoke the `V`-menu - enum class vmenu_ret : int { - CHANGE_TAB, - QUIT, - FIRE, // Who knew, apparently you can do that in list_monsters - }; - - game::vmenu_ret list_items( const std::vector &item_list ); - std::vector find_nearby_items( int iRadius ); + //vmenu_ret list_items( const std::vector &item_list ); + //std::vector find_nearby_items( int iRadius ); void reset_item_list_state( const catacurses::window &window, int height, bool bRadiusSort ); - game::vmenu_ret list_monsters( const std::vector &monster_list ); + surroundings_menu_ret list_monsters( const std::vector &monster_list ); /** Check for dangerous stuff at dest_loc, return false if the player decides not to step there */ diff --git a/src/handle_action.cpp b/src/handle_action.cpp index 9285ccf3eb24e..c5c5ec44bdd4f 100644 --- a/src/handle_action.cpp +++ b/src/handle_action.cpp @@ -2106,7 +2106,7 @@ bool game::do_regular_action( action_id &act, avatar &player_character, break; case ACTION_LIST_ITEMS: - list_items_monsters(); + show_surroundings_menu(); break; case ACTION_ZONES: diff --git a/src/list_view.cpp b/src/list_view.cpp new file mode 100644 index 0000000000000..15a22b36827bc --- /dev/null +++ b/src/list_view.cpp @@ -0,0 +1,116 @@ +#include "list_view.h" + +#include "item.h" +#include "mapdata.h" +#include "map_item_stack.h" +#include "monster.h" +#include "output.h" +#include "point.h" + +template +void list_view::draw( const catacurses::window &w, const point &p ) +{ + int start_pos = 0; + calcStartPos( start_pos, selected_index, height - 1, list_elements.size() ); + int max_size = std::min( height - 1, list_elements.size() ); + for( int i = 0; i < max_size; i++ ) { + T *element = list_elements[start_pos + i]; + if( element ) { + draw_element( w, p + point( 1, i ), width, element, element == get_selected_element() ); + } + } +} + +template +T *list_view::get_selected_element() +{ + if( !list_elements.empty() ) { + return list_elements[selected_index]; + } + return nullptr; +} + +template +int list_view::get_selected_index() +{ + return selected_index; +} + +template +int list_view::get_count() +{ + return list_elements.size(); +} + +template +void list_view::select_next() +{ + if( list_elements.empty() ) { + return; + } + + selected_index++; + + if( selected_index == static_cast( list_elements.size() ) ) { + selected_index = 0; + } +} + +template +void list_view::select_prev() +{ + if( list_elements.empty() ) { + return; + } + if( selected_index == 0 ) { + selected_index = list_elements.size(); + } + + selected_index--; +} + +template +void list_view::select_page_up() +{ + if( list_elements.empty() ) { + return; + } + + if( selected_index == 0 ) { + selected_index = list_elements.size() - 1; + } else if( selected_index - page_scroll < 0 ) { + selected_index = 0; + } else { + selected_index -= page_scroll; + } +} + +template +void list_view::select_page_down() +{ + if( list_elements.empty() ) { + return; + } + + const int size = list_elements.size(); + + if( selected_index == size - 1 ) { + selected_index = 0; + } else if( selected_index + page_scroll >= size ) { + selected_index = size - 1; + } else { + selected_index += page_scroll; + } +} + +template +void list_view::set_elements( std::vector new_list ) +{ + selected_index = 0; + list_elements = new_list; +} + +// explicit template instantiation +template class list_view>; +template class list_view>; +template class list_view>; diff --git a/src/list_view.h b/src/list_view.h new file mode 100644 index 0000000000000..b769156ad3800 --- /dev/null +++ b/src/list_view.h @@ -0,0 +1,38 @@ +#pragma once +#ifndef CATA_SRC_LIST_VIEW_H +#define CATA_SRC_LIST_VIEW_H + +#include + +#include "cursesdef.h" + +template +class list_view +{ + public: + list_view( const int height, + const int width ) : + height( height ), + width( width ) {}; + + T *get_selected_element(); + int get_selected_index(); + int get_count(); + void select_next(); + void select_prev(); + void select_page_up(); + void select_page_down(); + void set_elements( std::vector new_list ); + void draw( const catacurses::window &w, const point &p ); + void draw_element( const catacurses::window &w, const point &p, const int width, const T *element, + const bool selected ); + + private: + std::vector list_elements; + const int height; + const int width; + int selected_index = 0; + int page_scroll = 10; +}; + +#endif // CATA_SRC_LIST_VIEW_H diff --git a/src/list_view_draw_element.cpp b/src/list_view_draw_element.cpp new file mode 100644 index 0000000000000..3427a60c2dbb6 --- /dev/null +++ b/src/list_view_draw_element.cpp @@ -0,0 +1,114 @@ +#include "list_view.h" + +#include "item.h" +#include "line.h" +#include "mapdata.h" +#include "map_item_stack.h" +#include "monster.h" +#include "output.h" +#include "point.h" + +template +void list_view::draw_element( const catacurses::window &, const point &, const int, + const T *, const bool ) +{ + debugmsg( _( "List view has no draw function for this type of element." ) ); +} + +// NOLINTNEXTLINE(cata-xy) +static void draw_distance( const catacurses::window &w, const int cursor_x, const int cursor_y, + const tripoint &p1, const tripoint &p2, const bool selected ) +{ + nc_color dist_color = selected ? c_light_green : c_light_gray; + const int dist = rl_dist( p1, p2 ); + const int num_width = dist > 9 ? 2 : 1; + mvwprintz( w, point( cursor_x - num_width, cursor_y ), dist_color, "%*d %s", + num_width, dist, direction_name_short( direction_from( p1, p2 ) ) ); + +} + +template<> +void list_view>::draw_element( const catacurses::window &w, const point &p, + const int width, const map_entity_stack *element, + const bool selected ) +{ + if( element ) { + const item *it = element->get_selected_entity(); + if( it ) { + std::string text; + if( element->entities.size() > 1 ) { + text = string_format( "[%d/%d] (%d) ", element->get_selected_index() + 1, element->entities.size(), + element->totalcount ); + } + + const int count = element->get_selected_count(); + if( count > 1 ) { + text += string_format( "%d ", count ); + } + + text += it->display_name( count ); + + nc_color color = it->color_in_inventory(); + if( selected ) { + color = hilite( color ); + } + + trim_and_print( w, p, width - 9, color, text ); + + draw_distance( w, width - 5, p.y, tripoint_zero, *element->get_selected_pos(), + selected ); + } + } +} + +template<> +void list_view>::draw_element( const catacurses::window &w, + const point &p, const int width, const map_entity_stack *element, + const bool selected ) +{ + if( element ) { + const Creature *mon = element->get_selected_entity(); + if( mon ) { + std::string text; + const monster *m = dynamic_cast( mon ); + if( m != nullptr ) { + text = m->name(); + } else { + text = mon->disp_name(); + } + + nc_color color = mon->basic_symbol_color(); + if( selected ) { + color = hilite( color ); + } + trim_and_print( w, p, width - 9, color, text ); + + draw_distance( w, width - 5, p.y, tripoint_zero, *element->get_selected_pos(), selected ); + } + } +} + +template<> +void list_view>::draw_element( const catacurses::window &w, + const point &p, const int width, const map_entity_stack *element, + const bool selected ) +{ + if( element ) { + const map_data_common_t *terfurn = element->get_selected_entity(); + if( terfurn ) { + std::string text; + if( element->entities.size() > 1 ) { + text = string_format( "[%d/%d] ", element->get_selected_index() + 1, element->entities.size() ); + } + text += terfurn->name(); + + nc_color color = terfurn->color(); + if( selected ) { + color = hilite( color ); + } + trim_and_print( w, p, width - 9, color, text ); + + draw_distance( w, width - 5, p.y, tripoint_zero, *element->get_selected_pos(), selected ); + } + } +} diff --git a/src/map_item_stack.cpp b/src/map_item_stack.cpp index 4d18fb414bdd9..7335b874b5aac 100644 --- a/src/map_item_stack.cpp +++ b/src/map_item_stack.cpp @@ -1,83 +1,127 @@ #include "map_item_stack.h" -#include -#include -#include - #include "item.h" #include "item_category.h" #include "item_search.h" -#include "line.h" +#include "mapdata.h" -map_item_stack::item_group::item_group() : count( 0 ), it( nullptr ) +template +const T *map_entity_stack::get_selected_entity() const { + if( !entities.empty() ) { + // todo: check index + return entities[selected_index].entity; + } + return nullptr; } -map_item_stack::item_group::item_group( const tripoint &p, const int arg_count, - const item *itm ) : pos( p ), - count( arg_count ), it( itm ) +template +cata::optional map_entity_stack::get_selected_pos() const { + if( !entities.empty() ) { + // todo: check index + return entities[selected_index].pos; + } + return cata::nullopt; } -map_item_stack::map_item_stack() : example( nullptr ), totalcount( 0 ) +template +int map_entity_stack::get_selected_count() const { - vIG.emplace_back( ); + if( !entities.empty() ) { + // todo: check index + return entities[selected_index].count; + } + return 0; } -map_item_stack::map_item_stack( const item *const it, const tripoint &pos ) : example( it ), - totalcount( it->count() ) +template +void map_entity_stack::select_next() { - vIG.emplace_back( pos, totalcount, it ); + if( entities.empty() ) { + return; + } + + selected_index++; + + if( selected_index >= static_cast( entities.size() ) ) { + selected_index = 0; + } } -void map_item_stack::add_at_pos( const item *const it, const tripoint &pos ) +template +void map_entity_stack::select_prev() { - const int amount = it->count(); + if( entities.empty() ) { + return; + } - if( vIG.empty() || vIG.back().pos != pos ) { - vIG.emplace_back( pos, amount, it ); - } else { - vIG.back().count += amount; + if( selected_index <= 0 ) { + selected_index = entities.size(); } - totalcount += amount; + selected_index--; } -bool map_item_stack::map_item_stack_sort( const map_item_stack &lhs, const map_item_stack &rhs ) +template +int map_entity_stack::get_selected_index() const { - const item_category &lhs_cat = lhs.example->get_category_of_contents(); - const item_category &rhs_cat = rhs.example->get_category_of_contents(); + return selected_index; +} - if( lhs_cat == rhs_cat ) { - return square_dist( tripoint_zero, lhs.vIG[0].pos ) < - square_dist( tripoint_zero, rhs.vIG[0].pos ); +template +map_entity_stack::map_entity_stack() : totalcount( 0 ) +{ + entities.emplace_back(); +} + +template +map_entity_stack::map_entity_stack( const T *const entity, const tripoint &pos, + const int count ) : totalcount( count ) +{ + entities.emplace_back( pos, 1, entity ); +} + +template +void map_entity_stack::add_at_pos( const T *const entity, const tripoint &pos, const int count ) +{ + if( entities.empty() || entities.back().pos != pos ) { + entities.emplace_back( pos, 1, entity ); + } else { + entities.back().count++; } - return lhs_cat < rhs_cat; + totalcount += count; +} + +template +bool map_entity_stack::map_entity_stack_compare( const map_entity_stack & ) +{ + return false; } -std::vector filter_item_stacks( const std::vector &stack, - const std::string &filter ) +template<> +bool map_entity_stack::map_entity_stack_compare( const map_entity_stack &rhs ) { - std::vector ret; + const item_category &lhs_cat = get_selected_entity()->get_category_of_contents(); + const item_category &rhs_cat = rhs.get_selected_entity()->get_category_of_contents(); - auto z = item_filter_from_string( filter ); - std::copy_if( stack.begin(), stack.end(), - std::back_inserter( ret ), [z]( const map_item_stack & a ) { - if( a.example != nullptr ) { - return z( *a.example ); - } - return false; - } ); + if( lhs_cat == rhs_cat ) { + return square_dist( tripoint_zero, entities[0].pos ) < + square_dist( tripoint_zero, rhs.entities[0].pos ); + } - return ret; + return lhs_cat < rhs_cat; } //returns the first non priority items. -int list_filter_high_priority( std::vector &stack, const std::string &priorities ) +template +int list_filter_high_priority( std::vector> &stack, + const std::string &priorities ) { + // todo: actually use it and specialization (only used for items) // TODO:optimize if necessary - std::vector tempstack; + std::vector> tempstack; const auto filter_fn = item_filter_from_string( priorities ); for( auto it = stack.begin(); it != stack.end(); ) { if( priorities.empty() || ( it->example != nullptr && !filter_fn( *it->example ) ) ) { @@ -95,11 +139,13 @@ int list_filter_high_priority( std::vector &stack, const std::st return id; } -int list_filter_low_priority( std::vector &stack, const int start, +template +int list_filter_low_priority( std::vector> &stack, const int start, const std::string &priorities ) { + // todo: actually use it and specialization (only used for items) // TODO:optimize if necessary - std::vector tempstack; + std::vector> tempstack; const auto filter_fn = item_filter_from_string( priorities ); for( auto it = stack.begin() + start; it != stack.end(); ) { if( !priorities.empty() && it->example != nullptr && filter_fn( *it->example ) ) { @@ -116,3 +162,8 @@ int list_filter_low_priority( std::vector &stack, const int star } return id; } + +// explicit template instantiation +template class map_entity_stack; +template class map_entity_stack; +template class map_entity_stack; diff --git a/src/map_item_stack.h b/src/map_item_stack.h index 688379139111a..8c2a7d6a28dc0 100644 --- a/src/map_item_stack.h +++ b/src/map_item_stack.h @@ -2,49 +2,57 @@ #ifndef CATA_SRC_MAP_ITEM_STACK_H #define CATA_SRC_MAP_ITEM_STACK_H -#include -#include - +#include "optional.h" #include "point.h" -class item; - -class map_item_stack +template +class map_entity_stack { private: - class item_group + class entity_group { public: tripoint pos; int count; - const item *it; + const T *entity; //only expected to be used for things like lists and vectors - item_group(); - item_group( const tripoint &p, int arg_count, const item *itm ); + entity_group() : count( 0 ), entity( nullptr ) {}; + entity_group( const tripoint &p, int arg_count, const T *entity ) : + pos( p ), count( arg_count ), entity( entity ) {}; }; + + int selected_index = 0; public: - const item *example; //an example item for showing stats, etc. - std::vector vIG; + std::vector entities; int totalcount; + const T *get_selected_entity() const; + cata::optional get_selected_pos() const; + int get_selected_count() const; + int get_selected_index() const; + + void select_next(); + void select_prev(); + //only expected to be used for things like lists and vectors - map_item_stack(); - map_item_stack( const item *it, const tripoint &pos ); + map_entity_stack(); + map_entity_stack( const T *entity, const tripoint &pos, const int count = 1 ); - // This adds to an existing item group if the last current - // item group is the same position and otherwise creates and - // adds to a new item group. Note that it does not search - // through all older item groups for a match. - void add_at_pos( const item *it, const tripoint &pos ); + // This adds to an existing entity group if the last current + // entity group is the same position and otherwise creates and + // adds to a new entity group. Note that it does not search + // through all older entity groups for a match. + void add_at_pos( const T *entity, const tripoint &pos, const int count = 1 ); - static bool map_item_stack_sort( const map_item_stack &lhs, const map_item_stack &rhs ); + bool map_entity_stack_compare( const map_entity_stack &rhs ); }; -std::vector filter_item_stacks( const std::vector &stack, - const std::string &filter ); -int list_filter_high_priority( std::vector &stack, const std::string &priorities ); -int list_filter_low_priority( std::vector &stack, int start, +template +int list_filter_high_priority( std::vector> &stack, + const std::string &priorities ); +template +int list_filter_low_priority( std::vector> &stack, int start, const std::string &priorities ); #endif // CATA_SRC_MAP_ITEM_STACK_H diff --git a/src/output.cpp b/src/output.cpp index f9c971ab56e65..15d8c916c57ba 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -1057,9 +1057,11 @@ input_event draw_item_info( const std::function &init_wind } } } - draw_scrollbar( win, *data.ptr_selected, height, folded.size(), - point( data.scrollbar_left ? 0 : getmaxx( win ) - 1, - ( data.without_border && data.use_full_win ? 0 : 1 ) ), BORDER_COLOR, true ); + if( !data.without_scrollbar ) { + draw_scrollbar( win, *data.ptr_selected, height, folded.size(), + point( data.scrollbar_left ? 0 : getmaxx( win ) - 1, + ( data.without_border && data.use_full_win ? 0 : 1 ) ), BORDER_COLOR, true ); + } if( !data.without_border ) { draw_custom_border( win, buffer.empty() ); } diff --git a/src/output.h b/src/output.h index 52a962e23e1bd..8f0f3ba974635 100644 --- a/src/output.h +++ b/src/output.h @@ -562,6 +562,7 @@ struct item_info_data { int *ptr_selected = &selected; bool without_getch = false; bool without_border = false; + bool without_scrollbar = false; bool handle_scrolling = false; bool any_input = true; bool scrollbar_left = true; diff --git a/src/surroundings_menu.cpp b/src/surroundings_menu.cpp new file mode 100644 index 0000000000000..32e2cc23eb721 --- /dev/null +++ b/src/surroundings_menu.cpp @@ -0,0 +1,840 @@ +#include "surroundings_menu.h" + +#include "game_inventory.h" +#include "game_ui.h" +#include "item.h" +#include "item_search.h" +#include "line.h" +#include "messages.h" +#include "monster.h" +#include "options.h" +#include "panels.h" +#include "safemode_ui.h" +#include "string_input_popup.h" +#include "ui_manager.h" + +static surroundings_menu_tab_enum &operator++( surroundings_menu_tab_enum &c ) +{ + c = static_cast( static_cast( c ) + 1 ); + if( c == surroundings_menu_tab_enum::num_tabs ) { + c = static_cast( 0 ); + } + return c; +} + +static surroundings_menu_tab_enum &operator--( surroundings_menu_tab_enum &c ) +{ + if( c == static_cast( 0 ) ) { + c = surroundings_menu_tab_enum::num_tabs; + } + c = static_cast( static_cast( c ) - 1 ); + return c; +} + +static void get_string_input( const std::string &title, const int width, std::string &edit, + const std::string &identifier ) +{ + return string_input_popup() + .title( title ) + .width( width ) + .description( _( "UP: history, CTRL-U clear line, ESC: abort, ENTER: save" ) ) + .identifier( identifier ) + .max_length( 256 ) + .edit( edit ); +} + +static input_context get_base_context() +{ + input_context ctxt( "LIST_SURROUNDINGS" ); + ctxt.register_action( "NEXT_TAB" ); + ctxt.register_action( "PREV_TAB" ); + ctxt.register_action( "HELP_KEYBINDINGS" ); + ctxt.register_action( "QUIT" ); + ctxt.register_action( "UP" ); + ctxt.register_action( "DOWN" ); + ctxt.register_action( "LEFT" ); + ctxt.register_action( "RIGHT" ); + ctxt.register_action( "look" ); + ctxt.register_action( "zoom_in" ); + ctxt.register_action( "zoom_out" ); + ctxt.register_action( "PAGE_UP" ); + ctxt.register_action( "PAGE_DOWN" ); + ctxt.register_action( "SCROLL_SURROUNDINGS_INFO_UP" ); + ctxt.register_action( "SCROLL_SURROUNDINGS_INFO_DOWN" ); + ctxt.register_action( "FILTER" ); + ctxt.register_action( "RESET_FILTER" ); + ctxt.register_action( "SORT" ); + ctxt.register_action( "TRAVEL_TO" ); + + return ctxt; +} + +static input_context get_item_context() +{ + input_context ctxt = get_base_context(); + ctxt.register_action( "EXAMINE" ); + ctxt.register_action( "COMPARE" ); + ctxt.register_action( "PRIORITY_INCREASE" ); + ctxt.register_action( "PRIORITY_DECREASE" ); + + return ctxt; +} + +static input_context get_monster_context() +{ + input_context ctxt = get_base_context(); + ctxt.register_action( "fire" ); + ctxt.register_action( "SAFEMODE_BLACKLIST_ADD" ); + ctxt.register_action( "SAFEMODE_BLACKLIST_REMOVE" ); + + return ctxt; +} + +static input_context get_terfurn_context() +{ + input_context ctxt = get_base_context(); + + return ctxt; +} + +static void add_item_recursive( std::vector &item_order, + std::map> &temp_items, const item *it, + const tripoint &relative_pos ) +{ + const std::string name = it->tname(); + + if( std::find( item_order.begin(), item_order.end(), name ) == item_order.end() ) { + item_order.push_back( name ); + + temp_items[name] = map_entity_stack( it, relative_pos, it->count() ); + } else { + temp_items[name].add_at_pos( it, relative_pos, it->count() ); + } + + for( const item *content : it->all_items_top() ) { + add_item_recursive( item_order, temp_items, content, relative_pos ); + } +} + +static void find_nearby_items( std::map> + &temp_items, std::vector*> &items, const Character &you, map &m, + const int radius ) +{ + std::vector item_order; + + if( you.is_blind() ) { + return; + } + + tripoint pos = you.pos(); + + for( tripoint &points_p_it : closest_points_first( pos, radius ) ) { + if( points_p_it.y >= pos.y - radius && points_p_it.y <= pos.y + radius && + you.sees( points_p_it ) && m.sees_some_items( points_p_it, you ) ) { + + for( item &elem : m.i_at( points_p_it ) ) { + const tripoint relative_pos = points_p_it - pos; + add_item_recursive( item_order, temp_items, &elem, relative_pos ); + } + } + } + + for( const std::string &elem : item_order ) { + // todo: remove this extra sorting when closest_points_first supports circular distances + std::sort( temp_items[elem].entities.begin(), temp_items[elem].entities.end(), + []( const auto & lhs, const auto & rhs ) { + return rl_dist( tripoint_zero, lhs.pos ) < rl_dist( tripoint_zero, rhs.pos ); + } ); + items.push_back( &temp_items[elem] ); + } + + // todo: remove this extra sorting when closest_points_first supports circular distances + std::sort( items.begin(), items.end(), [&you]( const map_entity_stack *lhs, + const map_entity_stack *rhs ) { + return rl_dist( you.pos(), you.pos() + *lhs->get_selected_pos() ) < rl_dist( you.pos(), + you.pos() + *rhs->get_selected_pos() ); + } ); +} + +static void add_terfurn( std::vector &item_order, + std::map> &temp_items, + const map_data_common_t *terfurn, const tripoint &relative_pos ) +{ + const std::string name = terfurn->name(); + + if( std::find( item_order.begin(), item_order.end(), name ) == item_order.end() ) { + item_order.push_back( name ); + + temp_items[name] = map_entity_stack( terfurn, relative_pos ); + } else { + temp_items[name].add_at_pos( terfurn, relative_pos ); + } +} + +static void find_nearby_terfurn( + std::map> &temp_terfurn, + std::vector*> &terfurn, const Character &you, map &m, + const int radius ) +{ + std::vector item_order; + + if( you.is_blind() ) { + return; + } + + tripoint pos = you.pos(); + + for( tripoint &points_p_it : closest_points_first( pos, radius ) ) { + if( points_p_it.y >= pos.y - radius && points_p_it.y <= pos.y + radius && + you.sees( points_p_it ) ) { + const tripoint relative_pos = points_p_it - pos; + + ter_id ter = m.ter( points_p_it ); + add_terfurn( item_order, temp_terfurn, &*ter, relative_pos ); + + if( m.has_furn( points_p_it ) ) { + furn_id furn = m.furn( points_p_it ); + add_terfurn( item_order, temp_terfurn, &*furn, relative_pos ); + } + } + } + + for( const std::string &elem : item_order ) { + // todo: remove this extra sorting when closest_points_first supports circular distances + std::sort( temp_terfurn[elem].entities.begin(), temp_terfurn[elem].entities.end(), + []( const auto & lhs, const auto & rhs ) { + return rl_dist( tripoint_zero, lhs.pos ) < rl_dist( tripoint_zero, rhs.pos ); + } ); + terfurn.push_back( &temp_terfurn[elem] ); + } + + // todo: remove this extra sorting when closest_points_first supports circular distances + std::sort( terfurn.begin(), terfurn.end(), [&you]( const map_entity_stack *lhs, + const map_entity_stack *rhs ) { + return rl_dist( you.pos(), you.pos() + *lhs->get_selected_pos() ) < rl_dist( you.pos(), + you.pos() + *rhs->get_selected_pos() ); + } ); +} + +static void find_nearby_monsters( std::vector> + &temp_monsters, std::vector*> &monsters, const Character &you, + const int radius ) +{ + std::vector mons = you.get_visible_creatures( radius ); + + std::sort( mons.begin(), mons.end(), [&]( const Creature * lhs, const Creature * rhs ) { + const Creature::Attitude att_lhs = lhs->attitude_to( you ); + const Creature::Attitude att_rhs = rhs->attitude_to( you ); + + return att_lhs < att_rhs || ( att_lhs == att_rhs + && rl_dist( you.pos(), lhs->pos() ) < rl_dist( you.pos(), rhs->pos() ) ); + } ); + + for( Creature *mon : mons ) { + tripoint pos = mon->pos() - you.pos(); + map_entity_stack mstack( mon, pos ); + temp_monsters.push_back( mstack ); + } + for( map_entity_stack &mon : temp_monsters ) { + monsters.push_back( &mon ); + } +} + +items_tab::items_tab( const std::string title, avatar &you, map &m, + cata::optional &path_end, + int width, int info_height, const std::function &invalidate_main_ui, + const std::function &zoom_callback, + const std::function &look_callback ) : + surroundings_menu_tab( title, you, m, path_end, width, info_height, invalidate_main_ui, + zoom_callback, look_callback ) +{ + find_nearby_items( items, elements, you, m, radius ); + list.set_elements( elements ); + ctxt = get_item_context(); +} + +monsters_tab::monsters_tab( const std::string title, avatar &you, map &m, + cata::optional &path_end, + int width, int info_height, const std::function &invalidate_main_ui, + const std::function &zoom_callback, + const std::function &look_callback ) : + surroundings_menu_tab( title, you, m, path_end, width, info_height, invalidate_main_ui, + zoom_callback, look_callback ) +{ + find_nearby_monsters( monsters, elements, you, radius ); + list.set_elements( elements ); + ctxt = get_monster_context(); +} + +terfurn_tab::terfurn_tab( const std::string title, avatar &you, map &m, + cata::optional &path_end, + int width, int info_height, const std::function &invalidate_main_ui, + const std::function &zoom_callback, + const std::function &look_callback ) : + surroundings_menu_tab( title, you, m, path_end, width, info_height, invalidate_main_ui, + zoom_callback, look_callback ) +{ + find_nearby_terfurn( terfurn, elements, you, m, radius ); + list.set_elements( elements ); + ctxt = get_terfurn_context(); +} + +surroundings_menu_ret terfurn_tab::execute() +{ + shared_ptr_fast ui = create_or_get_ui_adaptor(); + + while( true ) { + centerlistview( you, list.get_selected_element(), width ); + + update_path_end(); + invalidate_main_ui(); + + ui_manager::redraw(); + const std::string &action = ctxt.handle_input(); + + if( action == "NEXT_TAB" ) { + you.view_offset = stored_view_offset; + return surroundings_menu_ret::nexttab; + } else if( action == "PREV_TAB" ) { + you.view_offset = stored_view_offset; + return surroundings_menu_ret::prevtab; + } else if( action == "QUIT" ) { + you.view_offset = stored_view_offset; + return surroundings_menu_ret::quit; + } else if( action == "TRAVEL_TO" && path_end ) { + you.view_offset = stored_view_offset; + if( !you.sees( *path_end ) ) { + add_msg( _( "You can't see that destination." ) ); + } + std::vector route = m.route( you.pos(), *path_end, you.get_pathfinding_settings(), + you.get_path_avoid() ); + if( route.size() > 1 ) { + route.pop_back(); + you.set_destination( route ); + break; + } else { + add_msg( m_info, _( "You can't travel there." ) ); + } + } else { + on_input( action ); + } + } + + return surroundings_menu_ret::quit; +} + +surroundings_menu_ret items_tab::execute() +{ + shared_ptr_fast ui = create_or_get_ui_adaptor(); + + while( true ) { + centerlistview( you, list.get_selected_element(), width ); + + update_path_end(); + invalidate_main_ui(); + + ui_manager::redraw(); + const std::string &action = ctxt.handle_input(); + + if( action == "NEXT_TAB" ) { + you.view_offset = stored_view_offset; + return surroundings_menu_ret::nexttab; + } else if( action == "PREV_TAB" ) { + you.view_offset = stored_view_offset; + return surroundings_menu_ret::prevtab; + } else if( action == "QUIT" ) { + you.view_offset = stored_view_offset; + return surroundings_menu_ret::quit; + } else if( action == "TRAVEL_TO" && path_end ) { + you.view_offset = stored_view_offset; + if( !you.sees( *path_end ) ) { + add_msg( _( "You can't see that destination." ) ); + } + std::vector route = m.route( you.pos(), *path_end, you.get_pathfinding_settings(), + you.get_path_avoid() ); + if( route.size() > 1 ) { + route.pop_back(); + you.set_destination( route ); + break; + } else { + add_msg( m_info, _( "You can't travel there." ) ); + } + } else if( action == "COMPARE" ) { + game_menus::inv::compare( you, path_end ); + } else if( action == "PRIORITY_INCREASE" ) { + get_string_input( _( "High Priority:" ), width, list_item_upvote, "list_item_priority" ); + } else if( action == "PRIORITY_DECREASE" ) { + get_string_input( _( "Low Priority:" ), width, list_item_downvote, "list_item_downvote" ); + } else { + on_input( action ); + } + } + + return surroundings_menu_ret::quit; +} + +surroundings_menu_ret monsters_tab::execute() +{ + shared_ptr_fast ui = create_or_get_ui_adaptor(); + + const int max_gun_range = you.get_wielded_item().gun_range( &you ); + + while( true ) { + centerlistview( you, list.get_selected_element(), width ); + + update_path_end(); + invalidate_main_ui(); + + ui_manager::redraw(); + const std::string &action = ctxt.handle_input(); + + if( action == "NEXT_TAB" ) { + you.view_offset = stored_view_offset; + return surroundings_menu_ret::nexttab; + } else if( action == "PREV_TAB" ) { + you.view_offset = stored_view_offset; + return surroundings_menu_ret::prevtab; + } else if( action == "TRAVEL_TO" && path_end ) { + you.view_offset = stored_view_offset; + if( !you.sees( *path_end ) ) { + add_msg( _( "You can't see that destination." ) ); + } + std::vector route = m.route( you.pos(), *path_end, you.get_pathfinding_settings(), + you.get_path_avoid() ); + if( route.size() > 1 ) { + route.pop_back(); + you.set_destination( route ); + break; + } else { + add_msg( m_info, _( "You can't travel there." ) ); + } + } else if( action == "QUIT" ) { + you.view_offset = stored_view_offset; + return surroundings_menu_ret::quit; + } else if( action == "fire" && path_end && rl_dist( you.pos(), *path_end ) <= max_gun_range ) { + // only bound for creatures, but can shoot at any location + you.last_target_pos = *path_end; + you.recoil = MAX_RECOIL; + you.view_offset = stored_view_offset; + return surroundings_menu_ret::fire; + } else if( action == "SAFEMODE_BLACKLIST_ADD" ) { + if( !get_safemode().empty() ) { + const monster *m = dynamic_cast + ( list.get_selected_element()->get_selected_entity() ); + const std::string monName = ( m != nullptr ) ? m->name() : "human"; + + get_safemode().add_rule( monName, Creature::Attitude::ANY, get_option( "SAFEMODEPROXIMITY" ), + rule_state::BLACKLISTED ); + } + } else if( action == "SAFEMODE_BLACKLIST_REMOVE" ) { + const monster *m = dynamic_cast + ( list.get_selected_element()->get_selected_entity() ); + const std::string monName = ( m != nullptr ) ? m->name() : "human"; + + if( get_safemode().has_rule( monName, Creature::Attitude::ANY ) ) { + get_safemode().remove_rule( monName, Creature::Attitude::ANY ); + } + } else { + on_input( action ); + } + } + + return surroundings_menu_ret::quit; +} + +template +void surroundings_menu_tab::on_input( const std::string &action ) +{ + if( action == "UP" ) { + info_scroll_pos = 0; + list.select_prev(); + } else if( action == "DOWN" ) { + info_scroll_pos = 0; + list.select_next(); + } else if( action == "LEFT" ) { + info_scroll_pos = 0; + list.get_selected_element()->select_prev(); + } else if( action == "RIGHT" ) { + info_scroll_pos = 0; + list.get_selected_element()->select_next(); + } else if( action == "look" ) { + hide_ui = true; + look_callback(); + hide_ui = false; + } else if( action == "zoom_in" ) { + zoom_callback( true ); + } else if( action == "zoom_out" ) { + zoom_callback( false ); + } else if( action == "PAGE_UP" ) { + info_scroll_pos = 0; + list.select_page_up(); + } else if( action == "PAGE_DOWN" ) { + info_scroll_pos = 0; + list.select_page_down(); + } else if( action == "SCROLL_SURROUNDINGS_INFO_UP" ) { + info_scroll_pos -= catacurses::getmaxy( info_window ) - 4; + } else if( action == "SCROLL_SURROUNDINGS_INFO_DOWN" ) { + info_scroll_pos += catacurses::getmaxy( info_window ) - 4; + } else if( action == "FILTER" ) { + get_string_input( _( "Filter:" ), width, filter, "item_filter" ); + std::vector filtered = filter_elements( filter ); + list.set_elements( filtered ); + } else if( action == "RESET_FILTER" ) { + reset_filter(); + } else if( action == "SORT" ) { + + } else if( action == "EXAMINE" ) { + catacurses::window temp_info = catacurses::newwin( TERMY, width - 5, point_zero ); + int scroll = 0; + draw_info( temp_info, scroll, list.get_selected_element(), true ); + } +} + +template +void surroundings_menu_tab::draw_shortcut_hint( const catacurses::window & ) +{ + // draw nothing by default +} + +template<> +void surroundings_menu_tab>::draw_shortcut_hint( + const catacurses::window &w ) +{ + std::string sSafemode = _( "You have no safemode rules" ); + if( !get_safemode().empty() ) { + map_entity_stack *mstack = list.get_selected_element(); + if( mstack ) { + const Creature *crit = mstack->get_selected_entity(); + if( crit ) { + const monster *mon = static_cast( crit ); + const std::string monName = !mon ? get_safemode().npc_type_name() : mon->name(); + + if( get_safemode().has_rule( monName, Creature::Attitude::ANY ) ) { + sSafemode = _( "emove from safemode Blacklist" ); + } else { + sSafemode = _( "dd to safemode Blacklist" ); + } + } + } + } + + shortcut_print( w, point( 2, getmaxy( w ) - 1 ), c_white, c_light_green, sSafemode ); +} + +template +void surroundings_menu_tab::centerlistview( avatar &you, T *selected, int ui_width ) +{ + if( selected ) { + cata::optional active_item_position = selected->get_selected_pos(); + if( active_item_position ) { + // todo: remove this option and make it a toggle instead? + if( get_option( "SHIFT_LIST_ITEM_VIEW" ) != "false" ) { + you.view_offset.z = active_item_position->z; + if( get_option( "SHIFT_LIST_ITEM_VIEW" ) == "centered" ) { + you.view_offset.x = active_item_position->x; + you.view_offset.y = active_item_position->y; + } else { + point pos( active_item_position->xy() + point( POSX, POSY ) ); + + // item/monster list UI is on the right, so get the difference between its width + // and the width of the sidebar on the right (if any) + int sidebar_right_adjusted = ui_width - panel_manager::get_manager().get_width_right(); + // if and only if that difference is greater than zero, use that as offset + int right_offset = sidebar_right_adjusted > 0 ? sidebar_right_adjusted : 0; + + // Convert offset to tile counts, calculate adjusted terrain window width + // This lets us account for possible differences in terrain width between + // the normal sidebar and the list-all-whatever display. + to_map_font_dim_width( right_offset ); + int terrain_width = TERRAIN_WINDOW_WIDTH - right_offset; + + if( pos.x < 0 ) { + you.view_offset.x = pos.x; + } else if( pos.x >= terrain_width ) { + you.view_offset.x = pos.x - ( terrain_width - 1 ); + } else { + you.view_offset.x = 0; + } + + if( pos.y < 0 ) { + you.view_offset.y = pos.y; + } else if( pos.y >= TERRAIN_WINDOW_HEIGHT ) { + you.view_offset.y = pos.y - ( TERRAIN_WINDOW_HEIGHT - 1 ); + } else { + you.view_offset.y = 0; + } + } + } + } + } +} + +template +std::vector surroundings_menu_tab::filter_elements( const std::string & ) +{ + // default to no filtering + return elements; +} + +template<> +std::vector*> +surroundings_menu_tab>::filter_elements( const std::string &filter ) +{ + std::vector*> ret; + + std::function filter_func = item_filter_from_string( filter ); + std::copy_if( elements.begin(), elements.end(), + std::back_inserter( ret ), [filter_func]( const map_entity_stack *mstack ) { + if( !mstack->entities.empty() ) { + return filter_func( *mstack->entities.front().entity ); + } + return false; + } ); + + return ret; +} + +template<> +std::vector*> +surroundings_menu_tab>::filter_elements( const std::string &filter ) +{ + // todo: complex creature filters + std::vector*> ret; + std::copy_if( elements.begin(), elements.end(), + std::back_inserter( ret ), [&filter]( const map_entity_stack *mstack ) { + if( !mstack->entities.empty() ) { + const Creature *mon = mstack->entities.front().entity; + const monster *m = dynamic_cast( mon ); + if( m != nullptr ) { + return lcmatch( m->name(), filter ); + } else { + return lcmatch( mon->disp_name(), filter ); + } + } + return false; + } ); + return ret; +} + +template<> +std::vector*> +surroundings_menu_tab>::filter_elements( + const std::string &filter ) +{ + // todo: complex terrain/furniture filters + std::vector*> ret; + std::copy_if( elements.begin(), elements.end(), + std::back_inserter( ret ), [&filter]( const map_entity_stack *mstack ) { + if( !mstack->entities.empty() ) { + return lcmatch( mstack->entities.front().entity->name(), filter ); + } + return false; + } ); + return ret; +} + +template +void surroundings_menu_tab::query_filter() +{ + //filter_type = item_filter_type::FILTER; + //ui.invalidate_ui(); + + //uistate.list_item_filter_active = !sFilter.empty(); + //filter_type = cata::nullopt; +} + +template +void surroundings_menu_tab::reset_filter() +{ + filter.clear(); + list.set_elements( elements ); +} + +template +void surroundings_menu_tab::draw_info( const catacurses::window &, int &, const T *, const bool ) +{ + debugmsg( "Info for this element not implemented." ); +} + +template<> +void surroundings_menu_tab>::draw_info( const catacurses::window &w, + int &scroll_pos, const map_entity_stack *element, const bool examining ) +{ + if( element ) { + const item *it = element->get_selected_entity(); + if( it ) { + std::vector vThisItem; + std::vector vDummy; + it->info( true, vThisItem ); + + std::string name; + std::string type; + + if( examining ) { + name = it->tname(); + type = it->type_name(); + } + + item_info_data info_data( name, type, vThisItem, vDummy, scroll_pos ); + if( examining ) { + info_data.handle_scrolling = true; + } else { + info_data.without_getch = true; + info_data.without_border = true; + info_data.without_scrollbar = true; + } + + draw_item_info( w, info_data ); + + // todo: move to info_window_border + if( !examining ) { + // print info window title: < item name > + std::string title = string_format( "< %s >", it->display_name() ); + trim_and_print( w, point( 2, 0 ), width - 8, it->color_in_inventory(), title ); + } + } + } +} + +template<> +void surroundings_menu_tab>::draw_info( const catacurses::window &w, + int &, const map_entity_stack *element, const bool ) +{ + if( element ) { + const Creature *mon = element->get_selected_entity(); + if( mon ) { + mon->print_info( w, 1, getmaxy( w ) - 3, 1 ); + } + } +} + +template<> +void surroundings_menu_tab>::draw_info( + const catacurses::window &, int &, const map_entity_stack *, const bool ) +{ + // todo: draw something +} + +template +shared_ptr_fast surroundings_menu_tab::create_or_get_ui_adaptor() +{ + shared_ptr_fast current_ui = ui.lock(); + if( !current_ui ) { + ui = current_ui = make_shared_fast(); + current_ui->on_screen_resize( [this]( ui_adaptor & ui ) { + prepare_layout( ui ); + } ); + current_ui->mark_resize(); + + current_ui->on_redraw( [this]( const ui_adaptor & ) { + refresh_window(); + } ); + } + return current_ui; +} + +template +void surroundings_menu_tab::prepare_layout( ui_adaptor &ui ) +{ + if( hide_ui ) { + ui.position( point_zero, point_zero ); + } else { + const int offsetX = TERMX - width; + + list_window = catacurses::newwin( list_height + 1, width, point( offsetX, 0 ) ); + info_window = catacurses::newwin( info_height - 2, width - 2, point( offsetX + 1, + list_height + 2 ) ); + info_window_border = catacurses::newwin( info_height, width, point( offsetX, list_height + 1 ) ); + + centerlistview( you, list.get_selected_element(), width ); + + ui.position( point( offsetX, 0 ), point( width, TERMY ) ); + } +} + +template +void surroundings_menu_tab::refresh_window() +{ + if( !hide_ui ) { + werase( list_window ); + werase( info_window ); + werase( info_window_border ); + + draw_headers_footers_borders(); + wnoutrefresh( info_window_border ); + + list.draw( list_window, point( 1, 1 ) ); + wnoutrefresh( list_window ); + + draw_info( info_window, info_scroll_pos, list.get_selected_element() ); + wnoutrefresh( info_window ); + + // todo: move the cursor to the selected item (for screen readers) + // wmove( w_items, point( 1, iActive - iStartPos ) ); + } +} + +template +void surroundings_menu_tab::draw_headers_footers_borders() +{ + const int num_elements = list.get_count(); + const int num_width = num_elements > 999 ? 4 : + num_elements > 99 ? 3 : + num_elements > 9 ? 2 : 1; + const int selected_index = list.get_selected_index(); + + draw_custom_border( list_window, 1, 1, 1, 1, 1, 1, LINE_XXXO, LINE_XOXX ); + mvwprintz( list_window, point( 2, 0 ), c_light_green, " " ); + wprintz( list_window, c_white, title ); + mvwprintz( list_window, point( ( width / 2 ) - num_width - 2, 0 ), c_light_green, " %*d", num_width, + selected_index + 1 ); + wprintz( list_window, c_white, " / %*d ", num_width, static_cast( num_elements ) ); + draw_scrollbar( list_window, selected_index, list_height, static_cast( num_elements ), + point_south ); + + draw_shortcut_hint( list_window ); + + draw_custom_border( info_window_border, 1, 1, 1, 1, LINE_XXXO, LINE_XOXX, 1, 1 ); +} + +template +bool surroundings_menu_tab::empty() +{ + return elements.empty(); +} + +surroundings_menu_ret surroundings_menu::execute() +{ + // todo: remember last selected and make this loop from there + if( it_tab.empty() && selected_tab == surroundings_menu_tab_enum::items ) { + selected_tab = surroundings_menu_tab_enum::monsters; + } + if( mon_tab.empty() && selected_tab == surroundings_menu_tab_enum::monsters ) { + selected_tab = surroundings_menu_tab_enum::terfurn; + } + if( tf_tab.empty() && selected_tab == surroundings_menu_tab_enum::terfurn ) { + selected_tab = surroundings_menu_tab_enum::items; + } + + while( true ) { + ui_manager::redraw(); + surroundings_menu_ret action = surroundings_menu_ret::quit; + if( selected_tab == surroundings_menu_tab_enum::items ) { + action = it_tab.execute(); + } else if( selected_tab == surroundings_menu_tab_enum::monsters ) { + action = mon_tab.execute(); + } else if( selected_tab == surroundings_menu_tab_enum::terfurn ) { + action = tf_tab.execute(); + } + + if( action == surroundings_menu_ret::nexttab ) { + ++selected_tab; + } else if( action == surroundings_menu_ret::prevtab ) { + --selected_tab; + } else if( action == surroundings_menu_ret::fire || action == surroundings_menu_ret::quit ) { + return action; + } + } + + return surroundings_menu_ret::quit; +} diff --git a/src/surroundings_menu.h b/src/surroundings_menu.h new file mode 100644 index 0000000000000..f6a3b8a260433 --- /dev/null +++ b/src/surroundings_menu.h @@ -0,0 +1,193 @@ +#pragma once +#ifndef CATA_SRC_SURROUNDINGS_MENU_H +#define CATA_SRC_SURROUNDINGS_MENU_H + +#include "avatar.h" +#include "catacharset.h" +#include "cursesdef.h" +#include "input.h" +#include "list_view.h" +#include "map.h" +#include "map_item_stack.h" +#include "memory_fast.h" +#include "optional.h" +#include "output.h" +#include "point.h" +#include "type_id.h" + +class Creature; +class item; +class ui_adaptor; + +enum class surroundings_menu_ret : int { + quit = 0, + fire, + nexttab, + prevtab +}; + +enum class surroundings_menu_tab_enum : int { + items = 0, + monsters, + terfurn, + num_tabs +}; + +template +class surroundings_menu_tab +{ + public: + surroundings_menu_tab( const std::string title, + avatar &you, + map &m, + cata::optional &path_end, + const int width, + const int info_height, + const std::function &invalidate_main_ui, + const std::function &zoom_callback, + const std::function &look_callback ) : + width( width ), + list_height( TERMY - info_height - 1 ), + info_height( info_height ), + title( title ), + you( you ), + m( m ), + stored_view_offset( you.view_offset ), + path_start( you.pos() ), + path_end( path_end ), + ctxt( ctxt ), + list( list_height, width - 4 ), // border, space, name, space, border makes width - 4 + invalidate_main_ui( invalidate_main_ui ), + zoom_callback( zoom_callback ), + look_callback( look_callback ) { }; + + virtual surroundings_menu_ret execute() = 0; + bool empty(); + + void update_path_end() { + path_end = cata::nullopt; + + T *element = list.get_selected_element(); + if( element ) { + cata::optional p = element->get_selected_pos(); + if( p ) { + path_end = path_start + *p; + } + } + } + + const int radius = 60; + const int width; + const int list_height; + const int info_height; + + protected: + shared_ptr_fast create_or_get_ui_adaptor(); + weak_ptr_fast ui; + + void prepare_layout( ui_adaptor &ui ); + void refresh_window(); + void draw_headers_footers_borders(); + void draw_info( const catacurses::window &w, int &scroll_pos, const T *element, + const bool examining = false ); + void centerlistview( avatar &you, T *selected, int ui_width ); + void on_input( const std::string &action ); + void draw_shortcut_hint( const catacurses::window &w ); + void query_filter(); + void reset_filter(); + std::vector filter_elements( const std::string &filter ); + + const std::string title; + avatar &you; + map &m; + const tripoint stored_view_offset; + const tripoint path_start; + cata::optional &path_end; + std::vector elements; + input_context ctxt; + list_view list; + const std::function invalidate_main_ui; + const std::function zoom_callback; + const std::function look_callback; + + int info_scroll_pos = 0; + bool hide_ui = false; + + // todo: remember these more permanently + std::string filter; + std::string list_item_upvote; + std::string list_item_downvote; + + catacurses::window list_window; + catacurses::window info_window; + catacurses::window info_window_border; +}; + +class items_tab : public surroundings_menu_tab> +{ + public: + items_tab( const std::string title, avatar &you, map &m, cata::optional &path_end, + int width, int info_height, const std::function &invalidate_main_ui, + const std::function &zoom_callback, const std::function &look_callback ); + + surroundings_menu_ret execute() override; + + private: + std::map> items; +}; + +class monsters_tab : public surroundings_menu_tab> +{ + public: + monsters_tab( const std::string title, avatar &you, map &m, cata::optional &path_end, + int width, int info_height, const std::function &invalidate_main_ui, + const std::function &zoom_callback, const std::function &look_callback ); + + surroundings_menu_ret execute() override; + + private: + std::vector> monsters; +}; + +class terfurn_tab : public surroundings_menu_tab> +{ + public: + terfurn_tab( const std::string title, avatar &you, map &m, cata::optional &path_end, + int width, int info_height, const std::function &invalidate_main_ui, + const std::function &zoom_callback, const std::function &look_callback ); + + surroundings_menu_ret execute() override; + + private: + std::map> terfurn; +}; + +class surroundings_menu +{ + public: + surroundings_menu( int width, + int info_height, + avatar &you, + map &m, + cata::optional &path_end, + const std::function &invalidate_main_ui, + const std::function &zoom_callback, + const std::function &look_callback ) : + it_tab( _( "Items" ), you, m, path_end, width, info_height, invalidate_main_ui, zoom_callback, + look_callback ), + mon_tab( _( "Monsters" ), you, m, path_end, width, info_height, invalidate_main_ui, zoom_callback, + look_callback ), + tf_tab( _( "Terrain/Furniture" ), you, m, path_end, width, info_height, invalidate_main_ui, + zoom_callback, look_callback ) {}; + + surroundings_menu_ret execute(); + + private: + items_tab it_tab; + monsters_tab mon_tab; + terfurn_tab tf_tab; + + surroundings_menu_tab_enum selected_tab = surroundings_menu_tab_enum::items; +}; + +#endif // CATA_SRC_SURROUNDINGS_MENU_H