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..40f1e84087194 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -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 @@ -7372,20 +7329,12 @@ void game::list_items_monsters() // 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,36 @@ 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(); + } ); + + 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 +7820,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 +7859,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 +7897,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 +8152,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 +8185,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 +8214,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..21a96ec8c65c0 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; @@ -792,17 +793,11 @@ class game // V Menu Functions and helpers: void list_items_monsters(); // 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/list_view.cpp b/src/list_view.cpp new file mode 100644 index 0000000000000..74a2f5d16195c --- /dev/null +++ b/src/list_view.cpp @@ -0,0 +1,75 @@ +#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_items.size() ); + int max_size = std::min( height - 1, list_items.size() ); + for( int i = 0; i < max_size; i++ ) { + T *element = list_items[start_pos + i]; + if( element ) { + draw_element( w, point( 1, i ), width, element, element == get_selected_item() ); + } + } +} + +template +T *list_view::get_selected_item() +{ + if( !list_items.empty() ) { + return list_items[selected_index]; + } + return nullptr; +} + +template +int list_view::get_selected_index() +{ + return selected_index; +} + +template +int list_view::get_count() +{ + return list_items.size(); +} + +template +void list_view::select_next() +{ + if( list_items.empty() ) { + return; + } + + selected_index++; + + if( selected_index == static_cast( list_items.size() ) ) { + selected_index = 0; + } +} + +template +void list_view::select_prev() +{ + if( list_items.empty() ) { + return; + } + if( selected_index == 0 ) { + selected_index = list_items.size(); + } + + selected_index--; +} + +// explicit template initialization +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..241c578154566 --- /dev/null +++ b/src/list_view.h @@ -0,0 +1,36 @@ +#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_items, + const int height, + const int width ) : + list_items( list_items ), + height( height ), + width( width ) {}; + + T *get_selected_item(); + int get_selected_index(); + int get_count(); + void select_next(); + void select_prev(); + 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_items; + const int height; + const int width; + int selected_index = 0; +}; + +#endif // CATA_SRC_V_MENU_H diff --git a/src/list_view_draw_element.cpp b/src/list_view_draw_element.cpp new file mode 100644 index 0000000000000..bea22e6cd10d6 --- /dev/null +++ b/src/list_view_draw_element.cpp @@ -0,0 +1,86 @@ +#include "list_view.h" + +#include "item.h" +#include "mapdata.h" +#include "map_item_stack.h" +#include "monster.h" +#include "output.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." ) ); +} + +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 ); + } + text += it->display_name(); + + nc_color color = it->color_in_inventory(); + if( selected ) { + color = hilite( color ); + } + trim_and_print( w, p, width, color, text ); + } + } +} + +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, color, text ); + } + } +} + +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, color, text ); + } + } +} diff --git a/src/map_item_stack.cpp b/src/map_item_stack.cpp index 4d18fb414bdd9..8686a9af9e89a 100644 --- a/src/map_item_stack.cpp +++ b/src/map_item_stack.cpp @@ -1,71 +1,117 @@ #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" + +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() : count( 0 ), it( nullptr ) +template +const cata::optional map_entity_stack::get_selected_pos() const { + if( !entities.empty() ) { + // todo: check index + return entities[selected_index].pos; + } + return cata::nullopt; +} + +template +void map_entity_stack::select_next() +{ + if( entities.empty() ) { + return; + } + + selected_index++; + + if( selected_index >= static_cast( entities.size() ) ) { + selected_index = 0; + } } -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 +void map_entity_stack::select_prev() { + if( entities.empty() ) { + return; + } + + if( selected_index <= 0 ) { + selected_index = entities.size(); + } + + selected_index--; } -map_item_stack::map_item_stack() : example( nullptr ), totalcount( 0 ) +template +int map_entity_stack::get_selected_index() const { - vIG.emplace_back( ); + return selected_index; } -map_item_stack::map_item_stack( const item *const it, const tripoint &pos ) : example( it ), - totalcount( it->count() ) +template +map_entity_stack::map_entity_stack() : totalcount( 0 ) { - vIG.emplace_back( pos, totalcount, it ); + entities.emplace_back(); } -void map_item_stack::add_at_pos( const item *const it, const tripoint &pos ) +template +map_entity_stack::map_entity_stack( const T *const entity, const tripoint &pos, + const int count ) : totalcount( count ) { - const int amount = it->count(); + entities.emplace_back( pos, totalcount, entity ); +} - if( vIG.empty() || vIG.back().pos != pos ) { - vIG.emplace_back( pos, amount, it ); +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, count, entity ); } else { - vIG.back().count += amount; + entities.back().count += count; } - totalcount += amount; + totalcount += count; } -bool map_item_stack::map_item_stack_sort( const map_item_stack &lhs, const map_item_stack &rhs ) +template +bool map_entity_stack::map_entity_stack_sort( const map_entity_stack &, + const map_entity_stack & ) { - const item_category &lhs_cat = lhs.example->get_category_of_contents(); + // todo: specializations + /*const item_category &lhs_cat = lhs.example->get_category_of_contents(); const item_category &rhs_cat = rhs.example->get_category_of_contents(); if( lhs_cat == rhs_cat ) { - return square_dist( tripoint_zero, lhs.vIG[0].pos ) < - square_dist( tripoint_zero, rhs.vIG[0].pos ); + return square_dist( tripoint_zero, lhs.entities[0].pos ) < + square_dist( tripoint_zero, rhs.entities[0].pos ); } - return lhs_cat < rhs_cat; + return lhs_cat < rhs_cat;*/ + return false; } -std::vector filter_item_stacks( const std::vector &stack, - const std::string &filter ) +template +std::vector> filter_entity_stacks( const std::vector> + &stack, const std::string &filter ) { - std::vector ret; + // todo: actually use it and specializations + std::vector> ret; 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 ); + std::back_inserter( ret ), [z]( const map_entity_stack &a ) { + if( !a.entities.empty() ) { + return z( *a.entities.front() ); } return false; } ); @@ -74,10 +120,13 @@ std::vector filter_item_stacks( const 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 +144,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 +167,8 @@ int list_filter_low_priority( std::vector &stack, const int star } return id; } + +// explicit template initialization +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..cfae82ed0f171 100644 --- a/src/map_item_stack.h +++ b/src/map_item_stack.h @@ -2,49 +2,61 @@ #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; + const cata::optional get_selected_pos() const; + + void select_next(); + void select_prev(); + + int get_selected_index() const; + //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 ); + static bool map_entity_stack_sort( const map_entity_stack &lhs, + 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 +std::vector> filter_entity_stacks( const std::vector> + &stack, const std::string &filter ); +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..0524fd45680fd --- /dev/null +++ b/src/surroundings_menu.cpp @@ -0,0 +1,540 @@ +#include "surroundings_menu.h" + +#include "game_ui.h" +#include "item.h" +#include "map_item_stack.h" +#include "messages.h" +#include "monster.h" +#include "options.h" +#include "panels.h" +#include "ui_manager.h" + +vmenu_tabs &operator++( vmenu_tabs &c ) +{ + c = static_cast( static_cast( c ) + 1 ); + if( c == vmenu_tabs::num_tabs ) { + c = static_cast( 0 ); + } + return c; +} + +vmenu_tabs &operator--( vmenu_tabs &c ) +{ + if( c == static_cast( 0 ) ) { + c = vmenu_tabs::num_tabs; + } + c = static_cast( static_cast( c ) - 1 ); + return c; +} + +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; + } + } + } + } + } +} + +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 ) ) { + point p( points_p_it.x, points_p_it.y ); + const tripoint relative_pos = points_p_it - pos; + + ter_id ter = m.ter( p ); + add_terfurn( item_order, temp_terfurn, &*ter, relative_pos ); + + if( m.has_furn( p ) ) { + furn_id furn = m.furn( p ); + 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 &w, int &scroll_pos, + const T *element ) +{ + 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 &scroll_pos, 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 &w, int &scroll_pos, const map_entity_stack *mstack ) +{ + // 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 ); + monsters_tab = std::make_unique>>( _( "Monsters" ), + you, m, path_end, monsters, get_monster_context(), width, list_height, info_height, + invalidate_main_ui, zoom_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 ); + + while( true ) { + ui_manager::redraw(); + surroundings_menu_ret action = surroundings_menu_ret::quit; + if( selected_tab == vmenu_tabs::items ) { + action = items_tab->execute(); + } else if( selected_tab == vmenu_tabs::monsters ) { + action = monsters_tab->execute(); + } else if( selected_tab == vmenu_tabs::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 ) +{ + 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_item(), width ); + + ui.position( point( offsetX, 0 ), point( width, TERMY ) ); +} + +template +void surroundings_menu_tab::refresh_window() +{ + 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_item() ); + wnoutrefresh( info_window ); + +} + +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_custom_border( info_window_border, 1, 1, 1, 1, LINE_XXXO, LINE_XOXX, 1, 1 ); +} + +template +surroundings_menu_ret surroundings_menu_tab::execute() +{ + shared_ptr_fast ui = create_or_get_ui_adaptor(); + + while( true ) { + centerlistview( you, list.get_selected_item(), 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 == "fire" && path_end ) { + // 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 == "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_item()->select_prev(); + } else if( action == "RIGHT" ) { + scroll_pos = 0; + list.get_selected_item()->select_next(); + } else if( action == "look" ) { + + } 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" ) { + + } else if( action == "RESET_FILTER" ) { + + } else if( action == "SORT" ) { + + } 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 == "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_item() ); + } else if( action == "COMPARE" ) { + + } else if( action == "PRIORITY_INCREASE" ) { + + } else if( action == "PRIORITY_DECREASE" ) { + + } else if( action == "SAFEMODE_BLACKLIST_ADD" ) { + + } else if( action == "SAFEMODE_BLACKLIST_REMOVE" ) { + + } + } + + return surroundings_menu_ret::quit; +} + +// muss das gehandelt werden? +// ctxt.register_action("HELP_KEYBINDINGS"); + + + diff --git a/src/surroundings_menu.h b/src/surroundings_menu.h new file mode 100644 index 0000000000000..5688c049ded7b --- /dev/null +++ b/src/surroundings_menu.h @@ -0,0 +1,169 @@ +#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 vmenu_tabs : 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 &list_items, + 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 ) : + title( title ), + you( you ), + m( m ), + stored_view_offset( you.view_offset ), + path_start( you.pos() ), + path_end( path_end ), + ctxt( ctxt ), + width( width ), + list_height( list_height ), + info_height( info_height ), + list( list_items, list_height, width - 4 ), // border, space, name, space, border makes width - 4 + invalidate_main_ui( invalidate_main_ui ), + zoom_callback( zoom_callback ) { }; + + surroundings_menu_ret execute(); + + void update_path_end() { + path_end = cata::nullopt; + + T *element = list.get_selected_item(); + if( element ) { + cata::optional p = element->get_selected_pos(); + if( p ) { + path_end = path_start + *p; + } + } + } + + const int info_height; + const int list_height; + const int width; + + 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 ); + + list_view list; + + catacurses::window list_window; + catacurses::window list_window_border; + catacurses::window info_window; + catacurses::window info_window_border; + + const std::string title; + avatar &you; + map &m; + const tripoint path_start; + cata::optional &path_end; + input_context ctxt; + int scroll_pos = 0; + const tripoint stored_view_offset; + const std::function invalidate_main_ui; + const std::function zoom_callback; +}; + +class surroundings_menu +{ + public: + surroundings_menu( avatar &you, + map &m, + cata::optional &path_end, + const std::function &zoom, + const std::function &invalidate_main_ui ) : + you( you ), + m( m ), + path_end( path_end ), + zoom_callback( zoom ), + invalidate_main_ui( invalidate_main_ui ) {}; + + 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_items, + std::vector*> &items, 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; + + // 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; + + vmenu_tabs selected_tab = vmenu_tabs::items; +}; + +#endif // CATA_SRC_SURROUNDINGS_MENU_H