From d39553da41f4624e462d894b62420a5d28a540b4 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 | 183 +++----- src/game.h | 15 +- src/handle_action.cpp | 2 +- src/list_view.cpp | 82 ++++ src/list_view.h | 37 ++ src/list_view_draw_element.cpp | 114 +++++ src/map_item_stack.cpp | 139 ++++-- src/map_item_stack.h | 56 ++- src/surroundings_menu.cpp | 799 +++++++++++++++++++++++++++++++++ src/surroundings_menu.h | 188 ++++++++ 11 files changed, 1447 insertions(+), 201 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 67f8aada64236..e39439aa387c5 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -6886,7 +6886,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" ) { @@ -7051,41 +7051,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 ) { @@ -7123,49 +7123,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 @@ -7367,25 +7324,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 ) { @@ -7397,23 +7346,39 @@ 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( + u, + m, + path_end, + [&]( bool z_in ) { + if( z_in ) { + zoom_in(); } else { - break; + zoom_out(); } - } + mark_main_ui_adaptor_resize(); + }, + [&]() { + invalidate_main_ui_adaptor(); + }, + [&]() { + 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; @@ -7858,7 +7823,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; @@ -7897,10 +7862,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; @@ -7935,7 +7900,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 ) ); @@ -8190,7 +8155,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(); @@ -8223,14 +8188,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 @@ -8252,7 +8217,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 52eb9ef19ecd5..8b577128c99a6 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..c9fae1d4dcc3e --- /dev/null +++ b/src/list_view.cpp @@ -0,0 +1,82 @@ +#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 ) +{ + 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, 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::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..a56540667adee --- /dev/null +++ b/src/list_view.h @@ -0,0 +1,37 @@ +#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( std::vector list_elements, + const int height, + const int width ) : + list_elements( list_elements ), + height( height ), + width( width ) {}; + + T *get_selected_element(); + int get_selected_index(); + int get_count(); + void select_next(); + void select_prev(); + void set_elements( std::vector new_list ); + void draw( const catacurses::window &w ); + 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; +}; + +#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/surroundings_menu.cpp b/src/surroundings_menu.cpp new file mode 100644 index 0000000000000..d724c1c91c586 --- /dev/null +++ b/src/surroundings_menu.cpp @@ -0,0 +1,799 @@ +#include "surroundings_menu.h" + +#include "game_inventory.h" +#include "game_ui.h" +#include "item.h" +#include "item_search.h" +#include "line.h" +#include "map_item_stack.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 ); +} + +template +surroundings_menu_ret surroundings_menu_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; +} + +template<> +surroundings_menu_ret surroundings_menu_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; +} + +template<> +surroundings_menu_ret surroundings_menu_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" ) { + scroll_pos = 0; + list.select_prev(); + } else if( action == "DOWN" ) { + scroll_pos = 0; + list.select_next(); + } else if( action == "LEFT" ) { + scroll_pos = 0; + list.get_selected_element()->select_prev(); + } else if( action == "RIGHT" ) { + 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" ) { + scroll_pos = 0; + } else if( action == "PAGE_DOWN" ) { + scroll_pos = 0; + } else if( action == "SCROLL_SURROUNDINGS_INFO_UP" ) { + scroll_pos -= catacurses::getmaxy( info_window ) - 4; + } else if( action == "SCROLL_SURROUNDINGS_INFO_DOWN" ) { + 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" ) { + // todo: handle this better with a bool or something + int scroll = -1; + catacurses::window temp_info = catacurses::newwin( TERMY, width - 5, point_zero ); + draw_info( temp_info, scroll, list.get_selected_element() ); + } +} + +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 ); +} + +input_context surroundings_menu::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; +} + +input_context surroundings_menu::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; +} + +input_context surroundings_menu::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; +} + +input_context surroundings_menu::get_terfurn_context() +{ + input_context ctxt = get_base_context(); + + return ctxt; +} + +void surroundings_menu::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 ); + } +} + +void surroundings_menu::find_nearby_items( std::map> + &temp_items, std::vector*> &items, const Character &you, map &m, + const int radius ) +{ + items.clear(); + 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 ) { + items.push_back( &temp_items[elem] ); + } +} + +void surroundings_menu::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 ); + } +} + +void surroundings_menu::find_nearby_terfurn( + std::map> &temp_terfurn, + std::vector*> &terfurn, const Character &you, map &m, + const int radius ) +{ + terfurn.clear(); + 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 ) { + terfurn.push_back( &temp_terfurn[elem] ); + } +} + +void surroundings_menu::find_nearby_monsters( std::vector> + &temp_monsters, std::vector*> &monsters, const Character &you, + const int radius ) +{ + monsters.clear(); + 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 ); + } +} + +template +void surroundings_menu_tab::draw_info( const catacurses::window &, int &, const T * ) +{ + 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 ) +{ + 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( scroll_pos < 0 ) { + name = it->tname(); + type = it->type_name(); + } + + item_info_data info_data( name, type, vThisItem, vDummy, scroll_pos ); + if( scroll_pos < 0 ) { + info_data.handle_scrolling = true; + } else { + info_data.without_getch = true; + info_data.without_border = true; + } + + draw_item_info( w, info_data ); + } + } +} + +template<> +void surroundings_menu_tab>::draw_info( const catacurses::window &w, + int &, const map_entity_stack *element ) +{ + 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 * ) +{ + // todo: draw something +} + +surroundings_menu_ret surroundings_menu::execute() +{ + const int radius = 60; + + // todo: there must be a better way to do this? + std::map> temp_items; + find_nearby_items( temp_items, items, you, m, radius ); + + std::map> temp_terfurn; + find_nearby_terfurn( temp_terfurn, terfurn, you, m, radius ); + + std::vector> temp_monsters; + find_nearby_monsters( temp_monsters, monsters, you, radius ); + + int info_height = std::min( 25, TERMY / 2 ); + int list_height = TERMY - info_height - 1; + int width = 55; + + items_tab = std::make_unique>>( _( "Items" ), you, m, + path_end, items, get_item_context(), width, list_height, info_height, invalidate_main_ui, + zoom_callback, look_callback ); + monsters_tab = std::make_unique>>( _( "Monsters" ), + you, m, path_end, monsters, get_monster_context(), width, list_height, info_height, + invalidate_main_ui, zoom_callback, look_callback ); + terfurn_tab = std::make_unique>> + ( _( "Terrain/Furniture" ), you, m, path_end, terfurn, get_terfurn_context(), width, list_height, + info_height, invalidate_main_ui, zoom_callback, look_callback ); + + // todo: remember last selected and make this loop from there + if( items.empty() && selected_tab == surroundings_menu_tab_enum::items ) { + selected_tab = surroundings_menu_tab_enum::monsters; + } + if( monsters.empty() && selected_tab == surroundings_menu_tab_enum::monsters ) { + selected_tab = surroundings_menu_tab_enum::terfurn; + } + if( terfurn.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 = items_tab->execute(); + } else if( selected_tab == surroundings_menu_tab_enum::monsters ) { + action = monsters_tab->execute(); + } else if( selected_tab == surroundings_menu_tab_enum::terfurn ) { + action = terfurn_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; +} + +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; + + // todo: really use 4 windows? + list_window = catacurses::newwin( list_height, width - 2, point( offsetX + 1, 1 ) ); + list_window_border = 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( list_window_border ); + werase( info_window ); + werase( info_window_border ); + + draw_headers_footers_borders(); + wnoutrefresh( list_window_border ); + wnoutrefresh( info_window_border ); + + list.draw( list_window ); + wnoutrefresh( list_window ); + + draw_info( info_window, 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_border, 1, 1, 1, 1, 1, 1, LINE_XOXO, LINE_XOXO ); + mvwprintz( list_window_border, point( 2, 0 ), c_light_green, " " ); + wprintz( list_window_border, c_white, title ); + mvwprintz( list_window_border, point( ( width / 2 ) - num_width - 2, 0 ), c_light_green, " %*d", + num_width, selected_index + 1 ); + wprintz( list_window_border, c_white, " / %*d ", num_width, static_cast( num_elements ) ); + draw_scrollbar( list_window_border, 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 ); +} diff --git a/src/surroundings_menu.h b/src/surroundings_menu.h new file mode 100644 index 0000000000000..9d2e80ee8fb4b --- /dev/null +++ b/src/surroundings_menu.h @@ -0,0 +1,188 @@ +#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 "memory_fast.h" +#include "optional.h" +#include "output.h" +#include "point.h" +#include "type_id.h" + +class Creature; +class item; +class ui_adaptor; +template +class map_entity_stack; + +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, + std::vector elements, + input_context ctxt, + const int width, + const int list_height, + const int info_height, + const std::function &invalidate_main_ui, + const std::function &zoom_callback, + const std::function &look_callback ) : + width( width ), + list_height( list_height ), + info_height( info_height ), + title( title ), + you( you ), + m( m ), + stored_view_offset( you.view_offset ), + path_start( you.pos() ), + path_end( path_end ), + elements( elements ), + ctxt( ctxt ), + list( elements, 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 ) { }; + + surroundings_menu_ret execute(); + + 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 width; + const int list_height; + const int info_height; + + private: + 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 ); + 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 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 list_window_border; + catacurses::window info_window; + catacurses::window info_window_border; +}; + +class surroundings_menu +{ + public: + surroundings_menu( avatar &you, + map &m, + cata::optional &path_end, + const std::function &zoom, + const std::function &invalidate_main_ui, + const std::function &look_callback ) : + you( you ), + m( m ), + path_end( path_end ), + zoom_callback( zoom ), + invalidate_main_ui( invalidate_main_ui ), + look_callback( look_callback ) {}; + + surroundings_menu_ret execute(); + + private: + input_context get_base_context(); + input_context get_item_context(); + input_context get_monster_context(); + input_context get_terfurn_context(); + + void find_nearby_items( std::map> &temp_items, + std::vector*> &items, const Character &you, map &m, const int radius ); + void find_nearby_monsters( std::vector> &temp_monsters, + std::vector*> &monsters, const Character &you, const int radius ); + void find_nearby_terfurn( std::map> &temp_terfurn, + std::vector*> &terfurn, const Character &you, map &m, + const int radius ); + + void add_item_recursive( std::vector &item_order, + std::map> &temp_items, const item *it, + const tripoint &relative_pos ); + void add_terfurn( std::vector &item_order, + std::map> &temp_items, + const map_data_common_t *terfurn, const tripoint &relative_pos ); + + avatar &you; + map &m; + cata::optional &path_end; + + std::function zoom_callback; + std::function invalidate_main_ui; + std::function look_callback; + + // todo: couldn't figure out what kind of constructor is needed to make this regular objects + std::unique_ptr>> items_tab; + std::unique_ptr>> monsters_tab; + std::unique_ptr>> terfurn_tab; + + std::vector *> items; + std::vector *> monsters; + std::vector *> terfurn; + + surroundings_menu_tab_enum selected_tab = surroundings_menu_tab_enum::items; +}; + +#endif // CATA_SRC_SURROUNDINGS_MENU_H