From 327f76110a435267295d4c086a775f1e327591b5 Mon Sep 17 00:00:00 2001 From: redrosedialtone <64289850+redrosedialtone@users.noreply.github.com> Date: Tue, 25 Jan 2022 16:30:22 +1100 Subject: [PATCH 01/10] Medical Menu Implementation Implements the initial steps for the Medical Menu, changes including; ~keybindings.json - Adds 'View Medical' hotkey under 'N' ~action.cpp / ~action.h / ~avatar.h / handle_action.cpp / game.cpp - Adds the corresponding action to 'View Medical' +medical_ui.cpp - Menu implementation ~display.h / display.cpp - Exposes bodypart_status_colors --- data/raw/keybindings.json | 14 + src/action.cpp | 4 + src/action.h | 2 + src/avatar.h | 2 + src/display.cpp | 2 +- src/display.h | 1 + src/game.cpp | 1 + src/handle_action.cpp | 4 + src/medical_ui.cpp | 647 ++++++++++++++++++++++++++++++++++++++ 9 files changed, 676 insertions(+), 1 deletion(-) create mode 100644 src/medical_ui.cpp diff --git a/data/raw/keybindings.json b/data/raw/keybindings.json index 5405e13c4e8b0..72cef1d060636 100644 --- a/data/raw/keybindings.json +++ b/data/raw/keybindings.json @@ -1852,6 +1852,13 @@ "name": "Toggle activate/examine", "bindings": [ { "input_method": "keyboard_char", "key": "!" }, { "input_method": "keyboard_code", "key": "1", "mod": [ "shift" ] } ] }, + { + "type": "keybinding", + "id": "APPLY", + "category": "MEDICAL", + "name": "Apply or Use Item", + "bindings": [ { "input_method": "keyboard_char", "key": "A" }, { "input_method": "keyboard_code", "key": "a", "mod": [ "shift" ] } ] + }, { "type": "keybinding", "name": "Pause", @@ -2218,6 +2225,13 @@ "id": "mutations", "bindings": [ { "input_method": "keyboard_any", "key": "[" } ] }, + { + "type": "keybinding", + "name": "View Medical", + "category": "DEFAULTMODE", + "id": "medical", + "bindings": [ { "input_method": "keyboard_char", "key": "N" }, { "input_method": "keyboard_code", "key": "n", "mod": [ "shift" ] } ] + }, { "type": "keybinding", "name": "Re-layer armor/clothing", diff --git a/src/action.cpp b/src/action.cpp index 32650c586a65c..013b3a24c6495 100644 --- a/src/action.cpp +++ b/src/action.cpp @@ -309,6 +309,8 @@ std::string action_ident( action_id act ) return "factions"; case ACTION_SCORES: return "scores"; + case ACTION_MEDICAL: + return "medical"; case ACTION_MORALE: return "morale"; case ACTION_MESSAGES: @@ -431,6 +433,7 @@ bool can_action_change_worldstate( const action_id act ) case ACTION_SCORES: case ACTION_FACTIONS: case ACTION_MORALE: + case ACTION_MEDICAL: case ACTION_MESSAGES: case ACTION_HELP: case ACTION_MAIN_MENU: @@ -947,6 +950,7 @@ action_id handle_action_menu() REGISTER_ACTION( ACTION_SCORES ); REGISTER_ACTION( ACTION_FACTIONS ); REGISTER_ACTION( ACTION_MORALE ); + REGISTER_ACTION( ACTION_MEDICAL ); REGISTER_ACTION( ACTION_MESSAGES ); } else if( category == _( "Misc" ) ) { REGISTER_ACTION( ACTION_WAIT ); diff --git a/src/action.h b/src/action.h index 2503e4ee28606..d3fefb7aecf08 100644 --- a/src/action.h +++ b/src/action.h @@ -256,6 +256,8 @@ enum action_id : int { ACTION_FACTIONS, /** Display morale effects screen */ ACTION_MORALE, + /** Displays medical menu */ + ACTION_MEDICAL, /** Display messages screen */ ACTION_MESSAGES, /** Display help screen */ diff --git a/src/avatar.h b/src/avatar.h index 2cc16b472fc60..eb07243a73bba 100644 --- a/src/avatar.h +++ b/src/avatar.h @@ -143,6 +143,8 @@ class avatar : public Character /** Provides the window and detailed morale data */ void disp_morale(); + /** Opens the medical window */ + void disp_medical(); /** Resets stats, and applies effects in an idempotent manner */ void reset_stats() override; /** Resets all missions before saving character to template */ diff --git a/src/display.cpp b/src/display.cpp index 01b686929ddaa..6f89ec157a77a 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -1017,7 +1017,7 @@ std::pair display::vehicle_fuel_percent_text_color( const } // Return status/color pairs for all statuses affecting body part (bleeding, bitten, bandaged, etc.) -static std::map bodypart_status_colors( const Character &u, +std::map display::bodypart_status_colors( const Character &u, const bodypart_id &bp, const std::string &wgt_id ) { // List of active statuses and associated colors diff --git a/src/display.h b/src/display.h index 6238d8e4118c0..7c9415cacfadb 100644 --- a/src/display.h +++ b/src/display.h @@ -90,6 +90,7 @@ std::pair fatigue_text_color( const Character &u ); std::pair health_text_color( const Character &u ); std::pair pain_text_color( const Creature &c ); std::pair pain_text_color( const Character &u ); +std::map bodypart_status_colors(const Character& u, const bodypart_id& bp, const std::string& wgt_id); // Change in character body temperature, as colorized arrows std::pair temp_delta_arrows( const Character &u ); // Character morale, as a color-coded ascii emoticon face diff --git a/src/game.cpp b/src/game.cpp index 8cfcfe6e14680..4bc240005a9f4 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -2311,6 +2311,7 @@ input_context get_default_mode_input_context() ctxt.register_action( "drop_adj" ); ctxt.register_action( "bionics" ); ctxt.register_action( "mutations" ); + ctxt.register_action( "medical" ); ctxt.register_action( "sort_armor" ); ctxt.register_action( "wait" ); ctxt.register_action( "craft" ); diff --git a/src/handle_action.cpp b/src/handle_action.cpp index 52eb9ef19ecd5..06b4b2577f7e7 100644 --- a/src/handle_action.cpp +++ b/src/handle_action.cpp @@ -2484,6 +2484,10 @@ bool game::do_regular_action( action_id &act, avatar &player_character, player_character.disp_morale(); break; + case ACTION_MEDICAL: + player_character.disp_medical(); + break; + case ACTION_MESSAGES: Messages::display_messages(); break; diff --git a/src/medical_ui.cpp b/src/medical_ui.cpp new file mode 100644 index 0000000000000..725e602bb7d9a --- /dev/null +++ b/src/medical_ui.cpp @@ -0,0 +1,647 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "action.h" +#include "addiction.h" +#include "avatar_action.h" +#include "character.h" +#include "display.h" +#include "effect.h" +#include "flag.h" +#include "game.h" +#include "output.h" +#include "ui_manager.h" +#include "vitamin.h" +#include "weather.h" + +static const efftype_id effect_bandaged("bandaged"); +static const efftype_id effect_bite("bite"); +static const efftype_id effect_bleed("bleed"); +static const efftype_id effect_infected("infected"); +static const efftype_id effect_disinfected("disinfected"); +static const efftype_id effect_mending("mending"); +static const efftype_id effect_hypovolemia("hypovolemia"); + +static const trait_id trait_NOPAIN("NOPAIN"); +static const trait_id trait_TROGLO("TROGLO"); +static const trait_id trait_TROGLO2("TROGLO2"); +static const trait_id trait_TROGLO3("TROGLO3"); + +static const vitamin_id vitamin_blood("blood"); + +enum class medical_tab_mode { + TAB_SUMMARY +}; + +static std::string coloured_stat_display(int statCur, int statMax) { + nc_color cstatus; + if (statCur <= 0) { + cstatus = c_dark_gray; + } + else if (statCur < statMax / 2) { + cstatus = c_red; + } + else if (statCur < statMax) { + cstatus = c_light_red; + } + else if (statCur == statMax) { + cstatus = c_white; + } + else if (statCur < statMax * 1.5) { + cstatus = c_light_green; + } + else { + cstatus = c_green; + } + std::string cur = colorize(string_format(_("%2d"), statCur), cstatus); + return string_format(_("%s (%s)"), cur, statMax); +} + +static void draw_medical_titlebar(const catacurses::window& window, avatar* player, + const int WIDTH) +{ + input_context ctxt("MEDICAL", keyboard_mode::keychar); + const Character& you = *player->as_character(); + + werase(window); + draw_border(window, BORDER_COLOR, _(" MEDICAL ")); + + /* Window Title */ + center_print(window, 0, c_blue, _(" MEDICAL ")); + + /* Tabs */ + const std::vector> tabs = { + { medical_tab_mode::TAB_SUMMARY, string_format(_("SUMMARY")) } + }; + draw_tabs(window, tabs, medical_tab_mode::TAB_SUMMARY); + + const int TAB_WIDTH = 12; + + // Draw symbols to connect additional lines to border + + int width = getmaxx(window); + int height = getmaxy(window); + for (int i = 1; i < height - 1; ++i) { + // | + mvwputch(window, point(0, i), BORDER_COLOR, LINE_XOXO); + // | + mvwputch(window, point(width - 1, i), BORDER_COLOR, LINE_XOXO); + } + // |- + mvwputch(window, point(0, height - 1), BORDER_COLOR, LINE_XXXO); + // -| + mvwputch(window, point(width - 1, height - 1), BORDER_COLOR, LINE_XOXX); + + int right_indent = 2; + + /* Pain Indicator */ + auto pain_descriptor = display::pain_text_color(*player); + if (pain_descriptor.first.size() > 0) { + const std::string pain_str = "In " + pain_descriptor.first; + + const int pain_str_pos = right_print(window, 1, right_indent, pain_descriptor.second, pain_str); + + /* ~~Borders */ + for (int i = 1; i < getmaxy(window) - 1; i++) { + mvwputch(window, point(pain_str_pos - 2, i), BORDER_COLOR, LINE_XOXO); // | + } + mvwputch(window, point(pain_str_pos - 2, 0), BORDER_COLOR, LINE_OXXX); // ^|^ + mvwputch(window, point(pain_str_pos - 2, 2), BORDER_COLOR, LINE_XXOX); // _|_ + right_indent += utf8_width(remove_color_tags(pain_str)) + 3; + } + + /* Hunger, Thirst & Fatigue Indicator */ + + const std::pair hunger_pair = display::hunger_text_color(you); + const std::pair thirst_pair = display::thirst_text_color(you); + const std::pair fatigue_pair = display::fatigue_text_color(you); + int cur_str_pos = 0; + + // Hunger + if (hunger_pair.first.size() > 0) { + cur_str_pos = right_print(window, 1, right_indent, hunger_pair.second, hunger_pair.first); + + /* ~~Borders */ + for (int i = 1; i < getmaxy(window) - 1; i++) { + mvwputch(window, point(cur_str_pos - 2, i), BORDER_COLOR, LINE_XOXO); // | + } + mvwputch(window, point(cur_str_pos - 2, 0), BORDER_COLOR, LINE_OXXX); // ^|^ + mvwputch(window, point(cur_str_pos - 2, 2), BORDER_COLOR, LINE_XXOX); // _|_ + + right_indent += utf8_width(hunger_pair.first) + 3; + } + + // Thirst + if (thirst_pair.first.size() > 0) { + cur_str_pos = right_print(window, 1, right_indent, thirst_pair.second, thirst_pair.first); + + /* ~~Borders */ + for (int i = 1; i < getmaxy(window) - 1; i++) { + mvwputch(window, point(cur_str_pos - 2, i), BORDER_COLOR, LINE_XOXO); // | + } + mvwputch(window, point(cur_str_pos - 2, 0), BORDER_COLOR, LINE_OXXX); // ^|^ + mvwputch(window, point(cur_str_pos - 2, 2), BORDER_COLOR, LINE_XXOX); // _|_ + + right_indent += utf8_width(thirst_pair.first) + 3; + } + + //Fatigue + if (fatigue_pair.first.size() > 0) { + cur_str_pos = right_print(window, 1, right_indent, fatigue_pair.second, fatigue_pair.first); + + /* ~~Borders */ + for (int i = 1; i < getmaxy(window) - 1; i++) { + mvwputch(window, point(cur_str_pos - 2, i), BORDER_COLOR, LINE_XOXO); // | + } + mvwputch(window, point(cur_str_pos - 2, 0), BORDER_COLOR, LINE_OXXX); // ^|^ + mvwputch(window, point(cur_str_pos - 2, 2), BORDER_COLOR, LINE_XXOX); // _|_ + + right_indent += utf8_width(fatigue_pair.first) + 3; + } + + /* Hotkey Helper */ + std::string desc; + desc = string_format(_( + "[%s]Apply Item [%s]Keybindings"), + ctxt.get_desc("APPLY"), ctxt.get_desc("HELP_KEYBINDINGS")); + + const int max_width = right_indent + TAB_WIDTH; + if (WIDTH - max_width > 3) // If the window runs out of room, we won't print keybindings. + { + right_print(window, 1, right_indent, c_white, desc); + } +} + +void avatar::disp_medical() +{ + medical_tab_mode tab_mode = medical_tab_mode::TAB_SUMMARY; + const Character& you = *this->as_character(); + + /* Title Bar - Tabs, Pain Indicator & Blood Indicator */ + catacurses::window w_title; + /* Primary Window */ + catacurses::window wMedical; + /* Bottom detail bar, for printing selection_line.description() */ + catacurses::window w_description; + + const int TITLE_W_HEIGHT = 3; + const int DESC_W_HEIGHT = 6; // Consistent with Player Info (@) Menu + const int TEXT_START_Y = TITLE_W_HEIGHT; + int DESC_W_BEGIN = 0; + + int HEIGHT = 0; + int WIDTH = 0; + int second_column_x = 0; + int third_column_x = 0; + + point cursor(0, 0); + int selected_column_rows = 0; + + class selection_line + { + public: + selection_line() = default; + selection_line(const std::string& header_str, const std::string& desc_str) + : header_str(header_str), desc_str(desc_str) {} + + int print(const catacurses::window& win, const point begin, const int& width, const nc_color& base_color, bool selected) + { + if (selected) { + std::string highlight = colorize(">", h_white); + header_str = highlight + header_str; + } + return linecount = fold_and_print(win, begin, width, base_color, header_str); + } + + std::string description() { + return desc_str; + } + + int get_line_count() { + return linecount; + } + + private: + std::string header_str; + std::string desc_str; + int linecount; + }; + + ui_adaptor ui; + ui.on_screen_resize([&](ui_adaptor& ui) { + HEIGHT = std::min(TERMY, + std::max(FULL_SCREEN_HEIGHT, + TITLE_W_HEIGHT + DESC_W_HEIGHT + 5)); + WIDTH = FULL_SCREEN_WIDTH + (TERMX - FULL_SCREEN_WIDTH) / 5; + + const point win((TERMX - WIDTH) / 2, (TERMY - HEIGHT) / 2); + + wMedical = catacurses::newwin(HEIGHT, WIDTH, win); + + w_title = catacurses::newwin(TITLE_W_HEIGHT, WIDTH, win); + + DESC_W_BEGIN = HEIGHT - DESC_W_HEIGHT - 1; + w_description = catacurses::newwin(DESC_W_HEIGHT, WIDTH - 2, + point(win.x + 1, DESC_W_BEGIN + win.y)); + + second_column_x = 20 + (TERMX - FULL_SCREEN_WIDTH) / 6; + third_column_x = second_column_x + 8 + (TERMX - FULL_SCREEN_WIDTH) / 6; + + ui.position_from_window(wMedical); + }); + + ui.mark_resize(); + + std::vector SummaryLines; + std::vector EffectLines; + std::vector StatLines; + selection_line highlightline; + + ui.on_redraw([&](const ui_adaptor&) { + werase(wMedical); + + draw_border(wMedical, BORDER_COLOR, _(" MEDICAL ")); + mvwputch(wMedical, point(getmaxx(wMedical) - 1, 2), BORDER_COLOR, LINE_XOXX); // -| + + wnoutrefresh(wMedical); + + draw_medical_titlebar(w_title, this, WIDTH); + mvwputch(w_title, point(second_column_x - 2, TEXT_START_Y - 1), BORDER_COLOR, LINE_OXXX); // ^|^ + mvwputch(w_title, point(third_column_x - 2, TEXT_START_Y - 1), BORDER_COLOR, LINE_OXXX); // ^|^ + wnoutrefresh(w_title); + + + /* FIRST COLUUMN - HEALTH */ + + SummaryLines.clear(); + + // Header + fold_and_print(wMedical, point(2, TEXT_START_Y), WIDTH - 2, c_light_blue, "HEALTH"); + + const int left_padding = 1; // including border + const int right_padding = 1; // including border + const int SUMMARY_WIDTH = second_column_x - left_padding; // For nicely folding multiple tags. + int linerow = 0; // Current row of TEXT to print + int selection_row = 0; // Current index of selection vector + + + + + for (const bodypart_id& part : this->get_all_body_parts(get_body_part_flags::sorted)) { + + std::string header; // Bodypart Title + std::string hp_str; // Bodypart HP + std::string detail; + std::string description; + + + // Colorized strings for each status + std::vector color_strings; + widget_id wid("bodypart_status_indicator_template"); + for (const auto& sc : display::bodypart_status_colors(you, part, "bodypart_status_indicator_template")) { + std::string txt = io::enum_to_string(sc.first); + if (wid.is_valid()) { + translation t = widget_phrase::get_text_for_id(txt, wid); + txt = to_upper_case(t.empty() ? txt : t.translated()); + } + color_strings.emplace_back(string_format("[ %s ]", colorize(txt, sc.second))); + } + detail += join(color_strings, " "); + + bool no_feeling = this->is_avatar() && this->has_trait(trait_NOPAIN); + const int maximal_hp = this->get_part_hp_max(part); + const int current_hp = this->get_part_hp_cur(part); + const bool limb_is_broken = this->is_limb_broken(part); + const bool limb_is_mending = this->worn_with_flag(flag_SPLINT, part); + + if (limb_is_mending) { + detail += colorize(_("Splinted"), + c_blue); + if (no_feeling) { + hp_str = colorize("==%==", c_blue); + } + else { + const auto& eff = this->get_effect(effect_mending, part); + const int mend_perc = eff.is_null() ? 0.0 : 100 * eff.get_duration() / eff.get_max_duration(); + + const int num = mend_perc / 20; + hp_str = colorize(std::string(num, '#') + std::string(5 - num, '='), c_blue); + } + } + else if (limb_is_broken) { + detail += colorize(_("Broken"), c_red); + hp_str = "==%=="; + } + else if (no_feeling) { + if (current_hp < maximal_hp * 0.25) { + hp_str = colorize(_("Very Bad"), c_red); + } + else if (current_hp < maximal_hp * 0.5) { + hp_str = colorize(_("Bad"), c_light_red); + } + else if (current_hp < maximal_hp * 0.75) { + hp_str = colorize(_("Okay"), c_light_green); + } + else { + hp_str = colorize(_("Good"), c_green); + } + } + else { + std::pair h_bar = get_hp_bar(current_hp, maximal_hp, false); + hp_str = colorize(h_bar.first, h_bar.second) + + colorize(std::string(5 - utf8_width(h_bar.first), '.'), c_white); + } + header += colorize(uppercase_first_letter(body_part_name(part, 1)), display::limb_color(*this, part, true, true, true)) + " " + hp_str; + + /* Descriptions */ + + bool bleeding = this->has_effect(effect_bleed, part.id()); + bool bitten = this->has_effect(effect_bite, part.id()); + bool infected = this->has_effect(effect_infected, part.id()); + bool bandaged = this->has_effect(effect_bandaged, part.id()); + + // BLEEDING block + if (bleeding) { + const int bleed_intensity = this->get_effect_int(effect_bleed, part); + const effect bleed_effect = this->get_effect(effect_bleed, part); + description += string_format("[ %s ] - %s\n", + colorize(bleed_effect.get_speed_name(), colorize_bleeding_intensity(bleed_intensity)), + bleed_effect.disp_short_desc()); + } + + // BITTEN block + if (bitten) { + const effect bite_effect = this->get_effect(effect_bite, part); + description += string_format("[ %s ] - %s\n", + colorize(bite_effect.get_speed_name(), c_red), + bite_effect.disp_short_desc()); + } + + // INFECTED block + if (infected) { + const effect infected_effect = this->get_effect(effect_infected, part); + description += string_format("[ %s ] - %s\n", + colorize(infected_effect.get_speed_name(), c_light_green), + infected_effect.disp_short_desc()); + } + + selection_line line; + + if (detail.length() > 0) { + const std::string header_str = string_format("[%s] - %s", header, detail); + std::vector textformatted = foldstring(header_str, SUMMARY_WIDTH - 2, (char)93); + const int lineCount = textformatted.size(); + if (lineCount <= 1) { + line = selection_line(header_str, description); + } + else { + //If there are too many tags, display them neatly on a new line. + std::string print_line = string_format("%s\n", textformatted[0]); + for (int i = 1; i < lineCount; i++) { + if (i != lineCount) { + print_line += string_format("->%s\n", textformatted[i]); + } + else { + print_line += string_format("->%s", textformatted[i]); + } + } + line = selection_line(print_line, description); + } + } + else { + line = selection_line(string_format("[%s]", header), description); + } + + SummaryLines.emplace_back(line); + } + + + + /* Print Lines */ + + + for (selection_line& line : SummaryLines) { + const bool is_highlighted = cursor == point(0, selection_row); + if (is_highlighted) { highlightline = line; } + linerow += line.print(wMedical, point(left_padding, TEXT_START_Y + 2 + linerow), SUMMARY_WIDTH, c_light_gray, is_highlighted); + ++selection_row; + } + + /* SECOND COLUMN - EFFECTS */ + mvwprintz(wMedical, point(second_column_x, TEXT_START_Y), c_light_blue, _("EFFECTS")); + + EffectLines.clear(); + + for (auto& elem : *effects) { + for (auto& _effect_it : elem.second) { + const std::string name = _effect_it.second.disp_name(); + if (name.empty()) { + continue; + } + EffectLines.emplace_back(selection_line(name, _effect_it.second.disp_desc())); + } + } + + const float bmi = get_bmi(); + + if (bmi < character_weight_category::underweight) { + std::string starvation_name; + std::string starvation_text; + + if (bmi < character_weight_category::emaciated) { + starvation_name = _("Severely Malnourished"); + starvation_text = + _("Your body is severely weakened by starvation. You might die if you don't start eating regular meals!\n\n"); + } + else { + starvation_name = _("Malnourished"); + starvation_text = + _("Your body is weakened by starvation. Only time and regular meals will help you recover.\n\n"); + } + + if (bmi < character_weight_category::underweight) { + const float str_penalty = 1.0f - ((bmi - 13.0f) / 3.0f); + starvation_text += std::string(_("Strength")) + " -" + string_format("%2.0f%%\n", + str_penalty * 100.0f); + starvation_text += std::string(_("Dexterity")) + " -" + string_format("%2.0f%%\n", + str_penalty * 50.0f); + starvation_text += std::string(_("Intelligence")) + " -" + string_format("%2.0f%%", + str_penalty * 50.0f); + } + + EffectLines.emplace_back(selection_line(starvation_name, starvation_text)); + } + + if (has_trait(trait_TROGLO) && g->is_in_sunlight(pos()) && + get_weather().weather_id->sun_intensity >= sun_intensity_type::high) { + EffectLines.emplace_back(selection_line("In Sunlight", "The sunlight irritates you.\n")); + } + else if (has_trait(trait_TROGLO2) && g->is_in_sunlight(pos())) { + EffectLines.emplace_back(selection_line("In Sunlight", "The sunlight irritates you badly.\n")); + } + else if (has_trait(trait_TROGLO3) && g->is_in_sunlight(pos())) { + EffectLines.emplace_back(selection_line("In Sunlight", "The sunlight irritates you terribly.\n")); + } + + for (auto& elem : addictions) { + if (elem.sated < 0_turns && elem.intensity >= MIN_ADDICTION_LEVEL) { + EffectLines.emplace_back(selection_line(addiction_name(elem), addiction_text(elem))); + } + } + + /* Print Effect Lines */ + + linerow = 0; + selection_row = 0; + if (EffectLines.size() == 0) { EffectLines.emplace_back(selection_line(colorize("None", c_dark_gray), "")); } + for (selection_line& line : EffectLines) { + const bool is_highlighted = cursor == point(1, selection_row); + if (is_highlighted) { highlightline = line; } + linerow += line.print(wMedical, point(second_column_x, TEXT_START_Y + 2 + linerow), third_column_x - second_column_x, c_light_gray, is_highlighted); + ++selection_row; + } + + /* THIRD COLUMN - Stats */ + StatLines.clear(); + + mvwprintz(wMedical, point(third_column_x, TEXT_START_Y), c_light_blue, _("STATS")); + + std::string strength_str = coloured_stat_display(this->get_str(), this->get_str_base()); + StatLines.emplace_back( + selection_line(string_format("Strength: %s", strength_str), + "Strength affects your melee damage, the amount of weight you can carry, your total HP, " + "your resistance to many diseases, and the effectiveness of actions which require brute force.")); + + std::string dexterity_str = coloured_stat_display(this->get_dex(), this->get_dex_base()); + StatLines.emplace_back( + selection_line(string_format("Dexterity: %s", dexterity_str), + "Dexterity affects your chance to hit in melee combat, helps you steady your " + "gun for ranged combat, and enhances many actions that require finesse.")); + + std::string intelligence_str = coloured_stat_display(this->get_int(), this->get_int_base()); + StatLines.emplace_back( + selection_line(string_format("Intelligence: %s", intelligence_str), + "Intelligence is less important in most situations, but it is vital for more complex tasks like " + "electronics crafting. It also affects how much skill you can pick up from reading a book.")); + + std::string perception_str = coloured_stat_display(this->get_per(), this->get_per_base()); + StatLines.emplace_back( + selection_line(string_format("Perception: %s", perception_str), + "Perception is the most important stat for ranged combat. It's also used for " + "detecting traps and other things of interest.")); + + /* Print Stat Lines */ + + linerow = 0; + selection_row = 0; + for (selection_line& line : StatLines) { + const bool is_highlighted = cursor == point(2, selection_row); + if (is_highlighted) { highlightline = line; } + linerow += line.print(wMedical, point(third_column_x, TEXT_START_Y + 2 + linerow), WIDTH - third_column_x - right_padding, c_light_gray, is_highlighted); + ++selection_row; + } + + /* Update Cursor Boundary */ + + switch (cursor.x) { + case 0: + selected_column_rows = SummaryLines.size(); + break; + case 1: + selected_column_rows = EffectLines.size(); + break; + case 2: + selected_column_rows = StatLines.size(); + break; + } + + /* DESCRIPTION BAR */ + + werase(w_description); + // Number of display rows required by highlightline.description() + const std::string desc_str = highlightline.description(); + std::vector textformatted = foldstring(desc_str, WIDTH - 2, (char)32); + // Beginning row of description text [1-3] (w_description) + const int DESCRIPTION_TEXT_Y = DESC_W_HEIGHT - std::min(DESC_W_HEIGHT, static_cast(textformatted.size())); + // Actual position of the description bar (wMedical) + int DESCRIPTION_WIN_OFFSET = DESC_W_BEGIN + DESCRIPTION_TEXT_Y; + if (desc_str.size() > 0) + { + mvwputch(wMedical, point(0, DESCRIPTION_WIN_OFFSET - 1), BORDER_COLOR, LINE_XXXO); + mvwhline(wMedical, point(1, DESCRIPTION_WIN_OFFSET - 1), LINE_OXOX, getmaxx(wMedical) - 2); + mvwputch(wMedical, point(getmaxx(wMedical) - 1, DESCRIPTION_WIN_OFFSET - 1), BORDER_COLOR, LINE_XOXX); + fold_and_print(w_description, point(1, DESCRIPTION_TEXT_Y), WIDTH - 2, c_light_gray, highlightline.description()); + } + else { + DESCRIPTION_WIN_OFFSET = getmaxy(wMedical); + } + + wnoutrefresh(w_description); + + /* COLUMN BORDERS - Dependent on DESCRIPTION_WIN_OFFSET */ + //Second Column + mvwvline(wMedical, point(second_column_x - 2, TEXT_START_Y), LINE_XOXO, DESCRIPTION_WIN_OFFSET - 4); // | + mvwputch(wMedical, point(second_column_x - 2, DESCRIPTION_WIN_OFFSET - 1), BORDER_COLOR, LINE_XXOX); // _|_ + //Third Column + mvwvline(wMedical, point(third_column_x - 2, TEXT_START_Y), LINE_XOXO, DESCRIPTION_WIN_OFFSET - 4); // | + mvwputch(wMedical, point(third_column_x - 2, DESCRIPTION_WIN_OFFSET - 1), BORDER_COLOR, LINE_XXOX); // _|_ + + wnoutrefresh(wMedical); + }); + + input_context ctxt("MEDICAL"); + ctxt.register_action("UP"); + ctxt.register_action("DOWN"); + ctxt.register_action("LEFT"); + ctxt.register_action("RIGHT"); + ctxt.register_action("APPLY"); + ctxt.register_action("HELP_KEYBINDINGS"); + ctxt.register_action("QUIT"); + + std::string action; + for (;; ) { + ui_manager::redraw(); + const std::string action = ctxt.handle_input(); + const int ch = ctxt.get_raw_input().get_first_input(); + + if (action == "DOWN" || action == "UP") { + const int step = cursor.y + (action == "DOWN" ? 1 : -1); + if (step == -1) cursor.y = selected_column_rows - 1; + else if (step > selected_column_rows - 1) cursor.y = 0; + else { cursor.y = step; } + } + else if (action == "RIGHT" || action == "LEFT") { + const int step = cursor.x + (action == "RIGHT" ? 1 : -1); + if (step == -1) cursor.x = 2; + else if (step == 3) cursor.x = 0; + else { cursor.x = step; } + + switch (cursor.x) { + case 0: + selected_column_rows = SummaryLines.size(); + break; + case 1: + selected_column_rows = EffectLines.size(); + break; + case 2: + selected_column_rows = StatLines.size(); + break; + } + + if (cursor.y > selected_column_rows - 1) { + cursor.y = selected_column_rows - 1; + } + } + else if (action == "APPLY") { + avatar_action::use_item(*this); + } + else if (action == "QUIT") { + break; + } + } +} From 7ff75ee064c4b3662411a1b1cb4f2cba3d0a674c Mon Sep 17 00:00:00 2001 From: redrosedialtone <64289850+redrosedialtone@users.noreply.github.com> Date: Tue, 25 Jan 2022 18:25:58 +1100 Subject: [PATCH 02/10] [Medical Menu] Basic Build and Test (GCC 9, Curses, LTO) Styling Removal of a couple variables left behind from previous implementation, correct character casting. --- src/medical_ui.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/medical_ui.cpp b/src/medical_ui.cpp index 725e602bb7d9a..50670d31c9d5a 100644 --- a/src/medical_ui.cpp +++ b/src/medical_ui.cpp @@ -179,7 +179,6 @@ static void draw_medical_titlebar(const catacurses::window& window, avatar* play void avatar::disp_medical() { - medical_tab_mode tab_mode = medical_tab_mode::TAB_SUMMARY; const Character& you = *this->as_character(); /* Title Bar - Tabs, Pain Indicator & Blood Indicator */ @@ -363,7 +362,6 @@ void avatar::disp_medical() bool bleeding = this->has_effect(effect_bleed, part.id()); bool bitten = this->has_effect(effect_bite, part.id()); bool infected = this->has_effect(effect_infected, part.id()); - bool bandaged = this->has_effect(effect_bandaged, part.id()); // BLEEDING block if (bleeding) { @@ -394,7 +392,7 @@ void avatar::disp_medical() if (detail.length() > 0) { const std::string header_str = string_format("[%s] - %s", header, detail); - std::vector textformatted = foldstring(header_str, SUMMARY_WIDTH - 2, (char)93); + std::vector textformatted = foldstring(header_str, SUMMARY_WIDTH - 2, static_cast (93)); const int lineCount = textformatted.size(); if (lineCount <= 1) { line = selection_line(header_str, description); @@ -565,7 +563,7 @@ void avatar::disp_medical() werase(w_description); // Number of display rows required by highlightline.description() const std::string desc_str = highlightline.description(); - std::vector textformatted = foldstring(desc_str, WIDTH - 2, (char)32); + std::vector textformatted = foldstring(desc_str, WIDTH - 2, static_cast (32)); // Beginning row of description text [1-3] (w_description) const int DESCRIPTION_TEXT_Y = DESC_W_HEIGHT - std::min(DESC_W_HEIGHT, static_cast(textformatted.size())); // Actual position of the description bar (wMedical) @@ -603,11 +601,9 @@ void avatar::disp_medical() ctxt.register_action("HELP_KEYBINDINGS"); ctxt.register_action("QUIT"); - std::string action; for (;; ) { ui_manager::redraw(); const std::string action = ctxt.handle_input(); - const int ch = ctxt.get_raw_input().get_first_input(); if (action == "DOWN" || action == "UP") { const int step = cursor.y + (action == "DOWN" ? 1 : -1); From 0b76a434101ce6836a92569d31ee0bc871ebd128 Mon Sep 17 00:00:00 2001 From: redrosedialtone <64289850+redrosedialtone@users.noreply.github.com> Date: Thu, 3 Feb 2022 14:01:05 +1100 Subject: [PATCH 03/10] [Medical Menu] Extended Implementation -Access from Player Info ('@') Menu -Info Menu -Scrolling Text -Formatting -Astyle & Clang Tidy --- data/raw/keybindings.json | 6 + src/medical_ui.cpp | 1322 ++++++++++++++++++++++++------------- src/player_display.cpp | 4 + 3 files changed, 856 insertions(+), 476 deletions(-) diff --git a/data/raw/keybindings.json b/data/raw/keybindings.json index 72cef1d060636..fd9775c3e2132 100644 --- a/data/raw/keybindings.json +++ b/data/raw/keybindings.json @@ -1275,6 +1275,12 @@ "name": "Customize character", "bindings": [ { "input_method": "keyboard_any", "key": "y" } ] }, + { + "type": "keybinding", + "id": "MEDICAL_MENU", + "name": "Open Medical Menu", + "bindings": [ { "input_method": "keyboard_char", "key": "@" }, { "input_method": "keyboard_code", "key": "2", "mod": [ "shift" ] } ] + }, { "type": "keybinding", "id": "SAVE_TEMPLATE", diff --git a/src/medical_ui.cpp b/src/medical_ui.cpp index 50670d31c9d5a..715ee6b698576 100644 --- a/src/medical_ui.cpp +++ b/src/medical_ui.cpp @@ -9,7 +9,9 @@ #include "action.h" #include "addiction.h" #include "avatar_action.h" +#include "creature.h" #include "character.h" +#include "character_modifier.h" #include "display.h" #include "effect.h" #include "flag.h" @@ -19,624 +21,992 @@ #include "vitamin.h" #include "weather.h" -static const efftype_id effect_bandaged("bandaged"); -static const efftype_id effect_bite("bite"); -static const efftype_id effect_bleed("bleed"); -static const efftype_id effect_infected("infected"); -static const efftype_id effect_disinfected("disinfected"); -static const efftype_id effect_mending("mending"); -static const efftype_id effect_hypovolemia("hypovolemia"); +static const efftype_id effect_bite( "bite" ); +static const efftype_id effect_bleed( "bleed" ); +static const efftype_id effect_infected( "infected" ); -static const trait_id trait_NOPAIN("NOPAIN"); -static const trait_id trait_TROGLO("TROGLO"); -static const trait_id trait_TROGLO2("TROGLO2"); -static const trait_id trait_TROGLO3("TROGLO3"); +static const trait_id trait_COLDBLOOD4( "COLDBLOOD4" ); +static const trait_id trait_NOPAIN( "NOPAIN" ); +static const trait_id trait_SUNLIGHT_DEPENDENT( "SUNLIGHT_DEPENDENT" ); +static const trait_id trait_TROGLO( "TROGLO" ); +static const trait_id trait_TROGLO2( "TROGLO2" ); +static const trait_id trait_TROGLO3( "TROGLO3" ); -static const vitamin_id vitamin_blood("blood"); +static const flag_id json_flag_SPLINT( "SPLINT" ); enum class medical_tab_mode { TAB_SUMMARY }; -static std::string coloured_stat_display(int statCur, int statMax) { +class selection_line +{ + public: + selection_line() = default; + selection_line( const std::string text, const std::string &desc_str, const int max_width ) + : desc_str( desc_str ) { + std::vector textformatted = foldstring( text, max_width, + static_cast( 93 ) ); + row_count = textformatted.size(); + if( row_count > 1 ) { + //If there are too many tags, display them neatly on a new line. + std::string print_line = string_format( "%s\n", textformatted[0] ); + for( int i = 1; i < row_count; i++ ) { + if( i != row_count ) { + print_line += string_format( "->%s\n", textformatted[i] ); + } else { + print_line += string_format( "->%s", textformatted[i] ); + } + } + header_str = print_line; + } else { + header_str = text; + } + } + + std::string print() { + if( highlight_line ) { + header_str = colorize( ">", h_white ) + header_str; + } + return header_str; + } + + std::string description() { + return desc_str; + } + + void set_detail( const std::string &header, const std::string &detail ) { + detail_str = std::pair( header, detail ); + } + + std::pair get_detail() { + return detail_str; + } + + int get_row_count() { + return row_count; + } + + void set_highlight() { + highlight_line = true; + } + + bool highlighted() { + return highlight_line; + } + + private: + std::string header_str; + std::string desc_str; + std::pair detail_str; + int row_count; + bool highlight_line = false; +}; + +class medical_column +{ + public: + medical_column() = default; + medical_column( const int column_id, const point COLUMN_START, + const std::pair COLUMN_BOUNDS ) + : column_id( column_id ), COLUMN_BOUNDS( COLUMN_BOUNDS ), COLUMN_START( COLUMN_START ) {} + + void draw_column( const catacurses::window &window, const int BORDER_START, + const int BORDER_END ) { + mvwvline( window, point( COLUMN_START.x, BORDER_START ), LINE_XOXO, + BORDER_END - 4 ); // | + mvwputch( window, point( COLUMN_START.x, BORDER_END - 1 ), BORDER_COLOR, + LINE_XXOX ); // _|_ + } + + void print_column( const catacurses::window &window, const int LINE_START, const int MAX_HEIGHT ) { + int linerow = 0; + int selectionrow = 0; + + for( selection_line &line : column_lines ) { + const int row_start_x = line.highlighted() ? + COLUMN_START.x + left_padding - 1 : + COLUMN_START.x + left_padding; + const int row_start_y = COLUMN_START.y + linerow; + + if( row_start_y - LINE_START >= MAX_HEIGHT ) { + break; + } + + if( linerow >= LINE_START ) { + fold_and_print( window, point( row_start_x, row_start_y - LINE_START ), max_width(), + c_light_gray, line.print() ); + linerow += line.get_row_count(); + } else { + linerow++; + } + ++selectionrow; + } + COLUMN_ROWS = { selectionrow, linerow }; + } + + void add_column_line( selection_line line ) { + column_lines.emplace_back( line ); + COLUMN_ROWS.second += line.get_row_count(); + } + + selection_line set_highlight( int y ) { + int offset = y % column_lines.size(); + column_lines[offset].set_highlight(); + return column_lines[offset]; + } + + int selection_count() { + return COLUMN_ROWS.first; + } + + int row_count() { + return COLUMN_ROWS.second; + } + + int max_width() { + return COLUMN_BOUNDS.first - COLUMN_START.x - left_padding; + } + + bool empty() { + return column_lines.empty(); + } + + bool current_column( const int selected_id ) { + return column_id == selected_id; + } + + std::pair detail_str( int y ) { + std::pair ret; + if( y < column_lines.size() ) { + int offset = y % column_lines.size(); + ret = column_lines[offset].get_detail(); + } + return ret; + } + + private: + int column_id; + const int left_padding = 2; + std::pair COLUMN_ROWS = { 0, 0 }; // Selection Lines - Print Lines + std::pair COLUMN_BOUNDS; // Left Bound - Right Bound + point COLUMN_START; + std::vectorcolumn_lines; + std::string column_title; +}; + +static std::string coloured_stat_display( int statCur, int statMax ) +{ nc_color cstatus; - if (statCur <= 0) { + if( statCur <= 0 ) { cstatus = c_dark_gray; - } - else if (statCur < statMax / 2) { + } else if( statCur < statMax / 2 ) { cstatus = c_red; - } - else if (statCur < statMax) { + } else if( statCur < statMax ) { cstatus = c_light_red; - } - else if (statCur == statMax) { + } else if( statCur == statMax ) { cstatus = c_white; - } - else if (statCur < statMax * 1.5) { + } else if( statCur < statMax * 1.5 ) { cstatus = c_light_green; - } - else { + } else { cstatus = c_green; } - std::string cur = colorize(string_format(_("%2d"), statCur), cstatus); - return string_format(_("%s (%s)"), cur, statMax); + std::string cur = colorize( string_format( _( "%2d" ), statCur ), cstatus ); + return string_format( _( "%s (%s)" ), cur, statMax ); } -static void draw_medical_titlebar(const catacurses::window& window, avatar* player, - const int WIDTH) +static void draw_medical_titlebar( const catacurses::window &window, avatar *player, + const int WIDTH ) { - input_context ctxt("MEDICAL", keyboard_mode::keychar); - const Character& you = *player->as_character(); + input_context ctxt( "MEDICAL", keyboard_mode::keychar ); + const Character &you = *player->as_character(); - werase(window); - draw_border(window, BORDER_COLOR, _(" MEDICAL ")); + werase( window ); + draw_border( window, BORDER_COLOR, _( " MEDICAL " ) ); - /* Window Title */ - center_print(window, 0, c_blue, _(" MEDICAL ")); + // Window Title + center_print( window, 0, c_blue, _( " MEDICAL " ) ); - /* Tabs */ + // Tabs const std::vector> tabs = { - { medical_tab_mode::TAB_SUMMARY, string_format(_("SUMMARY")) } + { medical_tab_mode::TAB_SUMMARY, string_format( _( "SUMMARY" ) ) } }; - draw_tabs(window, tabs, medical_tab_mode::TAB_SUMMARY); + draw_tabs( window, tabs, medical_tab_mode::TAB_SUMMARY ); const int TAB_WIDTH = 12; // Draw symbols to connect additional lines to border - int width = getmaxx(window); - int height = getmaxy(window); - for (int i = 1; i < height - 1; ++i) { + int width = getmaxx( window ); + int height = getmaxy( window ); + for( int i = 1; i < height - 1; ++i ) { // | - mvwputch(window, point(0, i), BORDER_COLOR, LINE_XOXO); + mvwputch( window, point( 0, i ), BORDER_COLOR, LINE_XOXO ); // | - mvwputch(window, point(width - 1, i), BORDER_COLOR, LINE_XOXO); + mvwputch( window, point( width - 1, i ), BORDER_COLOR, LINE_XOXO ); } // |- - mvwputch(window, point(0, height - 1), BORDER_COLOR, LINE_XXXO); + mvwputch( window, point( 0, height - 1 ), BORDER_COLOR, LINE_XXXO ); // -| - mvwputch(window, point(width - 1, height - 1), BORDER_COLOR, LINE_XOXX); + mvwputch( window, point( width - 1, height - 1 ), BORDER_COLOR, LINE_XOXX ); int right_indent = 2; + int cur_str_pos = 0; - /* Pain Indicator */ - auto pain_descriptor = display::pain_text_color(*player); - if (pain_descriptor.first.size() > 0) { - const std::string pain_str = "In " + pain_descriptor.first; + // Pain Indicator + auto pain_descriptor = display::pain_text_color( *player ); + if( !pain_descriptor.first.empty() ) { + const std::string pain_str = string_format( "In %s", pain_descriptor.first ); - const int pain_str_pos = right_print(window, 1, right_indent, pain_descriptor.second, pain_str); + cur_str_pos = right_print( window, 1, right_indent, pain_descriptor.second, pain_str ); - /* ~~Borders */ - for (int i = 1; i < getmaxy(window) - 1; i++) { - mvwputch(window, point(pain_str_pos - 2, i), BORDER_COLOR, LINE_XOXO); // | + // Borders + for( int i = 1; i < getmaxy( window ) - 1; i++ ) { + mvwputch( window, point( cur_str_pos - 2, i ), BORDER_COLOR, LINE_XOXO ); // | } - mvwputch(window, point(pain_str_pos - 2, 0), BORDER_COLOR, LINE_OXXX); // ^|^ - mvwputch(window, point(pain_str_pos - 2, 2), BORDER_COLOR, LINE_XXOX); // _|_ - right_indent += utf8_width(remove_color_tags(pain_str)) + 3; + mvwputch( window, point( cur_str_pos - 2, 0 ), BORDER_COLOR, LINE_OXXX ); // ^|^ + mvwputch( window, point( cur_str_pos - 2, 2 ), BORDER_COLOR, LINE_XXOX ); // _|_ + right_indent += utf8_width( remove_color_tags( pain_str ) ) + 3; } - /* Hunger, Thirst & Fatigue Indicator */ - - const std::pair hunger_pair = display::hunger_text_color(you); - const std::pair thirst_pair = display::thirst_text_color(you); - const std::pair fatigue_pair = display::fatigue_text_color(you); - int cur_str_pos = 0; + const std::pair hunger_pair = display::hunger_text_color( you ); + const std::pair thirst_pair = display::thirst_text_color( you ); + const std::pair fatigue_pair = display::fatigue_text_color( you ); // Hunger - if (hunger_pair.first.size() > 0) { - cur_str_pos = right_print(window, 1, right_indent, hunger_pair.second, hunger_pair.first); + if( !hunger_pair.first.empty() ) { + cur_str_pos = right_print( window, 1, right_indent, hunger_pair.second, hunger_pair.first ); - /* ~~Borders */ - for (int i = 1; i < getmaxy(window) - 1; i++) { - mvwputch(window, point(cur_str_pos - 2, i), BORDER_COLOR, LINE_XOXO); // | + // Borders + for( int i = 1; i < getmaxy( window ) - 1; i++ ) { + mvwputch( window, point( cur_str_pos - 2, i ), BORDER_COLOR, LINE_XOXO ); // | } - mvwputch(window, point(cur_str_pos - 2, 0), BORDER_COLOR, LINE_OXXX); // ^|^ - mvwputch(window, point(cur_str_pos - 2, 2), BORDER_COLOR, LINE_XXOX); // _|_ + mvwputch( window, point( cur_str_pos - 2, 0 ), BORDER_COLOR, LINE_OXXX ); // ^|^ + mvwputch( window, point( cur_str_pos - 2, 2 ), BORDER_COLOR, LINE_XXOX ); // _|_ - right_indent += utf8_width(hunger_pair.first) + 3; + right_indent += utf8_width( hunger_pair.first ) + 3; } // Thirst - if (thirst_pair.first.size() > 0) { - cur_str_pos = right_print(window, 1, right_indent, thirst_pair.second, thirst_pair.first); + if( !thirst_pair.first.empty() ) { + cur_str_pos = right_print( window, 1, right_indent, thirst_pair.second, thirst_pair.first ); - /* ~~Borders */ - for (int i = 1; i < getmaxy(window) - 1; i++) { - mvwputch(window, point(cur_str_pos - 2, i), BORDER_COLOR, LINE_XOXO); // | + // Borders + for( int i = 1; i < getmaxy( window ) - 1; i++ ) { + mvwputch( window, point( cur_str_pos - 2, i ), BORDER_COLOR, LINE_XOXO ); // | } - mvwputch(window, point(cur_str_pos - 2, 0), BORDER_COLOR, LINE_OXXX); // ^|^ - mvwputch(window, point(cur_str_pos - 2, 2), BORDER_COLOR, LINE_XXOX); // _|_ + mvwputch( window, point( cur_str_pos - 2, 0 ), BORDER_COLOR, LINE_OXXX ); // ^|^ + mvwputch( window, point( cur_str_pos - 2, 2 ), BORDER_COLOR, LINE_XXOX ); // _|_ - right_indent += utf8_width(thirst_pair.first) + 3; + right_indent += utf8_width( thirst_pair.first ) + 3; } - //Fatigue - if (fatigue_pair.first.size() > 0) { - cur_str_pos = right_print(window, 1, right_indent, fatigue_pair.second, fatigue_pair.first); + // Fatigue + if( !fatigue_pair.first.empty() ) { + cur_str_pos = right_print( window, 1, right_indent, fatigue_pair.second, fatigue_pair.first ); - /* ~~Borders */ - for (int i = 1; i < getmaxy(window) - 1; i++) { - mvwputch(window, point(cur_str_pos - 2, i), BORDER_COLOR, LINE_XOXO); // | + // Borders + for( int i = 1; i < getmaxy( window ) - 1; i++ ) { + mvwputch( window, point( cur_str_pos - 2, i ), BORDER_COLOR, LINE_XOXO ); // | } - mvwputch(window, point(cur_str_pos - 2, 0), BORDER_COLOR, LINE_OXXX); // ^|^ - mvwputch(window, point(cur_str_pos - 2, 2), BORDER_COLOR, LINE_XXOX); // _|_ + mvwputch( window, point( cur_str_pos - 2, 0 ), BORDER_COLOR, LINE_OXXX ); // ^|^ + mvwputch( window, point( cur_str_pos - 2, 2 ), BORDER_COLOR, LINE_XXOX ); // _|_ - right_indent += utf8_width(fatigue_pair.first) + 3; + right_indent += utf8_width( fatigue_pair.first ) + 3; } - /* Hotkey Helper */ + // Hotkey Helper std::string desc; - desc = string_format(_( - "[%s]Apply Item [%s]Keybindings"), - ctxt.get_desc("APPLY"), ctxt.get_desc("HELP_KEYBINDINGS")); + desc = string_format( _( + "[%s/%s]Scroll Info [%s]Apply Item [%s]Keybindings" ), + ctxt.get_desc( "SCROLL_INFOBOX_UP" ), ctxt.get_desc( "SCROLL_INFOBOX_DOWN" ), + ctxt.get_desc( "APPLY" ), ctxt.get_desc( "HELP_KEYBINDINGS" ) ); const int max_width = right_indent + TAB_WIDTH; - if (WIDTH - max_width > 3) // If the window runs out of room, we won't print keybindings. - { - right_print(window, 1, right_indent, c_white, desc); + if( WIDTH - max_width > utf8_width( remove_color_tags( desc ) ) + 3 ) { + // If the window runs out of room, we won't print keybindings. + right_print( window, 1, right_indent, c_white, desc ); } } -void avatar::disp_medical() +// Displays a summary of each bodypart's health, including a display for a few 'statuses' +static medical_column draw_health_summary( const int column_count, avatar *player, + const point COLUMN_START, + const std::pair COLUMN_BOUNDS ) { - const Character& you = *this->as_character(); - - /* Title Bar - Tabs, Pain Indicator & Blood Indicator */ - catacurses::window w_title; - /* Primary Window */ - catacurses::window wMedical; - /* Bottom detail bar, for printing selection_line.description() */ - catacurses::window w_description; - - const int TITLE_W_HEIGHT = 3; - const int DESC_W_HEIGHT = 6; // Consistent with Player Info (@) Menu - const int TEXT_START_Y = TITLE_W_HEIGHT; - int DESC_W_BEGIN = 0; - - int HEIGHT = 0; - int WIDTH = 0; - int second_column_x = 0; - int third_column_x = 0; - - point cursor(0, 0); - int selected_column_rows = 0; - - class selection_line - { - public: - selection_line() = default; - selection_line(const std::string& header_str, const std::string& desc_str) - : header_str(header_str), desc_str(desc_str) {} - - int print(const catacurses::window& win, const point begin, const int& width, const nc_color& base_color, bool selected) - { - if (selected) { - std::string highlight = colorize(">", h_white); - header_str = highlight + header_str; + medical_column health_column = medical_column( column_count, COLUMN_START, COLUMN_BOUNDS ); + const int max_width = health_column.max_width(); + + for( const bodypart_id &part : player->get_all_body_parts( get_body_part_flags::sorted ) ) { + std::string header; // Bodypart Title + std::string hp_str; // Bodypart HP + std::string detail; + std::string description; + + const int bleed_intensity = player->get_effect_int( effect_bleed, part ); + const bool bleeding = bleed_intensity > 0; + const bool bitten = player->has_effect( effect_bite, part.id() ); + const bool infected = player->has_effect( effect_infected, part.id() ); + + // Colorized strings for each status + std::vector color_strings; + widget_id wid( "bodypart_status_indicator_template" ); + for( const auto &sc : display::bodypart_status_colors( *player, part, + "bodypart_status_indicator_template" ) ) { + std::string txt = io::enum_to_string( sc.first ); + if( wid.is_valid() ) { + translation t = widget_phrase::get_text_for_id( txt, wid ); + txt = to_upper_case( t.empty() ? txt : t.translated() ); } - return linecount = fold_and_print(win, begin, width, base_color, header_str); + color_strings.emplace_back( string_format( "[ %s ]", colorize( txt, sc.second ) ) ); } - - std::string description() { - return desc_str; + detail += join( color_strings, " " ); + + bool no_feeling = player->has_trait( trait_NOPAIN ); + const int maximal_hp = player->get_part_hp_max( part ); + const int current_hp = player->get_part_hp_cur( part ); + const bool limb_is_broken = player->is_limb_broken( part ); + const bool limb_is_mending = player->worn_with_flag( flag_SPLINT, part ); + + if( limb_is_mending ) { + detail += colorize( _( "Splinted" ), + c_blue ); + if( no_feeling ) { + hp_str = colorize( "==%==", c_blue ); + } else { + const auto &eff = player->get_effect( efftype_id( "mending" ), part ); + const int mend_perc = eff.is_null() ? 0.0 : 100 * eff.get_duration() / eff.get_max_duration(); + + const int num = mend_perc / 20; + hp_str = colorize( std::string( num, '#' ) + std::string( 5 - num, '=' ), c_blue ); + } + } else if( limb_is_broken ) { + detail += colorize( _( "Broken" ), c_red ); + hp_str = "==%=="; + } else if( no_feeling ) { + if( current_hp < maximal_hp * 0.25 ) { + hp_str = colorize( _( "Very Bad" ), c_red ); + } else if( current_hp < maximal_hp * 0.5 ) { + hp_str = colorize( _( "Bad" ), c_light_red ); + } else if( current_hp < maximal_hp * 0.75 ) { + hp_str = colorize( _( "Okay" ), c_light_green ); + } else { + hp_str = colorize( _( "Good" ), c_green ); + } + } else { + std::pair h_bar = get_hp_bar( current_hp, maximal_hp, false ); + hp_str = colorize( h_bar.first, h_bar.second ) + + colorize( std::string( 5 - utf8_width( h_bar.first ), '.' ), c_white ); + } + const std::string bp_name = uppercase_first_letter( body_part_name( part, 1 ) ); + header += colorize( bp_name, + display::limb_color( *player, + part, true, true, true ) ) + " " + hp_str; + + /* Descriptions */ + + // BLEEDING block + if( bleeding ) { + const effect bleed_effect = player->get_effect( effect_bleed, part ); + description += string_format( "[ %s ] - %s\n", + colorize( bleed_effect.get_speed_name(), colorize_bleeding_intensity( bleed_intensity ) ), + bleed_effect.disp_short_desc() ); } - int get_line_count() { - return linecount; + // BITTEN block + if( bitten ) { + const effect bite_effect = player->get_effect( effect_bite, part ); + description += string_format( "[ %s ] - %s\n", + colorize( bite_effect.get_speed_name(), c_yellow ), + bite_effect.disp_short_desc() ); } - private: - std::string header_str; - std::string desc_str; - int linecount; - }; + // INFECTED block + if( infected ) { + const effect infected_effect = player->get_effect( effect_infected, part ); + description += string_format( "[ %s ] - %s\n", + colorize( infected_effect.get_speed_name(), c_pink ), + infected_effect.disp_short_desc() ); + } - ui_adaptor ui; - ui.on_screen_resize([&](ui_adaptor& ui) { - HEIGHT = std::min(TERMY, - std::max(FULL_SCREEN_HEIGHT, - TITLE_W_HEIGHT + DESC_W_HEIGHT + 5)); - WIDTH = FULL_SCREEN_WIDTH + (TERMX - FULL_SCREEN_WIDTH) / 5; + selection_line line; + if( !detail.empty() ) { + line = selection_line( string_format( "[%s] - %s", header, detail ), description, max_width ); + } else { + line = selection_line( string_format( "[%s]", header ), description, max_width ); + } - const point win((TERMX - WIDTH) / 2, (TERMY - HEIGHT) / 2); + const bodypart *bp = player->get_part( part ); + std::string detail_str; + for( const limb_score &sc : limb_score::get_all() ) { + if( !part->has_limb_score( sc.getId() ) ) { + continue; + } - wMedical = catacurses::newwin(HEIGHT, WIDTH, win); + float injury_score = bp->get_limb_score( sc.getId(), 0, 0, 1 ); + float max_score = part->get_limb_score( sc.getId() ); + + if( injury_score < max_score ) { + const float injury_modifier = 100 * ( max_score - injury_score ) / max_score; + std::pair score_c; + if( injury_score < max_score * 0.4f ) { + score_c.first = string_format( _( "Crippled (-%.f%%)" ), injury_modifier ); + score_c.second = c_red; + } else if( injury_score < max_score * 0.6f ) { + score_c.first = string_format( _( "Impaired (-%.f%%)" ), injury_modifier ); + score_c.second = c_light_red; + } else if( injury_score < max_score * 0.75f ) { + score_c.first = string_format( _( "Weakened (-%.f%%)" ), injury_modifier ); + score_c.second = c_yellow; + } else if( injury_score < max_score * 0.9f ) { + score_c.first = string_format( _( "Weakened (-%.f%%)" ), injury_modifier ); + score_c.second = c_dark_gray; + } else { + score_c.first = string_format( _( "OK (-%.f%%)" ), injury_modifier ); + score_c.second = c_dark_gray; + } + detail_str += string_format( _( "%s: %s\n" ), sc.name().translated(), colorize( score_c.first, + score_c.second ) ); + } else { + detail_str += string_format( _( "%s: %s\n" ), sc.name().translated(), colorize( "OK", c_green ) ); + } + } - w_title = catacurses::newwin(TITLE_W_HEIGHT, WIDTH, win); + for( const character_modifier &mod : character_modifier::get_all() ) { + const limb_score_id &sc = mod.use_limb_score(); + if( sc.is_null() || !part->has_limb_score( sc ) ) { + continue; + } + std::string desc = mod.description().translated(); + float injury_score = bp->get_limb_score( sc, 0, 0, 1 ); + float max_score = part->get_limb_score( sc ); + nc_color score_c; + + if( injury_score < max_score * 0.4f ) { + score_c = c_red; + } else if( injury_score < max_score * 0.6f ) { + score_c = c_light_red; + } else if( injury_score < max_score * 0.75f ) { + score_c = c_yellow; + } else { + score_c = c_white; + } - DESC_W_BEGIN = HEIGHT - DESC_W_HEIGHT - 1; - w_description = catacurses::newwin(DESC_W_HEIGHT, WIDTH - 2, - point(win.x + 1, DESC_W_BEGIN + win.y)); + std::string valstr = colorize( string_format( "%.2f", mod.modifier( *player->as_character() ) ), + score_c ); + detail_str += string_format( "%s: %s%s\n", desc, mod.mod_type_str(), valstr ); + } - second_column_x = 20 + (TERMX - FULL_SCREEN_WIDTH) / 6; - third_column_x = second_column_x + 8 + (TERMX - FULL_SCREEN_WIDTH) / 6; + line.set_detail( string_format( _( "%s STATS" ), to_upper_case( bp_name ) ), detail_str ); + health_column.add_column_line( line ); + } + return health_column; +} - ui.position_from_window(wMedical); - }); +// Displays a summary list of all visible effects. +static medical_column draw_effects_summary( const int column_count, avatar *player, + const point COLUMN_START, + const std::pair COLUMN_BOUNDS ) +{ + medical_column effects_column = medical_column( column_count, COLUMN_START, COLUMN_BOUNDS ); + const int max_width = effects_column.max_width(); - ui.mark_resize(); + for( const effect &eff : player->get_effects() ) { + const std::string name = eff.disp_name(); + if( name.empty() ) { + continue; + } + effects_column.add_column_line( selection_line( name, eff.disp_desc(), max_width ) ); + } - std::vector SummaryLines; - std::vector EffectLines; - std::vector StatLines; - selection_line highlightline; + const float bmi = player->get_bmi(); - ui.on_redraw([&](const ui_adaptor&) { - werase(wMedical); + if( bmi < character_weight_category::underweight ) { + std::string starvation_name; + std::string starvation_text; - draw_border(wMedical, BORDER_COLOR, _(" MEDICAL ")); - mvwputch(wMedical, point(getmaxx(wMedical) - 1, 2), BORDER_COLOR, LINE_XOXX); // -| + if( bmi < character_weight_category::emaciated ) { + starvation_name = _( "Severely Malnourished" ); + starvation_text = + _( "Your body is severely weakened by starvation. You might die if you don't start eating regular meals!\n\n" ); + } else { + starvation_name = _( "Malnourished" ); + starvation_text = + _( "Your body is weakened by starvation. Only time and regular meals will help you recover.\n\n" ); + } - wnoutrefresh(wMedical); + if( bmi < character_weight_category::underweight ) { + const float str_penalty = 1.0f - ( ( bmi - 13.0f ) / 3.0f ); + starvation_text += std::string( _( "Strength" ) ) + " -" + string_format( "%2.0f%%\n", + str_penalty * 100.0f ); + starvation_text += std::string( _( "Dexterity" ) ) + " -" + string_format( "%2.0f%%\n", + str_penalty * 50.0f ); + starvation_text += std::string( _( "Intelligence" ) ) + " -" + string_format( "%2.0f%%", + str_penalty * 50.0f ); + } - draw_medical_titlebar(w_title, this, WIDTH); - mvwputch(w_title, point(second_column_x - 2, TEXT_START_Y - 1), BORDER_COLOR, LINE_OXXX); // ^|^ - mvwputch(w_title, point(third_column_x - 2, TEXT_START_Y - 1), BORDER_COLOR, LINE_OXXX); // ^|^ - wnoutrefresh(w_title); + effects_column.add_column_line( selection_line( starvation_name, starvation_text, max_width ) ); + } + if( player->has_trait( trait_TROGLO ) && g->is_in_sunlight( player->pos() ) && + get_weather().weather_id->sun_intensity >= sun_intensity_type::high ) { + effects_column.add_column_line( selection_line( "In Sunlight", "The sunlight irritates you.\n", + max_width ) ); + } else if( player->has_trait( trait_TROGLO2 ) && g->is_in_sunlight( player->pos() ) ) { + effects_column.add_column_line( selection_line( "In Sunlight", + "The sunlight irritates you badly.\n", max_width ) ); + } else if( player->has_trait( trait_TROGLO3 ) && g->is_in_sunlight( player->pos() ) ) { + effects_column.add_column_line( selection_line( "In Sunlight", + "The sunlight irritates you terribly.\n", max_width ) ); + } - /* FIRST COLUUMN - HEALTH */ + for( auto &elem : player->addictions ) { + if( elem.sated < 0_turns && elem.intensity >= MIN_ADDICTION_LEVEL ) { + effects_column.add_column_line( selection_line( addiction_name( elem ), addiction_text( elem ), + max_width ) ); + } + } - SummaryLines.clear(); + if( effects_column.empty() ) { + effects_column.add_column_line( selection_line( colorize( "None", c_dark_gray ), "", max_width ) ); + } - // Header - fold_and_print(wMedical, point(2, TEXT_START_Y), WIDTH - 2, c_light_blue, "HEALTH"); + return effects_column; +} - const int left_padding = 1; // including border - const int right_padding = 1; // including border - const int SUMMARY_WIDTH = second_column_x - left_padding; // For nicely folding multiple tags. - int linerow = 0; // Current row of TEXT to print - int selection_row = 0; // Current index of selection vector +// Displays a summary list of the player's statistics. +static medical_column draw_stats_summary( const int column_count, avatar *player, + const point COLUMN_START, + const std::pair COLUMN_BOUNDS ) +{ + medical_column stats_column = medical_column( column_count, COLUMN_START, COLUMN_BOUNDS ); + const int max_width = stats_column.max_width(); + + std::string speed_detail_str; + int runcost = player->run_cost( 100 ); + int newmoves = player->get_speed(); + + std::string coloured_str = colorize( string_format( _( "%s" ), runcost ), + ( runcost <= 100 ? c_green : c_red ) ); + selection_line runcost_line = selection_line( string_format( _( "Base Move Cost: %s" ), + coloured_str ), + colorize( _( "Base move cost is the final modified movement cost taken to traverse flat ground." ), + c_light_blue ), + max_width ); + + coloured_str = colorize( string_format( _( "%s" ), newmoves ), + ( newmoves >= 100 ? c_green : c_red ) ); + selection_line movecost_line = selection_line( string_format( _( "Current Speed: %s" ), + coloured_str ), + colorize( _( "Speed determines the amount of actions or movement points you can perform in a turn." ), + c_light_blue ), + max_width ); + + const int speed_modifier = player->get_enchantment_speed_bonus(); + + std::string pge_str; + if( speed_modifier != 0 ) { + pge_str = pgettext( "speed bonus", "Bio/Mut/Effects " ); + speed_detail_str += colorize( string_format( _( "%s -%2d%%\n" ), pge_str, speed_modifier ), + c_green ); + } + int pen = 0; + if( player->weight_carried() > player->weight_capacity() ) { + pen = 25 * ( player->weight_carried() - player->weight_capacity() ) / player->weight_capacity(); + pge_str = pgettext( "speed penalty", "Overburdened " ); + speed_detail_str += colorize( string_format( _( "%s -%2d%%\n" ), pge_str, pen ), c_red ); + } + pen = player->get_pain_penalty().speed; + if( pen >= 1 ) { + pge_str = pgettext( "speed penalty", "Pain " ); + speed_detail_str += colorize( string_format( _( "%s -%2d%%\n" ), pge_str, pen ), c_red ); + } + if( player->get_thirst() > 40 ) { + pen = std::abs( Character::thirst_speed_penalty( player->get_thirst() ) ); + pge_str = pgettext( "speed penalty", "Thirst " ); + speed_detail_str += colorize( string_format( _( "%s -%2d%%\n" ), pge_str, pen ), c_red ); + } + if( player->kcal_speed_penalty() < 0 ) { + pen = std::abs( player->kcal_speed_penalty() ); + //~ %s: Starving/Underfed (already left-justified), %2d: speed penalty + pge_str = pgettext( "speed penalty", player->get_bmi() < character_weight_category::underweight ? + _( "Starving" ) : _( "Underfed" ) ); + speed_detail_str += colorize( string_format( _( "%s -%2d%%\n" ), pge_str, pen ), c_red ); + } + if( player->has_trait( trait_SUNLIGHT_DEPENDENT ) && !g->is_in_sunlight( player->pos() ) ) { + pen = ( g->light_level( player->posz() ) >= 12 ? 5 : 10 ); + pge_str = pgettext( "speed penalty", "Out of Sunlight " ); + speed_detail_str += colorize( string_format( _( "%s -%2d%%\n" ), pge_str, pen ), c_red ); + } - for (const bodypart_id& part : this->get_all_body_parts(get_body_part_flags::sorted)) { + const float temperature_speed_modifier = player->mutation_value( "temperature_speed_modifier" ); + if( temperature_speed_modifier != 0 ) { + nc_color pen_color; + std::string pen_sign; + const int player_local_temp = get_weather().get_temperature( player->pos() ); + if( player->has_trait( trait_COLDBLOOD4 ) && player_local_temp > 65 ) { + pen_color = c_green; + pen_sign = "+"; + } else if( player_local_temp < 65 ) { + pen_color = c_red; + pen_sign = "-"; + } + if( !pen_sign.empty() ) { + pen = ( player_local_temp - 65 ) * temperature_speed_modifier; + pge_str = pgettext( "speed modifier", "Cold-Blooded " ); + speed_detail_str += colorize( string_format( _( "%s %s%2d%%\n" ), pge_str, pen_sign, + std::abs( pen ) ), c_red ); + } + } - std::string header; // Bodypart Title - std::string hp_str; // Bodypart HP - std::string detail; - std::string description; + std::map speed_effects; + for( effect &elem : player->get_effects() ) { + bool reduced = player->resists_effect( elem ); + int move_adjust = elem.get_mod( "SPEED", reduced ); + if( move_adjust != 0 ) { + const std::string dis_text = elem.get_speed_name(); + speed_effects[dis_text] += move_adjust; + } + } + for( const std::pair &speed_effect : speed_effects ) { + nc_color col = ( speed_effect.second > 0 ? c_green : c_red ); + speed_detail_str += colorize( string_format( _( "%s %s%d%%\n" ), speed_effect.first, + ( speed_effect.second > 0 ? "+" : "-" ), + std::abs( speed_effect.second ) ), col ); + } - // Colorized strings for each status - std::vector color_strings; - widget_id wid("bodypart_status_indicator_template"); - for (const auto& sc : display::bodypart_status_colors(you, part, "bodypart_status_indicator_template")) { - std::string txt = io::enum_to_string(sc.first); - if (wid.is_valid()) { - translation t = widget_phrase::get_text_for_id(txt, wid); - txt = to_upper_case(t.empty() ? txt : t.translated()); - } - color_strings.emplace_back(string_format("[ %s ]", colorize(txt, sc.second))); - } - detail += join(color_strings, " "); - - bool no_feeling = this->is_avatar() && this->has_trait(trait_NOPAIN); - const int maximal_hp = this->get_part_hp_max(part); - const int current_hp = this->get_part_hp_cur(part); - const bool limb_is_broken = this->is_limb_broken(part); - const bool limb_is_mending = this->worn_with_flag(flag_SPLINT, part); - - if (limb_is_mending) { - detail += colorize(_("Splinted"), - c_blue); - if (no_feeling) { - hp_str = colorize("==%==", c_blue); - } - else { - const auto& eff = this->get_effect(effect_mending, part); - const int mend_perc = eff.is_null() ? 0.0 : 100 * eff.get_duration() / eff.get_max_duration(); + runcost_line.set_detail( _( "SPEED" ), speed_detail_str ); + movecost_line.set_detail( _( "SPEED" ), speed_detail_str ); + + stats_column.add_column_line( runcost_line ); + stats_column.add_column_line( movecost_line ); + + std::string strength_str = coloured_stat_display( player->get_str(), player->get_str_base() ); + stats_column.add_column_line( + selection_line( string_format( _( "Strength: %s" ), strength_str ), + _( "Strength affects your melee damage, the amount of weight you can carry, your total HP, " + "your resistance to many diseases, and the effectiveness of actions which require brute force." ), + max_width ) ); + + std::string dexterity_str = coloured_stat_display( player->get_dex(), player->get_dex_base() ); + stats_column.add_column_line( + selection_line( string_format( _( "Dexterity: %s" ), dexterity_str ), + _( "Dexterity affects your chance to hit in melee combat, helps you steady your " + "gun for ranged combat, and enhances many actions that require finesse." ), + max_width ) ); + + std::string intelligence_str = coloured_stat_display( player->get_int(), player->get_int_base() ); + stats_column.add_column_line( + selection_line( string_format( _( "Intelligence: %s" ), intelligence_str ), + _( "Intelligence is less important in most situations, but it is vital for more complex tasks like " + "electronics crafting. It also affects how much skill you can pick up from reading a book." ), + max_width ) ); + + std::string perception_str = coloured_stat_display( player->get_per(), player->get_per_base() ); + stats_column.add_column_line( + selection_line( string_format( _( "Perception: %s" ), perception_str ), + _( "Perception is the most important stat for ranged combat. It's also used for " + "detecting traps and other things of interest." ), + max_width ) ); + + return stats_column; +} - const int num = mend_perc / 20; - hp_str = colorize(std::string(num, '#') + std::string(5 - num, '='), c_blue); - } - } - else if (limb_is_broken) { - detail += colorize(_("Broken"), c_red); - hp_str = "==%=="; - } - else if (no_feeling) { - if (current_hp < maximal_hp * 0.25) { - hp_str = colorize(_("Very Bad"), c_red); - } - else if (current_hp < maximal_hp * 0.5) { - hp_str = colorize(_("Bad"), c_light_red); - } - else if (current_hp < maximal_hp * 0.75) { - hp_str = colorize(_("Okay"), c_light_green); - } - else { - hp_str = colorize(_("Good"), c_green); - } - } - else { - std::pair h_bar = get_hp_bar(current_hp, maximal_hp, false); - hp_str = colorize(h_bar.first, h_bar.second) + - colorize(std::string(5 - utf8_width(h_bar.first), '.'), c_white); - } - header += colorize(uppercase_first_letter(body_part_name(part, 1)), display::limb_color(*this, part, true, true, true)) + " " + hp_str; +void avatar::disp_medical() +{ + // Windows + catacurses::window w_title; // Title Bar - Tabs, Pain Indicator & Blood Indicator + catacurses::window wMedical; // Primary Window + catacurses::window w_description; // Bottom Detail Bar - /* Descriptions */ + // Window Definitions + const int TITLE_W_HEIGHT = 3; + const int DESC_W_HEIGHT = 6; // Consistent with Player Info (@) Menu + const int HEADER_Y = TITLE_W_HEIGHT; + const int TEXT_START_Y = HEADER_Y + 2; + const int INFO_START_Y = HEADER_Y + 8; + int DESC_W_BEGIN; + int HEIGHT; + int WIDTH; + + // Column Definitions + int second_column_x = 0; + int third_column_x = 0; - bool bleeding = this->has_effect(effect_bleed, part.id()); - bool bitten = this->has_effect(effect_bite, part.id()); - bool infected = this->has_effect(effect_infected, part.id()); + // Scrolling + int SCROLL_POINT; // The number of printed rows at which to enable scrolling + int scroll_position = 0; + int INFO_SCROLL_POINT; + int info_scroll_position = 0; - // BLEEDING block - if (bleeding) { - const int bleed_intensity = this->get_effect_int(effect_bleed, part); - const effect bleed_effect = this->get_effect(effect_bleed, part); - description += string_format("[ %s ] - %s\n", - colorize(bleed_effect.get_speed_name(), colorize_bleeding_intensity(bleed_intensity)), - bleed_effect.disp_short_desc()); - } + int info_lines = 0; - // BITTEN block - if (bitten) { - const effect bite_effect = this->get_effect(effect_bite, part); - description += string_format("[ %s ] - %s\n", - colorize(bite_effect.get_speed_name(), c_red), - bite_effect.disp_short_desc()); - } + // Cursor + int cursor_bounds[3]; // Number of selectable rows in each column + point cursor = point_zero; // Selector Position - // INFECTED block - if (infected) { - const effect infected_effect = this->get_effect(effect_infected, part); - description += string_format("[ %s ] - %s\n", - colorize(infected_effect.get_speed_name(), c_light_green), - infected_effect.disp_short_desc()); - } + ui_adaptor ui; + ui.on_screen_resize( [&]( ui_adaptor & ui ) { + const int WIDTH_OFFSET = ( TERMX - FULL_SCREEN_WIDTH ) / 4; + HEIGHT = std::min( TERMY, FULL_SCREEN_HEIGHT ); + WIDTH = FULL_SCREEN_WIDTH + WIDTH_OFFSET; - selection_line line; + const point win( ( TERMX - WIDTH ) / 2, ( TERMY - HEIGHT ) / 2 ); - if (detail.length() > 0) { - const std::string header_str = string_format("[%s] - %s", header, detail); - std::vector textformatted = foldstring(header_str, SUMMARY_WIDTH - 2, static_cast (93)); - const int lineCount = textformatted.size(); - if (lineCount <= 1) { - line = selection_line(header_str, description); - } - else { - //If there are too many tags, display them neatly on a new line. - std::string print_line = string_format("%s\n", textformatted[0]); - for (int i = 1; i < lineCount; i++) { - if (i != lineCount) { - print_line += string_format("->%s\n", textformatted[i]); - } - else { - print_line += string_format("->%s", textformatted[i]); - } - } - line = selection_line(print_line, description); - } - } - else { - line = selection_line(string_format("[%s]", header), description); - } + wMedical = catacurses::newwin( HEIGHT, WIDTH, win ); - SummaryLines.emplace_back(line); - } + w_title = catacurses::newwin( TITLE_W_HEIGHT, WIDTH, win ); + DESC_W_BEGIN = HEIGHT - DESC_W_HEIGHT - 1; + w_description = catacurses::newwin( DESC_W_HEIGHT, WIDTH - 2, + win + point( 1, DESC_W_BEGIN ) ); + //40% - 30% - 30% + second_column_x = WIDTH / 2.5f; + third_column_x = second_column_x + WIDTH / 3.3f; - /* Print Lines */ + ui.position_from_window( wMedical ); + } ); + ui.mark_resize(); - for (selection_line& line : SummaryLines) { - const bool is_highlighted = cursor == point(0, selection_row); - if (is_highlighted) { highlightline = line; } - linerow += line.print(wMedical, point(left_padding, TEXT_START_Y + 2 + linerow), SUMMARY_WIDTH, c_light_gray, is_highlighted); - ++selection_row; - } + ui.on_redraw( [&]( const ui_adaptor & ) { + werase( wMedical ); - /* SECOND COLUMN - EFFECTS */ - mvwprintz(wMedical, point(second_column_x, TEXT_START_Y), c_light_blue, _("EFFECTS")); + draw_border( wMedical, BORDER_COLOR, _( " MEDICAL " ) ); + mvwputch( wMedical, point( getmaxx( wMedical ) - 1, 2 ), BORDER_COLOR, LINE_XOXX ); // -| - EffectLines.clear(); + wnoutrefresh( wMedical ); - for (auto& elem : *effects) { - for (auto& _effect_it : elem.second) { - const std::string name = _effect_it.second.disp_name(); - if (name.empty()) { - continue; - } - EffectLines.emplace_back(selection_line(name, _effect_it.second.disp_desc())); - } - } + draw_medical_titlebar( w_title, this, WIDTH ); + mvwputch( w_title, point( second_column_x, HEADER_Y - 1 ), BORDER_COLOR, LINE_OXXX ); // ^|^ + mvwputch( w_title, point( third_column_x, HEADER_Y - 1 ), BORDER_COLOR, LINE_OXXX ); // ^|^ + wnoutrefresh( w_title ); - const float bmi = get_bmi(); + SCROLL_POINT = HEIGHT - TEXT_START_Y - DESC_W_HEIGHT - 3; - if (bmi < character_weight_category::underweight) { - std::string starvation_name; - std::string starvation_text; + // Columns - if (bmi < character_weight_category::emaciated) { - starvation_name = _("Severely Malnourished"); - starvation_text = - _("Your body is severely weakened by starvation. You might die if you don't start eating regular meals!\n\n"); - } - else { - starvation_name = _("Malnourished"); - starvation_text = - _("Your body is weakened by starvation. Only time and regular meals will help you recover.\n\n"); - } + int column_id = 0; - if (bmi < character_weight_category::underweight) { - const float str_penalty = 1.0f - ((bmi - 13.0f) / 3.0f); - starvation_text += std::string(_("Strength")) + " -" + string_format("%2.0f%%\n", - str_penalty * 100.0f); - starvation_text += std::string(_("Dexterity")) + " -" + string_format("%2.0f%%\n", - str_penalty * 50.0f); - starvation_text += std::string(_("Intelligence")) + " -" + string_format("%2.0f%%", - str_penalty * 50.0f); - } + // Health Summary + fold_and_print( wMedical, point( 2, HEADER_Y ), WIDTH - 2, c_light_blue, _( "HEALTH" ) ); + medical_column health_column = draw_health_summary( column_id++, this, point( 0, TEXT_START_Y ), + std::pair( second_column_x, HEIGHT ) ); - EffectLines.emplace_back(selection_line(starvation_name, starvation_text)); - } + // Effects Summary + mvwprintz( wMedical, point( second_column_x + 2, HEADER_Y ), c_light_blue, _( "EFFECTS" ) ); + medical_column effects_column = draw_effects_summary( column_id++, this, point( second_column_x, + TEXT_START_Y ), std::pair( third_column_x, HEIGHT ) ); - if (has_trait(trait_TROGLO) && g->is_in_sunlight(pos()) && - get_weather().weather_id->sun_intensity >= sun_intensity_type::high) { - EffectLines.emplace_back(selection_line("In Sunlight", "The sunlight irritates you.\n")); - } - else if (has_trait(trait_TROGLO2) && g->is_in_sunlight(pos())) { - EffectLines.emplace_back(selection_line("In Sunlight", "The sunlight irritates you badly.\n")); - } - else if (has_trait(trait_TROGLO3) && g->is_in_sunlight(pos())) { - EffectLines.emplace_back(selection_line("In Sunlight", "The sunlight irritates you terribly.\n")); - } + // Stats Summary + mvwprintz( wMedical, point( third_column_x + 2, HEADER_Y ), c_light_blue, _( "STATS" ) ); + medical_column stats_column = draw_stats_summary( column_id++, this, point( third_column_x, + TEXT_START_Y ), std::pair( WIDTH - 2, 5 ) ); - for (auto& elem : addictions) { - if (elem.sated < 0_turns && elem.intensity >= MIN_ADDICTION_LEVEL) { - EffectLines.emplace_back(selection_line(addiction_name(elem), addiction_text(elem))); - } + // Description Text + std::string desc_str; + std::pair detail_str; + switch( cursor.x ) { + case 0: + desc_str = health_column.set_highlight( cursor.y ).description(); + detail_str = health_column.detail_str( cursor.y ); + break; + case 1: + desc_str = effects_column.set_highlight( cursor.y ).description(); + detail_str = effects_column.detail_str( cursor.y ); + break; + case 2: + desc_str = stats_column.set_highlight( cursor.y ).description(); + detail_str = stats_column.detail_str( cursor.y ); + break; + default: + break; } - /* Print Effect Lines */ + // Description Bar + werase( w_description ); - linerow = 0; - selection_row = 0; - if (EffectLines.size() == 0) { EffectLines.emplace_back(selection_line(colorize("None", c_dark_gray), "")); } - for (selection_line& line : EffectLines) { - const bool is_highlighted = cursor == point(1, selection_row); - if (is_highlighted) { highlightline = line; } - linerow += line.print(wMedical, point(second_column_x, TEXT_START_Y + 2 + linerow), third_column_x - second_column_x, c_light_gray, is_highlighted); - ++selection_row; - } - - /* THIRD COLUMN - Stats */ - StatLines.clear(); + int DESCRIPTION_WIN_OFFSET; // Y Position of the start of the description bar (wMedical) - mvwprintz(wMedical, point(third_column_x, TEXT_START_Y), c_light_blue, _("STATS")); + if( !desc_str.empty() ) { + // Number of display rows required by the highlighted line. + std::vector textformatted = foldstring( desc_str, WIDTH - 2, static_cast( 32 ) ); - std::string strength_str = coloured_stat_display(this->get_str(), this->get_str_base()); - StatLines.emplace_back( - selection_line(string_format("Strength: %s", strength_str), - "Strength affects your melee damage, the amount of weight you can carry, your total HP, " - "your resistance to many diseases, and the effectiveness of actions which require brute force.")); + // Beginning row of description text [0-6] (w_description) + const int DESCRIPTION_TEXT_Y = DESC_W_HEIGHT - std::min( DESC_W_HEIGHT, + static_cast( textformatted.size() ) ); - std::string dexterity_str = coloured_stat_display(this->get_dex(), this->get_dex_base()); - StatLines.emplace_back( - selection_line(string_format("Dexterity: %s", dexterity_str), - "Dexterity affects your chance to hit in melee combat, helps you steady your " - "gun for ranged combat, and enhances many actions that require finesse.")); + DESCRIPTION_WIN_OFFSET = DESC_W_BEGIN + DESCRIPTION_TEXT_Y; + mvwputch( wMedical, point( 0, DESCRIPTION_WIN_OFFSET - 1 ), BORDER_COLOR, LINE_XXXO ); + mvwhline( wMedical, point( 1, DESCRIPTION_WIN_OFFSET - 1 ), LINE_OXOX, getmaxx( wMedical ) - 2 ); + mvwputch( wMedical, point( getmaxx( wMedical ) - 1, DESCRIPTION_WIN_OFFSET - 1 ), BORDER_COLOR, + LINE_XOXX ); + fold_and_print( w_description, point( 1, DESCRIPTION_TEXT_Y ), WIDTH - 2, c_light_gray, + desc_str ); + } else { + DESCRIPTION_WIN_OFFSET = getmaxy( wMedical ); + } - std::string intelligence_str = coloured_stat_display(this->get_int(), this->get_int_base()); - StatLines.emplace_back( - selection_line(string_format("Intelligence: %s", intelligence_str), - "Intelligence is less important in most situations, but it is vital for more complex tasks like " - "electronics crafting. It also affects how much skill you can pick up from reading a book.")); + wnoutrefresh( w_description ); - std::string perception_str = coloured_stat_display(this->get_per(), this->get_per_base()); - StatLines.emplace_back( - selection_line(string_format("Perception: %s", perception_str), - "Perception is the most important stat for ranged combat. It's also used for " - "detecting traps and other things of interest.")); + // Info Menu - /* Print Stat Lines */ + INFO_SCROLL_POINT = HEIGHT - INFO_START_Y - DESC_W_HEIGHT - 3; + const int INFO_CUTOFF = DESCRIPTION_WIN_OFFSET - 1 - ( INFO_START_Y + 3 ); - linerow = 0; - selection_row = 0; - for (selection_line& line : StatLines) { - const bool is_highlighted = cursor == point(2, selection_row); - if (is_highlighted) { highlightline = line; } - linerow += line.print(wMedical, point(third_column_x, TEXT_START_Y + 2 + linerow), WIDTH - third_column_x - right_padding, c_light_gray, is_highlighted); - ++selection_row; - } + if( !detail_str.first.empty() ) { + mvwprintz( wMedical, point( third_column_x + 2, INFO_START_Y + 1 ), c_light_blue, + detail_str.first ); - /* Update Cursor Boundary */ + mvwputch( wMedical, point( third_column_x, INFO_START_Y ), BORDER_COLOR, LINE_XXXO ); // |- + mvwhline( wMedical, point( third_column_x + 1, INFO_START_Y ), LINE_OXOX, + getmaxx( wMedical ) - 2 ); // - + mvwputch( wMedical, point( getmaxx( wMedical ) - 1, INFO_START_Y ), BORDER_COLOR, // -| + LINE_XOXX ); - switch (cursor.x) { - case 0: - selected_column_rows = SummaryLines.size(); - break; - case 1: - selected_column_rows = EffectLines.size(); - break; - case 2: - selected_column_rows = StatLines.size(); - break; + const int info_width = WIDTH - third_column_x - 3; + std::vector textformatted = foldstring( detail_str.second, info_width, + static_cast( 32 ) ); + info_lines = textformatted.size(); + int i = 0; + for( std::string &line : textformatted ) { + if( i - info_scroll_position >= INFO_CUTOFF ) { + break; + } + if( i++ >= info_scroll_position ) { + trim_and_print( wMedical, point( third_column_x + 2, INFO_START_Y + 2 + i - info_scroll_position ), + info_width, c_light_gray, line ); + } + } + } else { + info_lines = 0; } - /* DESCRIPTION BAR */ - - werase(w_description); - // Number of display rows required by highlightline.description() - const std::string desc_str = highlightline.description(); - std::vector textformatted = foldstring(desc_str, WIDTH - 2, static_cast (32)); - // Beginning row of description text [1-3] (w_description) - const int DESCRIPTION_TEXT_Y = DESC_W_HEIGHT - std::min(DESC_W_HEIGHT, static_cast(textformatted.size())); - // Actual position of the description bar (wMedical) - int DESCRIPTION_WIN_OFFSET = DESC_W_BEGIN + DESCRIPTION_TEXT_Y; - if (desc_str.size() > 0) - { - mvwputch(wMedical, point(0, DESCRIPTION_WIN_OFFSET - 1), BORDER_COLOR, LINE_XXXO); - mvwhline(wMedical, point(1, DESCRIPTION_WIN_OFFSET - 1), LINE_OXOX, getmaxx(wMedical) - 2); - mvwputch(wMedical, point(getmaxx(wMedical) - 1, DESCRIPTION_WIN_OFFSET - 1), BORDER_COLOR, LINE_XOXX); - fold_and_print(w_description, point(1, DESCRIPTION_TEXT_Y), WIDTH - 2, c_light_gray, highlightline.description()); - } - else { - DESCRIPTION_WIN_OFFSET = getmaxy(wMedical); - } - - wnoutrefresh(w_description); - - /* COLUMN BORDERS - Dependent on DESCRIPTION_WIN_OFFSET */ - //Second Column - mvwvline(wMedical, point(second_column_x - 2, TEXT_START_Y), LINE_XOXO, DESCRIPTION_WIN_OFFSET - 4); // | - mvwputch(wMedical, point(second_column_x - 2, DESCRIPTION_WIN_OFFSET - 1), BORDER_COLOR, LINE_XXOX); // _|_ - //Third Column - mvwvline(wMedical, point(third_column_x - 2, TEXT_START_Y), LINE_XOXO, DESCRIPTION_WIN_OFFSET - 4); // | - mvwputch(wMedical, point(third_column_x - 2, DESCRIPTION_WIN_OFFSET - 1), BORDER_COLOR, LINE_XXOX); // _|_ - - wnoutrefresh(wMedical); - }); - - input_context ctxt("MEDICAL"); - ctxt.register_action("UP"); - ctxt.register_action("DOWN"); - ctxt.register_action("LEFT"); - ctxt.register_action("RIGHT"); - ctxt.register_action("APPLY"); - ctxt.register_action("HELP_KEYBINDINGS"); - ctxt.register_action("QUIT"); - - for (;; ) { + // Draw Selection Lines For Each Column + + const int MAX_COLUMN_HEIGHT = DESCRIPTION_WIN_OFFSET - 1; + + health_column.print_column( wMedical, + health_column.current_column( cursor.x ) ? scroll_position : 0, MAX_COLUMN_HEIGHT ); + effects_column.print_column( wMedical, + effects_column.current_column( cursor.x ) ? scroll_position : 0, MAX_COLUMN_HEIGHT ); + stats_column.print_column( wMedical, stats_column.current_column( cursor.x ) ? scroll_position : 0, + MAX_COLUMN_HEIGHT ); + + // Update Cursor Boundaries to number of selectable rows + + cursor_bounds[0] = health_column.selection_count(); + cursor_bounds[1] = effects_column.selection_count(); + cursor_bounds[2] = stats_column.selection_count(); + + // Draw Column Borders + + effects_column.draw_column( wMedical, HEADER_Y, DESCRIPTION_WIN_OFFSET ); + stats_column.draw_column( wMedical, HEADER_Y, DESCRIPTION_WIN_OFFSET ); + + // Draw Scrollbars + + const int content_size = ( cursor_bounds[cursor.x] - 1 ) * 2; + + scrollbar() + .offset_x( 0 ) + .offset_y( HEADER_Y ) + .content_size( content_size ) + .viewport_pos( cursor.y * 2 ) + .viewport_size( DESC_W_BEGIN - 3 ) + .scroll_to_last( true ) + .apply( wMedical ); + + scrollbar() + .offset_x( third_column_x ) + .offset_y( INFO_START_Y + 1 ) + .content_size( info_lines + INFO_SCROLL_POINT ) + .viewport_pos( info_scroll_position ) + .viewport_size( INFO_SCROLL_POINT + 1 ) + .scroll_to_last( true ) + .apply( wMedical ); + + wnoutrefresh( wMedical ); + } ); + + input_context ctxt( "MEDICAL" ); + ctxt.register_action( "UP" ); + ctxt.register_action( "DOWN" ); + ctxt.register_action( "LEFT" ); + ctxt.register_action( "RIGHT" ); + ctxt.register_action( "APPLY" ); + ctxt.register_action( "HELP_KEYBINDINGS" ); + ctxt.register_action( "SCROLL_INFOBOX_UP", to_translation( "Scroll up" ) ); + ctxt.register_action( "SCROLL_INFOBOX_DOWN", to_translation( "Scroll down" ) ); + ctxt.register_action( "QUIT" ); + + for( ;; ) { ui_manager::redraw(); const std::string action = ctxt.handle_input(); - if (action == "DOWN" || action == "UP") { - const int step = cursor.y + (action == "DOWN" ? 1 : -1); - if (step == -1) cursor.y = selected_column_rows - 1; - else if (step > selected_column_rows - 1) cursor.y = 0; - else { cursor.y = step; } - } - else if (action == "RIGHT" || action == "LEFT") { - const int step = cursor.x + (action == "RIGHT" ? 1 : -1); - if (step == -1) cursor.x = 2; - else if (step == 3) cursor.x = 0; - else { cursor.x = step; } + if( action == "DOWN" || action == "UP" ) { + const int step = cursor.y + ( action == "DOWN" ? 1 : -1 ); + const int limit = cursor_bounds[cursor.x] - 1; - switch (cursor.x) { - case 0: - selected_column_rows = SummaryLines.size(); - break; - case 1: - selected_column_rows = EffectLines.size(); - break; - case 2: - selected_column_rows = StatLines.size(); - break; + if( step == -1 ) { + cursor.y = limit; + } else if( step > limit ) { + cursor.y = 0; + } else { + cursor.y = step; } - if (cursor.y > selected_column_rows - 1) { - cursor.y = selected_column_rows - 1; + const int scroll_overflow = limit - SCROLL_POINT; + if( scroll_overflow > 0 ) { + const int half_list = SCROLL_POINT / 2; + scroll_position = std::max( 0, std::min( scroll_overflow, cursor.y - half_list ) ); } - } - else if (action == "APPLY") { - avatar_action::use_item(*this); - } - else if (action == "QUIT") { + info_scroll_position = 0; + } else if( action == "RIGHT" || action == "LEFT" ) { + const int step = cursor.x + ( action == "RIGHT" ? 1 : -1 ); + const int limit = 2; + if( step == -1 ) { + cursor.x = limit; + } else if( step > limit ) { + cursor.x = 0; + } else { + cursor.x = step; + } + + // Match the cursor to the nearest row on the next column. + const int y_limit = cursor_bounds[cursor.x] - 1; + cursor.y = std::min( cursor.y - scroll_position, y_limit ); + + const int scroll_overflow = y_limit - SCROLL_POINT; + if( scroll_overflow > 0 ) { + const int half_list = SCROLL_POINT / 2; + scroll_position = std::max( 0, std::min( scroll_overflow, cursor.y - half_list ) ); + } else { + scroll_position = 0; + } + info_scroll_position = 0; + } else if( action == "APPLY" ) { + avatar_action::use_item( *this ); + } else if( action == "SCROLL_INFOBOX_UP" || action == "SCROLL_INFOBOX_DOWN" ) { + const int scroll_overflow = info_lines - INFO_SCROLL_POINT - 1; + if( scroll_overflow > 0 ) { + const int step = info_scroll_position + ( action == "SCROLL_INFOBOX_DOWN" ? 1 : -1 ); + + if( step == -1 ) { + info_scroll_position = scroll_overflow; + } else if( step > scroll_overflow ) { + info_scroll_position = 0; + } else { + info_scroll_position = step; + } + } else { + info_scroll_position = 0; + } + } else if( action == "QUIT" ) { break; } } diff --git a/src/player_display.cpp b/src/player_display.cpp index 7d20387b50f71..313253a033d13 100644 --- a/src/player_display.cpp +++ b/src/player_display.cpp @@ -14,6 +14,7 @@ #include "calendar.h" #include "cata_utility.h" #include "catacharset.h" +#include "character.h" #include "character_modifier.h" #include "color.h" #include "cursesdef.h" @@ -1148,6 +1149,8 @@ static bool handle_player_display_action( Character &you, unsigned int &line, } else if( action == "SCROLL_INFOBOX_DOWN" ) { ++info_line; ui_info.invalidate_ui(); + } else if( action == "MEDICAL_MENU" ) { + you.as_avatar()->disp_medical(); } return done; } @@ -1325,6 +1328,7 @@ void Character::disp_info( bool customize_character ) ctxt.register_action( "SCROLL_INFOBOX_UP", to_translation( "Scroll information box up" ) ); ctxt.register_action( "SCROLL_INFOBOX_DOWN", to_translation( "Scroll information box down" ) ); ctxt.register_action( "HELP_KEYBINDINGS" ); + ctxt.register_action( "MEDICAL_MENU" ); std::map speed_effects; for( auto &elem : *effects ) { From 060145167d3382b6105c8ae3c1e6ce1c69eba3d8 Mon Sep 17 00:00:00 2001 From: Red Rose Date: Wed, 9 Feb 2022 12:15:14 +1100 Subject: [PATCH 04/10] [Medical Menu] Removal of widget dependency Removed temporarily until the widget implementation is less in-progress; Code cleanup. --- src/display.h | 1 - src/medical_ui.cpp | 64 +++++++++++++++++----------------------------- 2 files changed, 24 insertions(+), 41 deletions(-) diff --git a/src/display.h b/src/display.h index 06c15c8bf41d7..a1a11bbf3d507 100644 --- a/src/display.h +++ b/src/display.h @@ -90,7 +90,6 @@ std::pair fatigue_text_color( const Character &u ); std::pair health_text_color( const Character &u ); std::pair pain_text_color( const Creature &c ); std::pair pain_text_color( const Character &u ); -std::map bodypart_status_colors(const Character& u, const bodypart_id& bp, const std::string& wgt_id); // Change in character body temperature, as colorized arrows std::pair temp_delta_arrows( const Character &u ); // Character morale, as a color-coded ascii emoticon face diff --git a/src/medical_ui.cpp b/src/medical_ui.cpp index 715ee6b698576..e364570c73579 100644 --- a/src/medical_ui.cpp +++ b/src/medical_ui.cpp @@ -21,19 +21,16 @@ #include "vitamin.h" #include "weather.h" +static const trait_id trait_COLDBLOOD4( "COLDBLOOD4" ); static const efftype_id effect_bite( "bite" ); static const efftype_id effect_bleed( "bleed" ); static const efftype_id effect_infected( "infected" ); - -static const trait_id trait_COLDBLOOD4( "COLDBLOOD4" ); static const trait_id trait_NOPAIN( "NOPAIN" ); static const trait_id trait_SUNLIGHT_DEPENDENT( "SUNLIGHT_DEPENDENT" ); static const trait_id trait_TROGLO( "TROGLO" ); static const trait_id trait_TROGLO2( "TROGLO2" ); static const trait_id trait_TROGLO3( "TROGLO3" ); -static const flag_id json_flag_SPLINT( "SPLINT" ); - enum class medical_tab_mode { TAB_SUMMARY }; @@ -123,17 +120,19 @@ class medical_column int selectionrow = 0; for( selection_line &line : column_lines ) { - const int row_start_x = line.highlighted() ? - COLUMN_START.x + left_padding - 1 : - COLUMN_START.x + left_padding; - const int row_start_y = COLUMN_START.y + linerow; - - if( row_start_y - LINE_START >= MAX_HEIGHT ) { + const point row_start( + line.highlighted() ? + COLUMN_START.x + left_padding - 1 : + COLUMN_START.x + left_padding, + COLUMN_START.y + linerow + ); + + if( row_start.y - LINE_START >= MAX_HEIGHT ) { break; } if( linerow >= LINE_START ) { - fold_and_print( window, point( row_start_x, row_start_y - LINE_START ), max_width(), + fold_and_print( window, point( row_start.x, row_start.y - LINE_START ), max_width(), c_light_gray, line.print() ); linerow += line.get_row_count(); } else { @@ -177,7 +176,7 @@ class medical_column std::pair detail_str( int y ) { std::pair ret; - if( y < column_lines.size() ) { + if( y < static_cast( column_lines.size() ) ) { int offset = y % column_lines.size(); ret = column_lines[offset].get_detail(); } @@ -255,7 +254,7 @@ static void draw_medical_titlebar( const catacurses::window &window, avatar *pla // Pain Indicator auto pain_descriptor = display::pain_text_color( *player ); if( !pain_descriptor.first.empty() ) { - const std::string pain_str = string_format( "In %s", pain_descriptor.first ); + const std::string pain_str = string_format( _( "In %s" ), pain_descriptor.first ); cur_str_pos = right_print( window, 1, right_indent, pain_descriptor.second, pain_str ); @@ -346,30 +345,14 @@ static medical_column draw_health_summary( const int column_count, avatar *playe const bool bleeding = bleed_intensity > 0; const bool bitten = player->has_effect( effect_bite, part.id() ); const bool infected = player->has_effect( effect_infected, part.id() ); - - // Colorized strings for each status - std::vector color_strings; - widget_id wid( "bodypart_status_indicator_template" ); - for( const auto &sc : display::bodypart_status_colors( *player, part, - "bodypart_status_indicator_template" ) ) { - std::string txt = io::enum_to_string( sc.first ); - if( wid.is_valid() ) { - translation t = widget_phrase::get_text_for_id( txt, wid ); - txt = to_upper_case( t.empty() ? txt : t.translated() ); - } - color_strings.emplace_back( string_format( "[ %s ]", colorize( txt, sc.second ) ) ); - } - detail += join( color_strings, " " ); - - bool no_feeling = player->has_trait( trait_NOPAIN ); + const bool no_feeling = player->has_trait( trait_NOPAIN ); const int maximal_hp = player->get_part_hp_max( part ); const int current_hp = player->get_part_hp_cur( part ); const bool limb_is_broken = player->is_limb_broken( part ); const bool limb_is_mending = player->worn_with_flag( flag_SPLINT, part ); if( limb_is_mending ) { - detail += colorize( _( "Splinted" ), - c_blue ); + detail += string_format( _( "[ %s ]" ), colorize( ( "SPLINTED" ), c_yellow ) ); if( no_feeling ) { hp_str = colorize( "==%==", c_blue ); } else { @@ -380,7 +363,7 @@ static medical_column draw_health_summary( const int column_count, avatar *playe hp_str = colorize( std::string( num, '#' ) + std::string( 5 - num, '=' ), c_blue ); } } else if( limb_is_broken ) { - detail += colorize( _( "Broken" ), c_red ); + detail += string_format( _( "[ %s ]" ), colorize( ( "BROKEN" ), c_red ) ); hp_str = "==%=="; } else if( no_feeling ) { if( current_hp < maximal_hp * 0.25 ) { @@ -402,19 +385,20 @@ static medical_column draw_health_summary( const int column_count, avatar *playe display::limb_color( *player, part, true, true, true ) ) + " " + hp_str; - /* Descriptions */ - // BLEEDING block if( bleeding ) { const effect bleed_effect = player->get_effect( effect_bleed, part ); + const nc_color bleeding_color = colorize_bleeding_intensity( bleed_intensity ); + detail += string_format( _( "[ %s ]" ), colorize( ( "BLEEDING" ), bleeding_color ) ); description += string_format( "[ %s ] - %s\n", - colorize( bleed_effect.get_speed_name(), colorize_bleeding_intensity( bleed_intensity ) ), + colorize( bleed_effect.get_speed_name(), bleeding_color ), bleed_effect.disp_short_desc() ); } // BITTEN block if( bitten ) { const effect bite_effect = player->get_effect( effect_bite, part ); + detail += string_format( _( "[ %s ]" ), colorize( ( "BITTEN" ), c_yellow ) ); description += string_format( "[ %s ] - %s\n", colorize( bite_effect.get_speed_name(), c_yellow ), bite_effect.disp_short_desc() ); @@ -423,6 +407,7 @@ static medical_column draw_health_summary( const int column_count, avatar *playe // INFECTED block if( infected ) { const effect infected_effect = player->get_effect( effect_infected, part ); + detail += string_format( _( "[ %s ]" ), colorize( ( "INFECTED" ), c_pink ) ); description += string_format( "[ %s ] - %s\n", colorize( infected_effect.get_speed_name(), c_pink ), infected_effect.disp_short_desc() ); @@ -585,7 +570,7 @@ static medical_column draw_stats_summary( const int column_count, avatar *player int runcost = player->run_cost( 100 ); int newmoves = player->get_speed(); - std::string coloured_str = colorize( string_format( _( "%s" ), runcost ), + std::string coloured_str = colorize( string_format( _( "%d" ), runcost ), ( runcost <= 100 ? c_green : c_red ) ); selection_line runcost_line = selection_line( string_format( _( "Base Move Cost: %s" ), coloured_str ), @@ -593,7 +578,7 @@ static medical_column draw_stats_summary( const int column_count, avatar *player c_light_blue ), max_width ); - coloured_str = colorize( string_format( _( "%s" ), newmoves ), + coloured_str = colorize( string_format( _( "%d" ), newmoves ), ( newmoves >= 100 ? c_green : c_red ) ); selection_line movecost_line = selection_line( string_format( _( "Current Speed: %s" ), coloured_str ), @@ -630,9 +615,8 @@ static medical_column draw_stats_summary( const int column_count, avatar *player } if( player->kcal_speed_penalty() < 0 ) { pen = std::abs( player->kcal_speed_penalty() ); - //~ %s: Starving/Underfed (already left-justified), %2d: speed penalty pge_str = pgettext( "speed penalty", player->get_bmi() < character_weight_category::underweight ? - _( "Starving" ) : _( "Underfed" ) ); + ( "Starving" ) : ( "Underfed" ) ); speed_detail_str += colorize( string_format( _( "%s -%2d%%\n" ), pge_str, pen ), c_red ); } if( player->has_trait( trait_SUNLIGHT_DEPENDENT ) && !g->is_in_sunlight( player->pos() ) ) { @@ -746,7 +730,7 @@ void avatar::disp_medical() // Cursor int cursor_bounds[3]; // Number of selectable rows in each column - point cursor = point_zero; // Selector Position + point cursor; ui_adaptor ui; ui.on_screen_resize( [&]( ui_adaptor & ui ) { From 3d4ab9c0f1fd7e0648c8ecf20e2e3395189d2254 Mon Sep 17 00:00:00 2001 From: Maleclypse <54345792+Maleclypse@users.noreply.github.com> Date: Tue, 15 Mar 2022 22:09:46 -0500 Subject: [PATCH 05/10] Update data/raw/keybindings.json --- data/raw/keybindings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/raw/keybindings.json b/data/raw/keybindings.json index fb2116961bee8..961a09420a25b 100644 --- a/data/raw/keybindings.json +++ b/data/raw/keybindings.json @@ -1862,7 +1862,7 @@ "type": "keybinding", "id": "APPLY", "category": "MEDICAL", - "name": "Apply or Use Item", + "name": "Apply and/or Use Item", "bindings": [ { "input_method": "keyboard_char", "key": "A" }, { "input_method": "keyboard_code", "key": "a", "mod": [ "shift" ] } ] }, { From d6020c1fedc6f11fccad789994a0b1af5932d602 Mon Sep 17 00:00:00 2001 From: Red Rose Date: Sun, 20 Mar 2022 17:42:35 +1100 Subject: [PATCH 06/10] Refactoring # Explain why this change is being made # |<---- Limit Each Line to a Maximum Of 72 Characters ---->| # Provide links or keys to any relevant tickets, articles or other resources # Example: fixes #1234, closes #2345, resolves #3456, references #4567 --- src/medical_ui.cpp | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/src/medical_ui.cpp b/src/medical_ui.cpp index e364570c73579..3d05451fb2656 100644 --- a/src/medical_ui.cpp +++ b/src/medical_ui.cpp @@ -457,28 +457,29 @@ static medical_column draw_health_summary( const int column_count, avatar *playe } for( const character_modifier &mod : character_modifier::get_all() ) { - const limb_score_id &sc = mod.use_limb_score(); - if( sc.is_null() || !part->has_limb_score( sc ) ) { - continue; - } - std::string desc = mod.description().translated(); - float injury_score = bp->get_limb_score( sc, 0, 0, 1 ); - float max_score = part->get_limb_score( sc ); - nc_color score_c; - - if( injury_score < max_score * 0.4f ) { - score_c = c_red; - } else if( injury_score < max_score * 0.6f ) { - score_c = c_light_red; - } else if( injury_score < max_score * 0.75f ) { - score_c = c_yellow; - } else { - score_c = c_white; - } + for( const auto &sc : mod.use_limb_scores() ) { + if( sc.first.is_null() || !part->has_limb_score( sc.first ) ) { + continue; + } + std::string desc = mod.description().translated(); + float injury_score = bp->get_limb_score( sc.first, 0, 0, 1 ); + float max_score = part->get_limb_score( sc.first ); + nc_color score_c; + + if( injury_score < max_score * 0.4f ) { + score_c = c_red; + } else if( injury_score < max_score * 0.6f ) { + score_c = c_light_red; + } else if( injury_score < max_score * 0.75f ) { + score_c = c_yellow; + } else { + score_c = c_white; + } - std::string valstr = colorize( string_format( "%.2f", mod.modifier( *player->as_character() ) ), - score_c ); - detail_str += string_format( "%s: %s%s\n", desc, mod.mod_type_str(), valstr ); + std::string valstr = colorize( string_format( "%.2f", mod.modifier( *player->as_character() ) ), + score_c ); + detail_str += string_format( "%s: %s%s\n", desc, mod.mod_type_str(), valstr ); + } } line.set_detail( string_format( _( "%s STATS" ), to_upper_case( bp_name ) ), detail_str ); From 1f050a6092a1be2354120bf7e000355ddf0a03e7 Mon Sep 17 00:00:00 2001 From: Red Rose Date: Sun, 20 Mar 2022 17:58:14 +1100 Subject: [PATCH 07/10] [Medical Menu] Code Clarity # |<-- Using around 50, Maximum 72 Characters -->| # Explain why this change is being made # |<---- Limit Each Line to a Maximum Of 72 Characters ---->| # Provide links or keys to any relevant tickets, articles or other resources # Example: fixes #1234, closes #2345, resolves #3456, references #4567 --- src/medical_ui.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/medical_ui.cpp b/src/medical_ui.cpp index 3d05451fb2656..036ecf8a73dba 100644 --- a/src/medical_ui.cpp +++ b/src/medical_ui.cpp @@ -42,7 +42,7 @@ class selection_line selection_line( const std::string text, const std::string &desc_str, const int max_width ) : desc_str( desc_str ) { std::vector textformatted = foldstring( text, max_width, - static_cast( 93 ) ); + ']' ); row_count = textformatted.size(); if( row_count > 1 ) { //If there are too many tags, display them neatly on a new line. @@ -819,7 +819,7 @@ void avatar::disp_medical() if( !desc_str.empty() ) { // Number of display rows required by the highlighted line. - std::vector textformatted = foldstring( desc_str, WIDTH - 2, static_cast( 32 ) ); + std::vector textformatted = foldstring( desc_str, WIDTH - 2, ' ' ); // Beginning row of description text [0-6] (w_description) const int DESCRIPTION_TEXT_Y = DESC_W_HEIGHT - std::min( DESC_W_HEIGHT, @@ -855,7 +855,7 @@ void avatar::disp_medical() const int info_width = WIDTH - third_column_x - 3; std::vector textformatted = foldstring( detail_str.second, info_width, - static_cast( 32 ) ); + ' ' ); info_lines = textformatted.size(); int i = 0; for( std::string &line : textformatted ) { From 1700441c57db9fe3250c467053ee0389cc6e65c8 Mon Sep 17 00:00:00 2001 From: Red Rose Date: Tue, 22 Mar 2022 18:47:32 +1100 Subject: [PATCH 08/10] [Medical Menu] C++ Analysis # |<-- Using around 50, Maximum 72 Characters -->| # Explain why this change is being made # |<---- Limit Each Line to a Maximum Of 72 Characters ---->| # Provide links or keys to any relevant tickets, articles or other resources # Example: fixes #1234, closes #2345, resolves #3456, references #4567 --- src/medical_ui.cpp | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/medical_ui.cpp b/src/medical_ui.cpp index 036ecf8a73dba..87e4eceeb671d 100644 --- a/src/medical_ui.cpp +++ b/src/medical_ui.cpp @@ -21,9 +21,9 @@ #include "vitamin.h" #include "weather.h" -static const trait_id trait_COLDBLOOD4( "COLDBLOOD4" ); static const efftype_id effect_bite( "bite" ); static const efftype_id effect_bleed( "bleed" ); +static const trait_id trait_COLDBLOOD4( "COLDBLOOD4" ); static const efftype_id effect_infected( "infected" ); static const trait_id trait_NOPAIN( "NOPAIN" ); static const trait_id trait_SUNLIGHT_DEPENDENT( "SUNLIGHT_DEPENDENT" ); @@ -132,7 +132,7 @@ class medical_column } if( linerow >= LINE_START ) { - fold_and_print( window, point( row_start.x, row_start.y - LINE_START ), max_width(), + fold_and_print( window, row_start - point( 0, LINE_START ), max_width(), c_light_gray, line.print() ); linerow += line.get_row_count(); } else { @@ -222,9 +222,6 @@ static void draw_medical_titlebar( const catacurses::window &window, avatar *pla werase( window ); draw_border( window, BORDER_COLOR, _( " MEDICAL " ) ); - // Window Title - center_print( window, 0, c_blue, _( " MEDICAL " ) ); - // Tabs const std::vector> tabs = { { medical_tab_mode::TAB_SUMMARY, string_format( _( "SUMMARY" ) ) } @@ -320,11 +317,19 @@ static void draw_medical_titlebar( const catacurses::window &window, avatar *pla ctxt.get_desc( "SCROLL_INFOBOX_UP" ), ctxt.get_desc( "SCROLL_INFOBOX_DOWN" ), ctxt.get_desc( "APPLY" ), ctxt.get_desc( "HELP_KEYBINDINGS" ) ); + const int details_width = utf8_width( remove_color_tags( desc ) ) + 3; const int max_width = right_indent + TAB_WIDTH; - if( WIDTH - max_width > utf8_width( remove_color_tags( desc ) ) + 3 ) { + if( WIDTH - max_width > details_width ) { // If the window runs out of room, we won't print keybindings. right_print( window, 1, right_indent, c_white, desc ); } + + const std::string TITLE_STR = " MEDICAL "; + + // Window Title + if( WIDTH - ( details_width + utf8_width( TITLE_STR ) > WIDTH / 2 ) ) { + center_print( window, 0, c_blue, _( TITLE_STR ) ); + } } // Displays a summary of each bodypart's health, including a display for a few 'statuses' @@ -352,7 +357,7 @@ static medical_column draw_health_summary( const int column_count, avatar *playe const bool limb_is_mending = player->worn_with_flag( flag_SPLINT, part ); if( limb_is_mending ) { - detail += string_format( _( "[ %s ]" ), colorize( ( "SPLINTED" ), c_yellow ) ); + detail += string_format( _( "[ %s ]" ), colorize( _( "SPLINTED" ), c_yellow ) ); if( no_feeling ) { hp_str = colorize( "==%==", c_blue ); } else { @@ -363,7 +368,7 @@ static medical_column draw_health_summary( const int column_count, avatar *playe hp_str = colorize( std::string( num, '#' ) + std::string( 5 - num, '=' ), c_blue ); } } else if( limb_is_broken ) { - detail += string_format( _( "[ %s ]" ), colorize( ( "BROKEN" ), c_red ) ); + detail += string_format( _( "[ %s ]" ), colorize( _( "BROKEN" ), c_red ) ); hp_str = "==%=="; } else if( no_feeling ) { if( current_hp < maximal_hp * 0.25 ) { @@ -389,7 +394,7 @@ static medical_column draw_health_summary( const int column_count, avatar *playe if( bleeding ) { const effect bleed_effect = player->get_effect( effect_bleed, part ); const nc_color bleeding_color = colorize_bleeding_intensity( bleed_intensity ); - detail += string_format( _( "[ %s ]" ), colorize( ( "BLEEDING" ), bleeding_color ) ); + detail += string_format( _( "[ %s ]" ), colorize( _( "BLEEDING" ), bleeding_color ) ); description += string_format( "[ %s ] - %s\n", colorize( bleed_effect.get_speed_name(), bleeding_color ), bleed_effect.disp_short_desc() ); @@ -398,7 +403,7 @@ static medical_column draw_health_summary( const int column_count, avatar *playe // BITTEN block if( bitten ) { const effect bite_effect = player->get_effect( effect_bite, part ); - detail += string_format( _( "[ %s ]" ), colorize( ( "BITTEN" ), c_yellow ) ); + detail += string_format( _( "[ %s ]" ), colorize( _( "BITTEN" ), c_yellow ) ); description += string_format( "[ %s ] - %s\n", colorize( bite_effect.get_speed_name(), c_yellow ), bite_effect.disp_short_desc() ); @@ -407,7 +412,7 @@ static medical_column draw_health_summary( const int column_count, avatar *playe // INFECTED block if( infected ) { const effect infected_effect = player->get_effect( effect_infected, part ); - detail += string_format( _( "[ %s ]" ), colorize( ( "INFECTED" ), c_pink ) ); + detail += string_format( _( "[ %s ]" ), colorize( _( "INFECTED" ), c_pink ) ); description += string_format( "[ %s ] - %s\n", colorize( infected_effect.get_speed_name(), c_pink ), infected_effect.disp_short_desc() ); @@ -617,7 +622,7 @@ static medical_column draw_stats_summary( const int column_count, avatar *player if( player->kcal_speed_penalty() < 0 ) { pen = std::abs( player->kcal_speed_penalty() ); pge_str = pgettext( "speed penalty", player->get_bmi() < character_weight_category::underweight ? - ( "Starving" ) : ( "Underfed" ) ); + "Starving" : "Underfed" ); speed_detail_str += colorize( string_format( _( "%s -%2d%%\n" ), pge_str, pen ), c_red ); } if( player->has_trait( trait_SUNLIGHT_DEPENDENT ) && !g->is_in_sunlight( player->pos() ) ) { From 512c38da992c5c0951ba179738df5b987319cab1 Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Thu, 24 Mar 2022 17:43:38 -0700 Subject: [PATCH 09/10] Update src/medical_ui.cpp --- src/medical_ui.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/medical_ui.cpp b/src/medical_ui.cpp index 87e4eceeb671d..a10e348fd4665 100644 --- a/src/medical_ui.cpp +++ b/src/medical_ui.cpp @@ -23,8 +23,9 @@ static const efftype_id effect_bite( "bite" ); static const efftype_id effect_bleed( "bleed" ); -static const trait_id trait_COLDBLOOD4( "COLDBLOOD4" ); static const efftype_id effect_infected( "infected" ); + +static const trait_id trait_COLDBLOOD4( "COLDBLOOD4" ); static const trait_id trait_NOPAIN( "NOPAIN" ); static const trait_id trait_SUNLIGHT_DEPENDENT( "SUNLIGHT_DEPENDENT" ); static const trait_id trait_TROGLO( "TROGLO" ); From f1c9717eb10ea20a6b8adce5dbd2754671fe09b3 Mon Sep 17 00:00:00 2001 From: Red Rose Date: Sun, 27 Mar 2022 15:54:54 +1100 Subject: [PATCH 10/10] [Medical Menu] Static efftype_id # |<-- Using around 50, Maximum 72 Characters -->| # Explain why this change is being made # |<---- Limit Each Line to a Maximum Of 72 Characters ---->| # Provide links or keys to any relevant tickets, articles or other resources # Example: fixes #1234, closes #2345, resolves #3456, references #4567 --- src/medical_ui.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/medical_ui.cpp b/src/medical_ui.cpp index a10e348fd4665..b7ebb1ae1d275 100644 --- a/src/medical_ui.cpp +++ b/src/medical_ui.cpp @@ -24,6 +24,7 @@ static const efftype_id effect_bite( "bite" ); static const efftype_id effect_bleed( "bleed" ); static const efftype_id effect_infected( "infected" ); +static const efftype_id effect_mending( "mending" ); static const trait_id trait_COLDBLOOD4( "COLDBLOOD4" ); static const trait_id trait_NOPAIN( "NOPAIN" ); @@ -362,7 +363,7 @@ static medical_column draw_health_summary( const int column_count, avatar *playe if( no_feeling ) { hp_str = colorize( "==%==", c_blue ); } else { - const auto &eff = player->get_effect( efftype_id( "mending" ), part ); + const auto &eff = player->get_effect( effect_mending, part ); const int mend_perc = eff.is_null() ? 0.0 : 100 * eff.get_duration() / eff.get_max_duration(); const int num = mend_perc / 20;