diff --git a/data/json/ui/bodyparts.json b/data/json/ui/bodyparts.json new file mode 100644 index 0000000000000..b943f90f5b178 --- /dev/null +++ b/data/json/ui/bodyparts.json @@ -0,0 +1,65 @@ +[ + { + "id": "bodypart_status_text", + "//": "Base widget for showing body part status; needs bodypart field defined in derived widget.", + "type": "widget", + "style": "text", + "var": "bp_status_text" + }, + { + "id": "bp_status_head_text", + "type": "widget", + "label": "HEAD", + "bodypart": "head", + "copy-from": "bodypart_status_text" + }, + { + "id": "bp_status_torso_text", + "type": "widget", + "label": "TORSO", + "bodypart": "torso", + "copy-from": "bodypart_status_text" + }, + { + "id": "bp_status_left_arm_text", + "type": "widget", + "label": "L ARM", + "bodypart": "arm_l", + "copy-from": "bodypart_status_text" + }, + { + "id": "bp_status_right_arm_text", + "type": "widget", + "label": "R ARM", + "bodypart": "arm_r", + "copy-from": "bodypart_status_text" + }, + { + "id": "bp_status_left_leg_text", + "type": "widget", + "label": "L LEG", + "bodypart": "leg_l", + "copy-from": "bodypart_status_text" + }, + { + "id": "bp_status_right_leg_text", + "type": "widget", + "label": "R LEG", + "bodypart": "leg_r", + "copy-from": "bodypart_status_text" + }, + { + "id": "bodypart_status_top_layout", + "type": "widget", + "style": "layout", + "arrange": "columns", + "widgets": [ "bp_status_left_arm_text", "bp_status_head_text", "bp_status_right_arm_text" ] + }, + { + "id": "bodypart_status_bottom_layout", + "type": "widget", + "style": "layout", + "arrange": "columns", + "widgets": [ "bp_status_left_leg_text", "bp_status_torso_text", "bp_status_right_leg_text" ] + } +] diff --git a/data/json/ui/sidebar.json b/data/json/ui/sidebar.json index d3bc1fe126ed4..fc64a86480dcd 100644 --- a/data/json/ui/sidebar.json +++ b/data/json/ui/sidebar.json @@ -638,6 +638,22 @@ "var": "moon_phase_text", "//": "Uses display::get_moon" }, + { + "id": "move_mode_letter", + "type": "widget", + "label": "Mode", + "style": "text", + "var": "move_mode_letter", + "//": "Single-letter W/R/C/P, from display::move_mode_letter_color" + }, + { + "id": "move_mode_desc", + "type": "widget", + "label": "Mode", + "style": "text", + "var": "move_mode_text", + "//": "Full movement mode name, from display::move_mode_text_color" + }, { "id": "pain_desc", "type": "widget", @@ -694,6 +710,30 @@ "var": "weary_malus_text", "//": "Uses display::weary_malus_text_color" }, + { + "id": "vehicle_azimuth_desc", + "type": "widget", + "label": "Azimuth", + "style": "text", + "var": "veh_azimuth_text", + "//": "Uses display::vehicle_azimuth_text" + }, + { + "id": "vehicle_cruise_desc", + "type": "widget", + "label": "Vehicle Speed", + "style": "text", + "var": "veh_cruise_text", + "//": "Current/target vehicle cruising speed from display::vehicle_cruise_text_color" + }, + { + "id": "vehicle_fuel_desc", + "type": "widget", + "label": "Vehicle Fuel", + "style": "text", + "var": "veh_fuel_text", + "//": "Current/max fuel for active vehicle engine from display::vehicle_fuel_text_color" + }, { "id": "weariness_desc", "type": "widget", @@ -751,6 +791,13 @@ "style": "text", "var": "wind_text" }, + { + "id": "weather_desc", + "type": "widget", + "label": "Weather", + "style": "text", + "var": "weather_text" + }, { "id": "lighting_desc", "type": "widget", diff --git a/data/mods/TEST_DATA/widgets.json b/data/mods/TEST_DATA/widgets.json index 9092a8105f649..50086000826ff 100644 --- a/data/mods/TEST_DATA/widgets.json +++ b/data/mods/TEST_DATA/widgets.json @@ -64,6 +64,20 @@ "var": "focus", "style": "number" }, + { + "id": "test_health_num", + "type": "widget", + "label": "HEALTH", + "var": "health", + "style": "number" + }, + { + "id": "test_health_text", + "type": "widget", + "label": "HEALTH", + "var": "health_text", + "style": "text" + }, { "id": "test_mana_num", "type": "widget", @@ -85,6 +99,20 @@ "var": "rad_badge_text", "style": "text" }, + { + "id": "test_pain_num", + "type": "widget", + "label": "PAIN", + "var": "pain", + "style": "number" + }, + { + "id": "test_pain_text", + "type": "widget", + "label": "PAIN", + "var": "pain_text", + "style": "text" + }, { "id": "test_speed_num", "type": "widget", @@ -124,6 +152,20 @@ "var": "move", "style": "number" }, + { + "id": "test_move_mode_text", + "type": "widget", + "label": "MODE", + "var": "move_mode_text", + "style": "text" + }, + { + "id": "test_move_mode_letter", + "type": "widget", + "label": "MODE", + "var": "move_mode_letter", + "style": "text" + }, { "id": "test_str_num", "type": "widget", @@ -152,6 +194,13 @@ "var": "stat_per", "style": "number" }, + { + "id": "test_weather_text", + "type": "widget", + "label": "Weather", + "var": "weather_text", + "style": "text" + }, { "id": "test_weariness_num", "type": "widget", @@ -194,6 +243,22 @@ "bodypart": "torso", "style": "number" }, + { + "id": "test_status_torso_text", + "type": "widget", + "label": "TORSO STATUS", + "var": "bp_status_text", + "bodypart": "torso", + "style": "text" + }, + { + "id": "test_status_left_arm_text", + "type": "widget", + "label": "LEFT ARM STATUS", + "var": "bp_status_text", + "bodypart": "arm_l", + "style": "text" + }, { "id": "test_stat_panel", "type": "widget", diff --git a/src/move_mode.cpp b/src/move_mode.cpp index 8b21985341715..84b990e8cfda3 100644 --- a/src/move_mode.cpp +++ b/src/move_mode.cpp @@ -122,6 +122,23 @@ std::string move_mode::name() const return _name.translated(); } +std::string move_mode::type_name() const +{ + switch( _type ) { + case move_mode_type::PRONE: + return "prone"; + case move_mode_type::CROUCHING: + return "crouching"; + case move_mode_type::WALKING: + return "walking"; + case move_mode_type::RUNNING: + return "running"; + default: + // Shouldn't happen, but make it visible if it does + return "bugging out"; + } +} + std::string move_mode::change_message( bool success, steed_type steed ) const { if( steed == steed_type::NUM ) { diff --git a/src/move_mode.h b/src/move_mode.h index bc6401eb28801..962c39f46e555 100644 --- a/src/move_mode.h +++ b/src/move_mode.h @@ -70,7 +70,10 @@ class move_mode move_mode() = default; + // name: walk, run, crouch, prone std::string name() const; + // type name: walking, running, crouching, prone + std::string type_name() const; std::string change_message( bool success, steed_type steed ) const; move_mode_id cycle() const; diff --git a/src/panels.cpp b/src/panels.cpp index 9024e7525d2a2..bf1fc0e0b8b82 100644 --- a/src/panels.cpp +++ b/src/panels.cpp @@ -54,6 +54,7 @@ #include "type_id.h" #include "ui_manager.h" #include "units.h" +#include "units_utility.h" #include "vehicle.h" #include "vpart_position.h" #include "weather.h" @@ -79,6 +80,8 @@ static const flag_id json_flag_RAD_DETECT( "RAD_DETECT" ); static const flag_id json_flag_SPLINT( "SPLINT" ); static const flag_id json_flag_THERMOMETER( "THERMOMETER" ); +static const itype_id fuel_type_muscle( "muscle" ); + static const string_id behavior__node_t_npc_needs( "npc_needs" ); static const trait_id trait_NOPAIN( "NOPAIN" ); @@ -1335,42 +1338,34 @@ nc_color display::limb_color( const Character &u, const bodypart_id &bp, bool bl if( bp == bodypart_str_id::NULL_ID() ) { return c_light_gray; } - int color_bit = 0; nc_color i_color = c_light_gray; const int intense = u.get_effect_int( effect_bleed, bp ); - if( bleed && intense > 0 ) { - color_bit += 1; - } - if( bite && u.has_effect( effect_bite, bp.id() ) ) { - color_bit += 10; - } - if( infect && u.has_effect( effect_infected, bp.id() ) ) { - color_bit += 100; - } - switch( color_bit ) { - case 1: - i_color = colorize_bleeding_intensity( intense ); - break; - case 10: - i_color = c_blue; - break; - case 100: - i_color = c_green; - break; - case 11: - if( intense < 21 ) { - i_color = c_magenta; - } else { - i_color = c_magenta_red; - } - break; - case 101: - if( intense < 21 ) { - i_color = c_yellow; - } else { - i_color = c_yellow_red; - } - break; + const bool bleeding = bleed && intense > 0; + const bool bitten = bite && u.has_effect( effect_bite, bp.id() ); + const bool infected = infect && u.has_effect( effect_infected, bp.id() ); + + // Handle worst cases first + if( bleeding && infected ) { + // Red and green make yellow + if( intense < 21 ) { + i_color = c_yellow; + } else { + i_color = c_yellow_red; + } + } else if( bleeding && bitten ) { + // Red and blue make magenta + if( intense < 21 ) { + i_color = c_magenta; + } else { + i_color = c_magenta_red; + } + } else if( infected ) { + i_color = c_green; // Green is very bad + } else if( bitten ) { + i_color = c_blue; // Blue is also bad + } else if( bleeding ) { + // Blood is some shade of red, naturally + i_color = colorize_bleeding_intensity( intense ); } return i_color; @@ -1440,6 +1435,127 @@ std::pair display::rad_badge_text_color( const Character return std::make_pair( rad_text, rad_color ); } +std::string display::bodypart_status( const Character &u, const bodypart_id &bp ) +{ + if( bp == bodypart_str_id::NULL_ID() ) { + return ""; + } + std::string bp_status; + const int bleed_intensity = u.get_effect_int( effect_bleed, bp ); + const bool bleeding = bleed_intensity > 0; + const bool bitten = u.has_effect( effect_bite, bp.id() ); + const bool infected = u.has_effect( effect_infected, bp.id() ); + + std::vector ailments; + if( bitten ) { + ailments.emplace_back( "bitten" ); + } + if( bleeding ) { + ailments.emplace_back( "bleeding" ); + } + if( infected ) { + ailments.emplace_back( "infected" ); + } + // TODO: Include bandage/antiseptic quality, broken limbs, splints + if( ailments.empty() ) { + bp_status = "--"; + } else { + bp_status = join( ailments, ", " ); + } + + return bp_status; +} + +std::pair display::bodypart_status_text_color( const Character &u, + const bodypart_id &bp ) +{ + std::string bp_stat_text = display::bodypart_status( u, bp ); + nc_color bp_stat_color = display::limb_color( u, bp, true, true, true ); + return std::make_pair( bp_stat_text, bp_stat_color ); +} + +// Get remotely controlled vehicle, or vehicle character is inside of +static vehicle *vehicle_driven( const Character &u ) +{ + vehicle *veh = g->remoteveh(); + if( veh == nullptr && u.in_vehicle ) { + veh = veh_pointer_or_null( get_map().veh_at( u.pos() ) ); + } + return veh; +} + +std::string display::vehicle_azimuth_text( const Character &u ) +{ + vehicle *veh = vehicle_driven( u ); + if( veh ) { + return veh->face.to_string_azimuth_from_north(); + } + return ""; +} + +std::pair display::vehicle_cruise_text_color( const Character &u ) +{ + // Defaults in case no vehicle is found + std::string vel_text; + nc_color vel_color = c_light_gray; + + // Show target velocity and current velocity, with units. + // For example: + // 25 < 10 mph : accelerating towards 25 mph + // 25 < 25 mph : cruising at 25 mph + // 10 < 25 mph : decelerating toward 10 + // Text color indicates how much the engine is straining beyond its safe velocity. + vehicle *veh = vehicle_driven( u ); + if( veh && veh->cruise_on ) { + int target = static_cast( convert_velocity( veh->cruise_velocity, VU_VEHICLE ) ); + int current = static_cast( convert_velocity( veh->velocity, VU_VEHICLE ) ); + const std::string units = get_option ( "USE_METRIC_SPEEDS" ); + vel_text = string_format( "%d < %d %s", target, current, units ); + + const float strain = veh->strain(); + if( strain <= 0 ) { + vel_color = c_light_blue; + } else if( strain <= 0.2 ) { + vel_color = c_yellow; + } else if( strain <= 0.4 ) { + vel_color = c_light_red; + } else { + vel_color = c_red; + } + } + return std::make_pair( vel_text, vel_color ); +} + +std::pair display::vehicle_fuel_percent_text_color( const Character &u ) +{ + // Defaults in case no vehicle is found + std::string fuel_text; + nc_color fuel_color = c_light_gray; + + vehicle *veh = vehicle_driven( u ); + if( veh && veh->cruise_on ) { + itype_id fuel_type = itype_id::NULL_ID(); + // FIXME: Move this to a vehicle helper function like get_active_engine + for( size_t e = 0; e < veh->engines.size(); e++ ) { + if( veh->is_engine_on( e ) && + !( veh->is_perpetual_type( e ) || veh->is_engine_type( e, fuel_type_muscle ) ) ) { + // Get the fuel type of the first engine that is turned on + fuel_type = veh->engine_fuel_current( e ); + } + } + int max_fuel = veh->fuel_capacity( fuel_type ); + int cur_fuel = veh->fuel_left( fuel_type ); + if( max_fuel != 0 ) { + int percent = cur_fuel * 100 / max_fuel; + // Simple percent indicator, yellow under 25%, red under 10% + fuel_text = string_format( "%d %%", percent ); + fuel_color = percent < 10 ? c_red : ( percent < 25 ? c_yellow : c_green ); + } + } + + return std::make_pair( fuel_text, fuel_color ); +} + static void draw_stats( avatar &u, const catacurses::window &w ) { werase( w ); @@ -1482,20 +1598,29 @@ static void draw_stats( avatar &u, const catacurses::window &w ) wnoutrefresh( w ); } -std::pair display::move_mode_text_color( const Character &u ) +// Single-letter move mode (W, R, C, P) +std::pair display::move_mode_letter_color( const Character &u ) { const std::string mm_text = std::string( 1, u.current_movement_mode()->panel_letter() ); const nc_color mm_color = u.current_movement_mode()->panel_color(); return std::make_pair( mm_text, mm_color ); } +// Full name of move mode (walking, running, crouching, prone) +std::pair display::move_mode_text_color( const Character &u ) +{ + const std::string mm_text = u.current_movement_mode()->type_name(); + const nc_color mm_color = u.current_movement_mode()->panel_color(); + return std::make_pair( mm_text, mm_color ); +} + static void draw_stealth( avatar &u, const catacurses::window &w ) { werase( w ); mvwprintz( w, point_zero, c_light_gray, _( "Speed" ) ); mvwprintz( w, point( 7, 0 ), value_color( u.get_speed() ), "%s", u.get_speed() ); - std::pair move_mode = display::move_mode_text_color( u ); + std::pair move_mode = display::move_mode_letter_color( u ); mvwprintz( w, point( 15 - utf8_width( move_mode.first ), 0 ), move_mode.second, move_mode.first ); if( u.is_deaf() ) { @@ -1700,7 +1825,7 @@ static void draw_char_narrow( avatar &u, const catacurses::window &w ) mvwprintz( w, point( 26, 1 ), focus_color( u.get_speed() ), "%s", u.get_speed() ); mvwprintz( w, point( 8, 0 ), c_light_gray, "%s", u.volume ); - std::pair move_mode_pair = display::move_mode_text_color( u ); + std::pair move_mode_pair = display::move_mode_letter_color( u ); std::string movecost = std::to_string( u.movecounter ) + string_format( "(%s)", move_mode_pair.first ); mvwprintz( w, point( 26, 2 ), move_mode_pair.second, "%s", movecost ); @@ -1736,7 +1861,7 @@ static void draw_char_wide( avatar &u, const catacurses::window &w ) mvwprintz( w, point( 23, 1 ), focus_color( u.get_speed() ), "%s", u.get_speed() ); - std::pair move_mode_pair = display::move_mode_text_color( u ); + std::pair move_mode_pair = display::move_mode_letter_color( u ); std::string movecost = std::to_string( u.movecounter ) + string_format( "(%s)", move_mode_pair.first ); mvwprintz( w, point( 38, 1 ), move_mode_pair.second, "%s", movecost ); @@ -2099,13 +2224,20 @@ static void draw_wind_padding( avatar &u, const catacurses::window &w ) render_wind( u, w, " %s: " ); } -static void draw_health_classic( avatar &u, const catacurses::window &w ) +std::pair display::weather_text_color( const Character &u ) { - vehicle *veh = g->remoteveh(); - if( veh == nullptr && u.in_vehicle ) { - veh = veh_pointer_or_null( get_map().veh_at( u.pos() ) ); + if( u.pos().z < 0 ) { + return std::make_pair( _( "Underground" ), c_light_gray ); + } else { + weather_manager &weather = get_weather(); + std::string weather_text = weather.weather_id->name.translated(); + nc_color weather_color = weather.weather_id->color; + return std::make_pair( weather_text, weather_color ); } +} +static void draw_health_classic( avatar &u, const catacurses::window &w ) +{ werase( w ); // 7x7 minimap @@ -2140,6 +2272,8 @@ static void draw_health_classic( avatar &u, const catacurses::window &w ) std::pair morale_pair = display::morale_face_color( u ); mvwprintz( w, point( 34, 1 ), morale_pair.second, morale_pair.first ); + vehicle *veh = vehicle_driven( u ); + if( !veh ) { // stats std::pair pair = display::str_text_color( u ); @@ -2170,7 +2304,7 @@ static void draw_health_classic( avatar &u, const catacurses::window &w ) if( !veh ) { mvwprintz( w, point( 21, 5 ), u.get_speed() < 100 ? c_red : c_white, _( "Spd " ) + std::to_string( u.get_speed() ) ); - std::pair move_mode_pair = display::move_mode_text_color( u ); + std::pair move_mode_pair = display::move_mode_letter_color( u ); std::string move_string = std::to_string( u.movecounter ) + " " + move_mode_pair.first; mvwprintz( w, point( 29, 5 ), move_mode_pair.second, move_string ); } @@ -2516,11 +2650,7 @@ static void draw_veh_compact( const avatar &u, const catacurses::window &w ) { werase( w ); - // vehicle display - vehicle *veh = g->remoteveh(); - if( veh == nullptr && u.in_vehicle ) { - veh = veh_pointer_or_null( get_map().veh_at( u.pos() ) ); - } + vehicle *veh = vehicle_driven( u ); if( veh ) { veh->print_fuel_indicators( w, point_zero ); mvwprintz( w, point( 6, 0 ), c_light_gray, veh->face.to_string_azimuth_from_north() ); @@ -2534,11 +2664,7 @@ static void draw_veh_padding( const avatar &u, const catacurses::window &w ) { werase( w ); - // vehicle display - vehicle *veh = g->remoteveh(); - if( veh == nullptr && u.in_vehicle ) { - veh = veh_pointer_or_null( get_map().veh_at( u.pos() ) ); - } + vehicle *veh = vehicle_driven( u ); if( veh ) { veh->print_fuel_indicators( w, point_east ); mvwprintz( w, point( 7, 0 ), c_light_gray, veh->face.to_string_azimuth_from_north() ); diff --git a/src/panels.h b/src/panels.h index 0847fbd557b20..33873407d8ff0 100644 --- a/src/panels.h +++ b/src/panels.h @@ -63,6 +63,14 @@ std::string activity_malus_str( const Character &u ); // gets the description, printed in player_display, related to your current bmi std::string weight_long_description( const Character &u ); +// For vehicle being driven or remotely piloted by character +// Azimuth (heading) in degrees +std::string vehicle_azimuth_text( const Character &u ); +// Vehicle target/current cruise velocity (and units) with engine strain color +std::pair vehicle_cruise_text_color( const Character &u ); +// Vehicle percent of fuel remaining for currently running engine +std::pair vehicle_fuel_percent_text_color( const Character &u ); + // Functions returning (text, color) pairs std::pair weariness_text_color( size_t weariness ); std::pair weariness_text_color( const Character &u ); @@ -82,10 +90,13 @@ std::pair morale_face_color( const avatar &u ); // Helpers for morale_face_color std::pair morale_emotion( const int morale_cur, const mood_face &face ); -// Current movement mode (as single letter) and color +// Current movement mode and color, as single letter or full word +std::pair move_mode_letter_color( const Character &u ); std::pair move_mode_text_color( const Character &u ); +// Current body part status (bleeding, bitten, infected) phrase and color +std::pair bodypart_status_text_color( const Character &u, + const bodypart_id &bp ); -// TODO: Swap text/string order to match previous functions std::pair temp_text_color( const Character &u ); std::pair power_text_color( const Character &u ); std::pair mana_text_color( const Character &you ); @@ -95,12 +106,16 @@ std::pair int_text_color( const Character &p ); std::pair per_text_color( const Character &p ); std::pair safe_mode_text_color( const bool classic_mode ); std::pair wind_text_color( const Character &u ); +std::pair weather_text_color( const Character &u ); // Define color for displaying the body temperature nc_color bodytemp_color( const Character &u, const bodypart_id &bp ); // Returns color which this limb would have in healing menus nc_color limb_color( const Character &u, const bodypart_id &bp, bool bleed, bool bite, bool infect ); +// Returns status phrase for status of body part (bleeding, bitten, and/or infected) +std::string bodypart_status( const Character &u, const bodypart_id &bp ); + // Color for displaying the given encumbrance level nc_color encumb_color( const int level ); diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 533c59d337607..ad7db2ff1244a 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -3307,6 +3307,11 @@ int vehicle::engine_fuel_left( const int e, bool recurse ) const return 0; } +itype_id vehicle::engine_fuel_current( int e ) const +{ + return parts[ engines[ e ] ].fuel_current(); +} + int vehicle::fuel_capacity( const itype_id &ftype ) const { return std::accumulate( parts.begin(), parts.end(), 0, [&ftype]( const int &lhs, diff --git a/src/vehicle.h b/src/vehicle.h index d6967b4f9a487..3cc0012c8bc50 100644 --- a/src/vehicle.h +++ b/src/vehicle.h @@ -1191,6 +1191,9 @@ class vehicle int fuel_left( int p, bool recurse = false ) const; // Checks how much of an engine's current fuel is left in the tanks. int engine_fuel_left( int e, bool recurse = false ) const; + // Returns what type of fuel an engine uses + itype_id engine_fuel_current( int e ) const; + // Returns total vehicle fuel capacity for the given fuel type int fuel_capacity( const itype_id &ftype ) const; // Returns the total specific energy of this fuel type. Frozen is ignored. diff --git a/src/vehicle_display.cpp b/src/vehicle_display.cpp index ad818458ad842..d6c8909f0580b 100644 --- a/src/vehicle_display.cpp +++ b/src/vehicle_display.cpp @@ -491,10 +491,12 @@ void vehicle::print_speed_gauge( const catacurses::window &win, const point &p, return; } + // Color is based on how much vehicle is straining beyond its safe velocity const float strain = this->strain(); nc_color col_vel = strain <= 0 ? c_light_blue : ( strain <= 0.2 ? c_yellow : ( strain <= 0.4 ? c_light_red : c_red ) ); + // Get cruising (target) velocity, and current (actual) velocity int t_speed = static_cast( convert_velocity( cruise_velocity, VU_VEHICLE ) ); int c_speed = static_cast( convert_velocity( velocity, VU_VEHICLE ) ); auto ndigits = []( int value ) { @@ -507,8 +509,11 @@ void vehicle::print_speed_gauge( const catacurses::window &win, const point &p, int t_offset = ndigits( t_speed ); int c_offset = ndigits( c_speed ); + // Target cruising velocity in green mvwprintz( win, p, c_light_green, "%d", t_speed ); mvwprintz( win, p + point( t_offset + spacing, 0 ), c_light_gray, "<" ); + // Current velocity in color indicating engine strain mvwprintz( win, p + point( t_offset + 1 + 2 * spacing, 0 ), col_vel, "%d", c_speed ); + // Units of speed (mph, km/h, t/t) mvwprintz( win, p + point( t_offset + c_offset + 1 + 3 * spacing, 0 ), c_light_gray, type ); } diff --git a/src/widget.cpp b/src/widget.cpp index 537d5e23a08bb..2e7f367517601 100644 --- a/src/widget.cpp +++ b/src/widget.cpp @@ -98,6 +98,8 @@ std::string enum_to_string( widget_var data ) return "activity_text"; case widget_var::body_temp_text: return "body_temp_text"; + case widget_var::bp_status_text: + return "bp_status_text"; case widget_var::date_text: return "date_text"; case widget_var::env_temp_text: @@ -114,6 +116,10 @@ std::string enum_to_string( widget_var data ) return "mood_text"; case widget_var::moon_phase_text: return "moon_phase_text"; + case widget_var::move_mode_letter: + return "move_mode_letter"; + case widget_var::move_mode_text: + return "move_mode_text"; case widget_var::pain_text: return "pain_text"; case widget_var::place_text: @@ -130,6 +136,14 @@ std::string enum_to_string( widget_var data ) return "thirst_text"; case widget_var::time_text: return "time_text"; + case widget_var::veh_azimuth_text: + return "veh_azimuth_text"; + case widget_var::veh_cruise_text: + return "veh_cruise_text"; + case widget_var::veh_fuel_text: + return "veh_fuel_text"; + case widget_var::weather_text: + return "weather_text"; case widget_var::weariness_text: return "weariness_text"; case widget_var::weary_malus_text: @@ -334,6 +348,7 @@ bool widget::uses_text_function() switch( _var ) { case widget_var::activity_text: case widget_var::body_temp_text: + case widget_var::bp_status_text: case widget_var::date_text: case widget_var::env_temp_text: case widget_var::fatigue_text: @@ -342,6 +357,8 @@ bool widget::uses_text_function() case widget_var::lighting_text: case widget_var::mood_text: case widget_var::moon_phase_text: + case widget_var::move_mode_letter: + case widget_var::move_mode_text: case widget_var::pain_text: case widget_var::place_text: case widget_var::power_text: @@ -350,6 +367,10 @@ bool widget::uses_text_function() case widget_var::style_text: case widget_var::thirst_text: case widget_var::time_text: + case widget_var::veh_azimuth_text: + case widget_var::veh_cruise_text: + case widget_var::veh_fuel_text: + case widget_var::weather_text: case widget_var::weariness_text: case widget_var::weary_malus_text: case widget_var::weight_text: @@ -374,6 +395,9 @@ std::string widget::color_text_function_string( const avatar &ava ) case widget_var::body_temp_text: desc = display::temp_text_color( ava ); break; + case widget_var::bp_status_text: + desc = display::bodypart_status_text_color( ava, _bp_id ); + break; case widget_var::date_text: desc.first = display::date_string(); break; @@ -398,6 +422,12 @@ std::string widget::color_text_function_string( const avatar &ava ) case widget_var::moon_phase_text: desc.first = display::get_moon(); break; + case widget_var::move_mode_letter: + desc = display::move_mode_letter_color( ava ); + break; + case widget_var::move_mode_text: + desc = display::move_mode_text_color( ava ); + break; case widget_var::pain_text: desc = display::pain_text_color( ava ); break; @@ -422,6 +452,18 @@ std::string widget::color_text_function_string( const avatar &ava ) case widget_var::time_text: desc.first = display::time_string( ava ); break; + case widget_var::veh_azimuth_text: + desc.first = display::vehicle_azimuth_text( ava ); + break; + case widget_var::veh_cruise_text: + desc = display::vehicle_cruise_text_color( ava ); + break; + case widget_var::veh_fuel_text: + desc = display::vehicle_fuel_percent_text_color( ava ); + break; + case widget_var::weather_text: + desc = display::weather_text_color( ava ); + break; case widget_var::weariness_text: desc = display::weariness_text_color( ava ); break; diff --git a/src/widget.h b/src/widget.h index 57e097ec427d8..8cdfea091fdc1 100644 --- a/src/widget.h +++ b/src/widget.h @@ -43,6 +43,7 @@ enum class widget_var : int { // Text vars activity_text, // Activity level text, color string body_temp_text, // Felt body temperature, color string + bp_status_text, // Status of bodypart (bleeding, bitten, and/or infected) 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 @@ -51,6 +52,8 @@ enum class widget_var : int { lighting_text, // Current light level, color string mood_text, // Mood as a text emote, color string moon_phase_text,// Current phase of the moon + move_mode_letter, // Movement mode, color letter (W/R/C/P) + move_mode_text, // Movement mode, color text (walking/running/crouching/prone) pain_text, // Pain description text, color string place_text, // Place name in world where character is power_text, // Remaining power from bionics, color string @@ -59,6 +62,10 @@ enum class widget_var : int { style_text, // Active martial arts style name thirst_text, // Thirst description text, color string time_text, // Current time - exact if character has a watch, approximate otherwise + veh_azimuth_text, // Azimuth or heading in degrees, string + veh_cruise_text, // Current/target cruising speed in vehicle, color string + veh_fuel_text, // Current/total fuel for active vehicle engine, color string + weather_text, // Weather/sky conditions (if visible), color string weariness_text, // Weariness description text, color string weary_malus_text, // Weariness malus or penalty weight_text, // Weight description text, color string diff --git a/tests/options_helpers.cpp b/tests/options_helpers.cpp index 2d24ac90df7a4..001d2a64497d4 100644 --- a/tests/options_helpers.cpp +++ b/tests/options_helpers.cpp @@ -19,11 +19,13 @@ override_option::~override_option() scoped_weather_override::scoped_weather_override( const weather_type_id &weather ) { get_weather().weather_override = weather; + get_weather().set_nextweather( calendar::turn ); } void scoped_weather_override::with_windspeed( const int windspeed_override ) { get_weather().windspeed_override = windspeed_override; + get_weather().set_nextweather( calendar::turn ); } scoped_weather_override::~scoped_weather_override() @@ -31,4 +33,5 @@ scoped_weather_override::~scoped_weather_override() weather_manager &weather = get_weather(); weather.weather_override = WEATHER_NULL; weather.windspeed_override.reset(); + get_weather().set_nextweather( calendar::turn ); } diff --git a/tests/widget_test.cpp b/tests/widget_test.cpp index 2e15749826767..2768523ab8df1 100644 --- a/tests/widget_test.cpp +++ b/tests/widget_test.cpp @@ -2,10 +2,29 @@ #include "player_helpers.h" #include "morale.h" +#include "options_helpers.h" +#include "weather.h" +#include "weather_type.h" #include "widget.h" +static const efftype_id effect_bite( "bite" ); +static const efftype_id effect_bleed( "bleed" ); +static const efftype_id effect_infected( "infected" ); + static const itype_id itype_rad_badge( "rad_badge" ); +static const move_mode_id move_mode_crouch( "crouch" ); +static const move_mode_id move_mode_prone( "prone" ); +static const move_mode_id move_mode_run( "run" ); +static const move_mode_id move_mode_walk( "walk" ); + +static const weather_type_id weather_acid_rain( "acid_rain" ); +static const weather_type_id weather_cloudy( "cloudy" ); +static const weather_type_id weather_drizzle( "drizzle" ); +static const weather_type_id weather_portal_storm( "portal_storm" ); +static const weather_type_id weather_snowing( "snowing" ); +static const weather_type_id weather_sunny( "sunny" ); + // test widgets defined in data/json/sidebar.json and data/mods/TEST_DATA/widgets.json static const widget_id widget_test_bp_wetness_head_num( "test_bp_wetness_head_num" ); static const widget_id widget_test_bp_wetness_torso_num( "test_bp_wetness_torso_num" ); @@ -15,12 +34,18 @@ static const widget_id widget_test_color_graph_widget( "test_color_graph_widget" static const widget_id widget_test_color_number_widget( "test_color_number_widget" ); 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_health_num( "test_health_num" ); +static const widget_id widget_test_health_text( "test_health_text" ); static const widget_id widget_test_hp_head_graph( "test_hp_head_graph" ); static const widget_id widget_test_hp_head_num( "test_hp_head_num" ); static const widget_id widget_test_int_num( "test_int_num" ); static const widget_id widget_test_mana_num( "test_mana_num" ); static const widget_id widget_test_morale_num( "test_morale_num" ); +static const widget_id widget_test_move_mode_letter( "test_move_mode_letter" ); +static const widget_id widget_test_move_mode_text( "test_move_mode_text" ); static const widget_id widget_test_move_num( "test_move_num" ); +static const widget_id widget_test_pain_num( "test_pain_num" ); +static const widget_id widget_test_pain_text( "test_pain_text" ); static const widget_id widget_test_per_num( "test_per_num" ); static const widget_id widget_test_pool_graph( "test_pool_graph" ); static const widget_id widget_test_rad_badge_text( "test_rad_badge_text" ); @@ -28,9 +53,12 @@ static const widget_id widget_test_speed_num( "test_speed_num" ); static const widget_id widget_test_stamina_graph( "test_stamina_graph" ); static const widget_id widget_test_stamina_num( "test_stamina_num" ); static const widget_id widget_test_stat_panel( "test_stat_panel" ); +static const widget_id widget_test_status_left_arm_text( "test_status_left_arm_text" ); +static const widget_id widget_test_status_torso_text( "test_status_torso_text" ); static const widget_id widget_test_str_num( "test_str_num" ); static const widget_id widget_test_text_widget( "test_text_widget" ); static const widget_id widget_test_weariness_num( "test_weariness_num" ); +static const widget_id widget_test_weather_text( "test_weather_text" ); TEST_CASE( "widget value strings", "[widget][value][string]" ) { @@ -226,7 +254,7 @@ TEST_CASE( "widgets", "[widget][graph][color]" ) } } -TEST_CASE( "widgets showing avatar attributes", "[widget][avatar]" ) +TEST_CASE( "stats widgets", "[widget][avatar][stats]" ) { avatar &ava = get_avatar(); clear_avatar(); @@ -247,6 +275,12 @@ TEST_CASE( "widgets showing avatar attributes", "[widget][avatar]" ) CHECK( int_w.layout( ava ) == "INT: 7" ); CHECK( per_w.layout( ava ) == "PER: 13" ); } +} + +TEST_CASE( "stamina widget", "[widget][avatar][stamina]" ) +{ + avatar &ava = get_avatar(); + clear_avatar(); SECTION( "stamina" ) { widget stamina_num_w = widget_test_stamina_num.obj(); @@ -270,8 +304,15 @@ TEST_CASE( "widgets showing avatar attributes", "[widget][avatar]" ) CHECK( stamina_num_w.layout( ava ) == "STAMINA: 10000" ); CHECK( stamina_graph_w.layout( ava ) == "STAMINA: ##########" ); } +} - SECTION( "speed pool" ) { +TEST_CASE( "speed widget", "[widget][avatar][speed]" ) +{ + avatar &ava = get_avatar(); + clear_avatar(); + + + SECTION( "speed number" ) { widget speed_w = widget_test_speed_num.obj(); ava.set_speed_base( 90 ); @@ -279,8 +320,15 @@ TEST_CASE( "widgets showing avatar attributes", "[widget][avatar]" ) ava.set_speed_base( 240 ); CHECK( speed_w.layout( ava ) == "SPEED: 240" ); } +} - SECTION( "focus pool" ) { +TEST_CASE( "focus widget", "[widget][avatar][focus]" ) +{ + avatar &ava = get_avatar(); + clear_avatar(); + + + SECTION( "focus number" ) { widget focus_w = widget_test_focus_num.obj(); ava.set_focus( 75 ); @@ -288,8 +336,15 @@ TEST_CASE( "widgets showing avatar attributes", "[widget][avatar]" ) ava.set_focus( 120 ); CHECK( focus_w.layout( ava ) == "FOCUS: 120" ); } +} + +TEST_CASE( "mana widget", "[widget][avatar][mana]" ) +{ + avatar &ava = get_avatar(); + clear_avatar(); + - SECTION( "mana pool" ) { + SECTION( "mana number" ) { widget mana_w = widget_test_mana_num.obj(); ava.magic->set_mana( 150 ); @@ -297,8 +352,14 @@ TEST_CASE( "widgets showing avatar attributes", "[widget][avatar]" ) ava.magic->set_mana( 450 ); CHECK( mana_w.layout( ava ) == "MANA: 450" ); } +} + +TEST_CASE( "morale widget", "[widget][avatar][morale]" ) +{ + avatar &ava = get_avatar(); + clear_avatar(); - SECTION( "morale" ) { + SECTION( "morale number" ) { widget morale_w = widget_test_morale_num.obj(); ava.clear_morale(); @@ -310,8 +371,14 @@ TEST_CASE( "widgets showing avatar attributes", "[widget][avatar]" ) ava.add_morale( MORALE_KILLED_INNOCENT, -100 ); CHECK( morale_w.layout( ava ) == "MORALE: -100" ); } +} + +TEST_CASE( "move counter widget", "[widget][avatar][move]" ) +{ + avatar &ava = get_avatar(); + clear_avatar(); - SECTION( "move counter" ) { + SECTION( "move counter number" ) { widget move_w = widget_test_move_num.obj(); ava.movecounter = 80; @@ -319,6 +386,69 @@ TEST_CASE( "widgets showing avatar attributes", "[widget][avatar]" ) ava.movecounter = 150; CHECK( move_w.layout( ava ) == "MOVE: 150" ); } +} + +TEST_CASE( "movement mode widget", "[widget][avatar][move_mode]" ) +{ + avatar &ava = get_avatar(); + clear_avatar(); + + SECTION( "movement mode" ) { + widget mode_letter_w = widget_test_move_mode_letter.obj(); + widget mode_text_w = widget_test_move_mode_text.obj(); + + ava.set_movement_mode( move_mode_walk ); + CHECK( mode_letter_w.layout( ava ) == "MODE: W" ); + CHECK( mode_text_w.layout( ava ) == "MODE: walking" ); + ava.set_movement_mode( move_mode_run ); + CHECK( mode_letter_w.layout( ava ) == "MODE: R" ); + CHECK( mode_text_w.layout( ava ) == "MODE: running" ); + ava.set_movement_mode( move_mode_crouch ); + CHECK( mode_letter_w.layout( ava ) == "MODE: C" ); + CHECK( mode_text_w.layout( ava ) == "MODE: crouching" ); + ava.set_movement_mode( move_mode_prone ); + CHECK( mode_letter_w.layout( ava ) == "MODE: P" ); + CHECK( mode_text_w.layout( ava ) == "MODE: prone" ); + } +} + +TEST_CASE( "health widget", "[widget][avatar][health]" ) +{ + avatar &ava = get_avatar(); + clear_avatar(); + + SECTION( "health" ) { + widget health_num_w = widget_test_health_num.obj(); + widget health_text_w = widget_test_health_text.obj(); + + ava.set_healthy( 120 ); + CHECK( health_num_w.layout( ava ) == "HEALTH: 120" ); + CHECK( health_text_w.layout( ava ) == "HEALTH: Excellent" ); + ava.set_healthy( 80 ); + CHECK( health_num_w.layout( ava ) == "HEALTH: 80" ); + CHECK( health_text_w.layout( ava ) == "HEALTH: Very good" ); + ava.set_healthy( 40 ); + CHECK( health_num_w.layout( ava ) == "HEALTH: 40" ); + CHECK( health_text_w.layout( ava ) == "HEALTH: Good" ); + ava.set_healthy( 0 ); + CHECK( health_num_w.layout( ava ) == "HEALTH: 0" ); + CHECK( health_text_w.layout( ava ) == "HEALTH: OK" ); + ava.set_healthy( -40 ); + CHECK( health_num_w.layout( ava ) == "HEALTH: -40" ); + CHECK( health_text_w.layout( ava ) == "HEALTH: Bad" ); + ava.set_healthy( -80 ); + CHECK( health_num_w.layout( ava ) == "HEALTH: -80" ); + CHECK( health_text_w.layout( ava ) == "HEALTH: Very bad" ); + ava.set_healthy( -120 ); + CHECK( health_num_w.layout( ava ) == "HEALTH: -120" ); + CHECK( health_text_w.layout( ava ) == "HEALTH: Horrible" ); + } +} + +TEST_CASE( "hit points widget", "[widget][avatar][hp]" ) +{ + avatar &ava = get_avatar(); + clear_avatar(); SECTION( "hit points" ) { bodypart_id head( "head" ); @@ -341,6 +471,126 @@ TEST_CASE( "widgets showing avatar attributes", "[widget][avatar]" ) // NOLINTNEXTLINE(cata-text-style): suppress "unnecessary space" warning before commas CHECK( head_graph_w.layout( ava ) == "HEAD: ,,,,," ); } +} + +TEST_CASE( "bodypart status widget", "[widget][avatar][bodypart]" ) +{ + avatar &ava = get_avatar(); + clear_avatar(); + + SECTION( "bodypart status" ) { + bodypart_id arm( "arm_l" ); + bodypart_id torso( "torso" ); + widget arm_status_w = widget_test_status_left_arm_text.obj(); + widget torso_status_w = widget_test_status_torso_text.obj(); + + // No ailments + CHECK( arm_status_w.layout( ava ) == "LEFT ARM STATUS: --" ); + CHECK( torso_status_w.layout( ava ) == "TORSO STATUS: --" ); + + // Add various ailments to the left arm, and ensure status is displayed correctly, + // while torso status display is unaffected + + WHEN( "bitten" ) { + ava.add_effect( effect_bite, 1_minutes, arm ); + CHECK( arm_status_w.layout( ava ) == "LEFT ARM STATUS: bitten" ); + CHECK( torso_status_w.layout( ava ) == "TORSO STATUS: --" ); + } + + WHEN( "bleeding" ) { + ava.add_effect( effect_bleed, 1_minutes, arm ); + CHECK( arm_status_w.layout( ava ) == "LEFT ARM STATUS: bleeding" ); + CHECK( torso_status_w.layout( ava ) == "TORSO STATUS: --" ); + } + + WHEN( "infected" ) { + ava.add_effect( effect_infected, 1_minutes, arm ); + CHECK( arm_status_w.layout( ava ) == "LEFT ARM STATUS: infected" ); + CHECK( torso_status_w.layout( ava ) == "TORSO STATUS: --" ); + } + + WHEN( "bitten and bleeding" ) { + ava.add_effect( effect_bite, 1_minutes, arm ); + ava.add_effect( effect_bleed, 1_minutes, arm ); + CHECK( arm_status_w.layout( ava ) == "LEFT ARM STATUS: bitten, bleeding" ); + CHECK( torso_status_w.layout( ava ) == "TORSO STATUS: --" ); + } + + WHEN( "bitten and infected" ) { + ava.add_effect( effect_bite, 1_minutes, arm ); + ava.add_effect( effect_infected, 1_minutes, arm ); + CHECK( arm_status_w.layout( ava ) == "LEFT ARM STATUS: bitten, infected" ); + CHECK( torso_status_w.layout( ava ) == "TORSO STATUS: --" ); + } + + WHEN( "bleeding and infected" ) { + ava.add_effect( effect_bleed, 1_minutes, arm ); + ava.add_effect( effect_infected, 1_minutes, arm ); + CHECK( arm_status_w.layout( ava ) == + "LEFT ARM STATUS: bleeding, infected" ); + CHECK( torso_status_w.layout( ava ) == "TORSO STATUS: --" ); + } + + WHEN( "bitten, bleeding, and infected" ) { + ava.add_effect( effect_bite, 1_minutes, arm ); + ava.add_effect( effect_bleed, 1_minutes, arm ); + ava.add_effect( effect_infected, 1_minutes, arm ); + CHECK( arm_status_w.layout( ava ) == + "LEFT ARM STATUS: bitten, bleeding, infected" ); + CHECK( torso_status_w.layout( ava ) == "TORSO STATUS: --" ); + } + } +} + +TEST_CASE( "pain widget", "[widget][avatar][pain]" ) +{ + avatar &ava = get_avatar(); + clear_avatar(); + + SECTION( "pain" ) { + widget pain_num_w = widget_test_pain_num.obj(); + widget pain_text_w = widget_test_pain_text.obj(); + + ava.set_pain( 0 ); + CHECK( pain_num_w.layout( ava ) == "PAIN: 0" ); + CHECK( pain_text_w.layout( ava ) == "PAIN: " ); + // FIXME: Is it possible for "No pain" to be displayed? + //CHECK( pain_text_w.layout( ava ) == "PAIN: No pain" ); + + ava.set_pain( 1 ); + CHECK( pain_num_w.layout( ava ) == "PAIN: 1" ); + CHECK( pain_text_w.layout( ava ) == "PAIN: Minimal pain" ); + ava.set_pain( 10 ); + CHECK( pain_num_w.layout( ava ) == "PAIN: 10" ); + CHECK( pain_text_w.layout( ava ) == "PAIN: Minimal pain" ); + ava.set_pain( 20 ); + CHECK( pain_num_w.layout( ava ) == "PAIN: 20" ); + CHECK( pain_text_w.layout( ava ) == "PAIN: Mild pain" ); + ava.set_pain( 30 ); + CHECK( pain_num_w.layout( ava ) == "PAIN: 30" ); + CHECK( pain_text_w.layout( ava ) == "PAIN: Moderate pain" ); + ava.set_pain( 40 ); + CHECK( pain_num_w.layout( ava ) == "PAIN: 40" ); + CHECK( pain_text_w.layout( ava ) == "PAIN: Distracting pain" ); + ava.set_pain( 50 ); + CHECK( pain_num_w.layout( ava ) == "PAIN: 50" ); + CHECK( pain_text_w.layout( ava ) == "PAIN: Distressing pain" ); + ava.set_pain( 60 ); + CHECK( pain_num_w.layout( ava ) == "PAIN: 60" ); + CHECK( pain_text_w.layout( ava ) == "PAIN: Unmanageable pain" ); + ava.set_pain( 70 ); + CHECK( pain_num_w.layout( ava ) == "PAIN: 70" ); + CHECK( pain_text_w.layout( ava ) == "PAIN: Intense pain" ); + ava.set_pain( 80 ); + CHECK( pain_num_w.layout( ava ) == "PAIN: 80" ); + CHECK( pain_text_w.layout( ava ) == "PAIN: Severe pain" ); + } +} + +TEST_CASE( "weariness widget", "[widget][avatar][weariness]" ) +{ + avatar &ava = get_avatar(); + clear_avatar(); SECTION( "weariness" ) { widget weariness_w = widget_test_weariness_num.obj(); @@ -348,6 +598,12 @@ TEST_CASE( "widgets showing avatar attributes", "[widget][avatar]" ) CHECK( weariness_w.layout( ava ) == "WEARINESS: 0" ); // TODO: Check weariness set to other levels } +} + +TEST_CASE( "wetness widget", "[widget][avatar][wetness]" ) +{ + avatar &ava = get_avatar(); + clear_avatar(); SECTION( "wetness" ) { widget head_wetness_w = widget_test_bp_wetness_head_num.obj(); @@ -393,6 +649,52 @@ TEST_CASE( "radiation badge widget", "[widget][radiation]" ) CHECK( rads_w.layout( ava ) == "RADIATION: black " ); } +TEST_CASE( "widgets showing weather conditions", "[widget][weather]" ) +{ + widget weather_w = widget_test_weather_text.obj(); + + avatar &ava = get_avatar(); + clear_avatar(); + + SECTION( "weather conditions" ) { + SECTION( "sunny" ) { + scoped_weather_override forecast( weather_sunny ); + REQUIRE( get_weather().weather_id->name.translated() == "Sunny" ); + CHECK( weather_w.layout( ava ) == "Weather: Sunny" ); + } + + SECTION( "cloudy" ) { + scoped_weather_override forecast( weather_cloudy ); + CHECK( weather_w.layout( ava ) == "Weather: Cloudy" ); + } + + SECTION( "drizzle" ) { + scoped_weather_override forecast( weather_drizzle ); + CHECK( weather_w.layout( ava ) == "Weather: Drizzle" ); + } + + SECTION( "snowing" ) { + scoped_weather_override forecast( weather_snowing ); + CHECK( weather_w.layout( ava ) == "Weather: Snowing" ); + } + + SECTION( "acid rain" ) { + scoped_weather_override forecast( weather_acid_rain ); + CHECK( weather_w.layout( ava ) == "Weather: Acid Rain" ); + } + + SECTION( "portal storm" ) { + scoped_weather_override forecast( weather_portal_storm ); + CHECK( weather_w.layout( ava ) == "Weather: Portal Storm" ); + } + + SECTION( "cannot see weather when underground" ) { + ava.setpos( tripoint_below ); + CHECK( weather_w.layout( ava ) == "Weather: Underground" ); + } + } +} + TEST_CASE( "layout widgets", "[widget][layout]" ) { widget stats_w = widget_test_stat_panel.obj();