diff --git a/data/json/ui/compass.json b/data/json/ui/compass.json index 85019ad39a2e2..6b08a3c3dedc1 100644 --- a/data/json/ui/compass.json +++ b/data/json/ui/compass.json @@ -75,12 +75,18 @@ "//": "An empty space indicating threats nearby", "id": "compass_local_text", "type": "widget", - "label": " ", "style": "text", "direction": "L", "var": "compass_text", "width": 6 }, + { + "id": "compass_legend_text", + "type": "widget", + "style": "text", + "var": "compass_legend_text", + "width": 100 + }, { "id": "compass_top_layout", "type": "widget", @@ -108,6 +114,6 @@ "label": "Compass Alt", "style": "layout", "arrange": "rows", - "widgets": [ "compass_top_layout", "compass_middle_layout", "compass_bottom_layout" ] + "widgets": [ "compass_top_layout", "compass_middle_layout", "compass_bottom_layout", "compass_legend_text" ] } ] diff --git a/data/mods/TEST_DATA/widgets.json b/data/mods/TEST_DATA/widgets.json index 2f65d935136ba..f14467808f311 100644 --- a/data/mods/TEST_DATA/widgets.json +++ b/data/mods/TEST_DATA/widgets.json @@ -109,6 +109,20 @@ "direction": "N", "width": 4 }, + { + "id": "test_compass_legend_5", + "type": "widget", + "style": "text", + "var": "compass_legend_text", + "width": 5 + }, + { + "id": "test_compass_legend_100", + "type": "widget", + "style": "text", + "var": "compass_legend_text", + "width": 100 + }, { "id": "test_speed_num", "type": "widget", diff --git a/doc/SIDEBAR_MOD.md b/doc/SIDEBAR_MOD.md index 68a8718b100e0..7c57aa9cbc847 100644 --- a/doc/SIDEBAR_MOD.md +++ b/doc/SIDEBAR_MOD.md @@ -204,6 +204,7 @@ Some vars refer to text descriptors. These must use style "text". Examples: | `weight_text` | "Emaciated", "Normal", "Overweight", etc. | `date_text` | Current day within season, like "Summer, day 15" | `compass_text` | A compass direction (ex: NE), displaying visible creatures in that direction +| `compass_legend_text` | A list of creatures visible by the player, corresponding to compass symbols For example, a widget to show the current STR stat would define this "var": diff --git a/src/panels.cpp b/src/panels.cpp index 2225346cb95ec..c68f79062be5d 100644 --- a/src/panels.cpp +++ b/src/panels.cpp @@ -2190,6 +2190,54 @@ static std::string get_compass_for_direction( const cardinal_direction dir, int return ret; } +static std::string get_compass_legend( const int max_width ) +{ + int wavail = max_width; + const monster_visible_info &mon_visible = get_avatar().get_mon_visible(); + //~ Creature name format in compass legend. 1$ = symbol, 2$ = name. ex: "Z shocker zombie" + const std::string name_fmt = _( "%1$s %2$s" ); + std::vector names; + for( const std::vector &nv : mon_visible.unique_types ) { + for( const npc *n : nv ) { + if( wavail < 0 ) { + break; + } + std::string name; + switch( n->get_attitude() ) { + case NPCATT_KILL: + name = colorize( "@", c_red ); + break; + case NPCATT_FOLLOW: + name = colorize( "@", c_light_green ); + break; + default: + name = colorize( "@", c_pink ); + break; + } + name = string_format( name_fmt, name, n->name ); + wavail -= utf8_width( name, true ); + names.emplace_back( name ); + } + } + std::map mlist; + for( const auto &mv : mon_visible.unique_mons ) { + for( const std::pair &m : mv ) { + mlist[m.first] += m.second; + } + } + for( const auto &m : mlist ) { + if( wavail < 0 ) { + break; + } + std::string name = m.second > 1 ? string_format( "%d ", m.second ) : ""; + name += string_format( name_fmt, colorize( m.first->sym, m.first->color ), + m.first->nname( m.second ) ); + wavail -= utf8_width( name, true ); + names.emplace_back( name ); + } + return enumerate_as_string( names, enumeration_conjunction::none ); +} + std::pair display::compass_text_color( const cardinal_direction dir, int width ) { @@ -2199,6 +2247,11 @@ std::pair display::compass_text_color( const cardinal_dir return { get_compass_for_direction( dir, width ), c_white }; } +std::pair display::compass_legend_text_color( int width ) +{ + return { get_compass_legend( width ), c_white }; +} + static void draw_health_classic( const draw_args &args ) { const avatar &u = args._ava; diff --git a/src/panels.h b/src/panels.h index cdc6bbe938fd7..ceec10badfef2 100644 --- a/src/panels.h +++ b/src/panels.h @@ -100,6 +100,7 @@ std::pair wind_text_color( const Character &u ); // Get visible threats by cardinal direction std::pair compass_text_color( const cardinal_direction dir, int width ); +std::pair compass_legend_text_color( int width ); // Define color for displaying the body temperature nc_color bodytemp_color( const Character &u, const bodypart_id &bp ); diff --git a/src/widget.cpp b/src/widget.cpp index d2dfcb6eef4c1..b7c26bd700d76 100644 --- a/src/widget.cpp +++ b/src/widget.cpp @@ -79,6 +79,8 @@ std::string enum_to_string( widget_var data ) // Compass case widget_var::compass_text: return "compass_text"; + case widget_var::compass_legend_text: + return "compass_legend_text"; // Base stats case widget_var::stat_str: return "stat_str"; @@ -441,6 +443,7 @@ bool widget::uses_text_function() case widget_var::activity_text: case widget_var::body_temp_text: case widget_var::compass_text: + case widget_var::compass_legend_text: case widget_var::date_text: case widget_var::env_temp_text: case widget_var::fatigue_text: @@ -549,6 +552,11 @@ std::string widget::color_text_function_string( const avatar &ava ) // Skip colorization. desc = display::compass_text_color( _direction, _width ); return desc.first; + case widget_var::compass_legend_text: + // Compass color is specific to individual threats. + // Skip colorization. + desc = display::compass_legend_text_color( _width ); + return desc.first; default: debugmsg( "Unexpected widget_var %s - no text_color function defined", io::enum_to_string( _var ) ); @@ -723,10 +731,15 @@ std::string widget::layout( const avatar &ava, const unsigned int max_width ) std::string shown = show( ava ); const std::string tlabel = _label.translated(); // Width used by label, ": " and value, using utf8_width to ignore color tags - unsigned int used_width = utf8_width( tlabel, true ) + 2 + utf8_width( shown, true ); + unsigned int used_width = utf8_width( shown, true ); + + // If the label is blank or omitted, don't reserve space for it + if( !tlabel.empty() ) { + used_width += utf8_width( tlabel, true ) + 2; + // Label and ": " first + ret += tlabel + ": "; + } - // Label and ": " first - ret += tlabel + ": "; // then enough padding to fit max_width if( used_width < max_width ) { ret += std::string( max_width - used_width, ' ' ); diff --git a/src/widget.h b/src/widget.h index 7312e8d1bbadf..1f3ca50978822 100644 --- a/src/widget.h +++ b/src/widget.h @@ -45,6 +45,7 @@ enum class widget_var : int { activity_text, // Activity level text, color string body_temp_text, // Felt body temperature, color string compass_text, // Compass / visible threats by cardinal direction + compass_legend_text, // Names of visible creatures that appear on the compass date_text, // Current date, in terms of day within season env_temp_text, // Environment temperature, if character has thermometer fatigue_text, // Fagitue description text, color string diff --git a/tests/widget_test.cpp b/tests/widget_test.cpp index b66f71a657f5d..18a4b83c9dcae 100644 --- a/tests/widget_test.cpp +++ b/tests/widget_test.cpp @@ -22,6 +22,8 @@ static const widget_id widget_test_color_number_widget( "test_color_number_widge static const widget_id widget_test_compass_N( "test_compass_N" ); static const widget_id widget_test_compass_N_nodir_nowidth( "test_compass_N_nodir_nowidth" ); static const widget_id widget_test_compass_N_nowidth( "test_compass_N_nowidth" ); +static const widget_id widget_test_compass_legend_100( "test_compass_legend_100" ); +static const widget_id widget_test_compass_legend_5( "test_compass_legend_5" ); static const widget_id widget_test_dex_num( "test_dex_num" ); static const widget_id widget_test_focus_num( "test_focus_num" ); static const widget_id widget_test_hp_head_graph( "test_hp_head_graph" ); @@ -407,6 +409,8 @@ TEST_CASE( "compass widget", "[widget]" ) widget c5s_N = widget_test_compass_N.obj(); widget c5s_N_nowidth = widget_test_compass_N_nowidth.obj(); widget c5s_N_nodir_nowidth = widget_test_compass_N_nodir_nowidth.obj(); + widget c5s_legend5 = widget_test_compass_legend_5.obj(); + widget c5s_legend100 = widget_test_compass_legend_100.obj(); avatar &ava = get_avatar(); clear_avatar(); @@ -420,6 +424,8 @@ TEST_CASE( "compass widget", "[widget]" ) CHECK( c5s_N.layout( ava ) == "N: " ); CHECK( c5s_N_nowidth.layout( ava ) == "N: " ); CHECK( c5s_N_nodir_nowidth.layout( ava ) == "N: " ); + CHECK( c5s_legend5.layout( ava ).empty() ); + CHECK( c5s_legend100.layout( ava ).empty() ); } SECTION( "1 monster NE" ) { @@ -433,6 +439,10 @@ TEST_CASE( "compass widget", "[widget]" ) CHECK( c5s_N.layout( ava ) == "N: " ); CHECK( c5s_N_nowidth.layout( ava ) == "N: " ); CHECK( c5s_N_nodir_nowidth.layout( ava ) == "N: " ); + CHECK( c5s_legend5.layout( ava ) == + "B monster producing CBMs when dissected" ); + CHECK( c5s_legend100.layout( ava ) == + "B monster producing CBMs when dissected" ); } SECTION( "1 monster N" ) { @@ -446,6 +456,10 @@ TEST_CASE( "compass widget", "[widget]" ) CHECK( c5s_N.layout( ava ) == "N: B" ); CHECK( c5s_N_nowidth.layout( ava ) == "N: +" ); CHECK( c5s_N_nodir_nowidth.layout( ava ) == "N: " ); + CHECK( c5s_legend5.layout( ava ) == + "B monster producing CBMs when dissected" ); + CHECK( c5s_legend100.layout( ava ) == + "B monster producing CBMs when dissected" ); } SECTION( "3 same monsters N" ) { @@ -464,6 +478,10 @@ TEST_CASE( "compass widget", "[widget]" ) CHECK( c5s_N.layout( ava ) == "N: B" ); CHECK( c5s_N_nowidth.layout( ava ) == "N: +" ); CHECK( c5s_N_nodir_nowidth.layout( ava ) == "N: " ); + CHECK( c5s_legend5.layout( ava ) == + "3 B monster producing CBMs when dissected" ); + CHECK( c5s_legend100.layout( ava ) == + "3 B monster producing CBMs when dissected" ); } SECTION( "3 different monsters N" ) { @@ -483,6 +501,11 @@ TEST_CASE( "compass widget", "[widget]" ) "N: BBS" ); CHECK( c5s_N_nowidth.layout( ava ) == "N: +" ); CHECK( c5s_N_nodir_nowidth.layout( ava ) == "N: " ); + CHECK( c5s_legend5.layout( ava ) == "S shearable monster" ); + CHECK( c5s_legend100.layout( ava ) == + "S shearable monster, " + "B monster producing bovine samples when dissected, " + "B monster producing CBMs when dissected" ); } }