/* * File: output.cc * Summary: Functions used to print player related info. * Written by: Linley Henzell */ #include "AppHdr.h" #include "output.h" #include #include #include "externs.h" #include "options.h" #include "species.h" #include "abl-show.h" #include "branch.h" #include "cio.h" #include "colour.h" #include "coord.h" #include "describe.h" #include "format.h" #include "fight.h" #include "godabil.h" #include "initfile.h" #include "itemname.h" #include "item_use.h" #include "menu.h" #include "message.h" #include "mon-stuff.h" #include "mon-info.h" #include "mon-util.h" #include "newgame.h" #include "jobs.h" #include "ouch.h" #include "player.h" #include "place.h" #include "religion.h" #include "skills2.h" #include "stuff.h" #include "transform.h" #include "travel.h" #include "viewchar.h" #include "viewgeom.h" // Color for captions like 'Health:', 'Str:', etc. #define HUD_CAPTION_COLOUR Options.status_caption_colour // Colour for values, which come after captions. static const short HUD_VALUE_COLOUR = LIGHTGREY; // ---------------------------------------------------------------------- // colour_bar // ---------------------------------------------------------------------- class colour_bar { typedef unsigned short color_t; public: colour_bar(color_t default_colour, color_t change_pos, color_t change_neg, color_t empty) : m_default(default_colour), m_change_pos(change_pos), m_change_neg(change_neg), m_empty(empty), m_old_disp(-1), m_request_redraw_after(0) { // m_old_disp < 0 means it's invalid and needs to be initialised. } bool wants_redraw() const { return (m_request_redraw_after && you.num_turns >= m_request_redraw_after); } void draw(int ox, int oy, int val, int max_val) { ASSERT(val <= max_val); if (max_val <= 0) { m_old_disp = -1; return; } const int width = crawl_view.hudsz.x - (ox-1); const int disp = width * val / max_val; const int old_disp = (m_old_disp < 0) ? disp : m_old_disp; m_old_disp = disp; cgotoxy(ox, oy, GOTO_STAT); textcolor(BLACK); for (int cx = 0; cx < width; cx++) { #ifdef USE_TILE // Maybe this should use textbackground too? textcolor(BLACK + m_empty * 16); if (cx < disp) textcolor(BLACK + m_default * 16); else if (old_disp > disp && cx < old_disp) textcolor(BLACK + m_change_neg * 16); putch(' '); #else if (cx < disp && cx < old_disp) { textcolor(m_default); putch('='); } else if (cx < disp) { textcolor(m_change_pos); putch('='); } else if (cx < old_disp) { textcolor(m_change_neg); putch('-'); } else { textcolor(m_empty); putch('-'); } #endif // If some change colour was rendered, redraw in a few // turns to clear it out. if (old_disp != disp) m_request_redraw_after = you.num_turns + 4; else m_request_redraw_after = 0; } textcolor(LIGHTGREY); textbackground(BLACK); } private: const color_t m_default; const color_t m_change_pos; const color_t m_change_neg; const color_t m_empty; int m_old_disp; int m_request_redraw_after; // force a redraw at this turn count }; colour_bar HP_Bar(LIGHTGREEN, GREEN, RED, DARKGREY); #ifdef USE_TILE colour_bar MP_Bar(BLUE, BLUE, LIGHTBLUE, DARKGREY); #else colour_bar MP_Bar(LIGHTBLUE, BLUE, MAGENTA, DARKGREY); #endif // ---------------------------------------------------------------------- // Status display // ---------------------------------------------------------------------- static int _bad_ench_colour( int lvl, int orange, int red ) { if (lvl > red) return (RED); else if (lvl > orange) return (LIGHTRED); return (YELLOW); } static int _dur_colour( int running_out_color, bool running_out ) { if (running_out) { return running_out_color; } else { switch (running_out_color) { case GREEN: return ( LIGHTGREEN ); case BLUE: return ( LIGHTBLUE ); case MAGENTA: return ( LIGHTMAGENTA ); case LIGHTGREY: return ( WHITE ); default: return running_out_color; } } } #ifdef DGL_SIMPLE_MESSAGING void update_message_status() { static const char *msg = "(Hit _)"; static const int len = strlen(msg); static const std::string spc(len, ' '); textcolor(LIGHTBLUE); cgotoxy(crawl_view.hudsz.x - len + 1, 1, GOTO_STAT); if (SysEnv.have_messages) cprintf(msg); else cprintf(spc.c_str()); textcolor(LIGHTGREY); } #endif void update_turn_count() { // Don't update turn counter when running/resting/traveling to // prevent pointless screen updates. if (!Options.show_gold_turns || you.running > 0 || you.running < 0 && Options.travel_delay == -1) { return; } cgotoxy(19+6, 8, GOTO_STAT); // Show the turn count starting from 1. You can still quit on turn 0. textcolor(HUD_VALUE_COLOUR); cprintf("%ld", you.num_turns); textcolor(LIGHTGREY); } static int _count_digits(int val) { if (val > 999) return 4; else if (val > 99) return 3; else if (val > 9) return 2; return 1; } static const char* _describe_hunger(int& color) { const bool vamp = (you.species == SP_VAMPIRE); switch (you.hunger_state) { case HS_ENGORGED: color = LIGHTGREEN; return (vamp ? "Alive" : "Engorged"); case HS_VERY_FULL: color = GREEN; return ("Very Full"); case HS_FULL: color = GREEN; return ("Full"); case HS_SATIATED: // normal color = GREEN; return NULL; case HS_HUNGRY: color = YELLOW; return (vamp ? "Thirsty" : "Hungry"); case HS_VERY_HUNGRY: color = YELLOW; return (vamp ? "Very Thirsty" : "Very Hungry"); case HS_NEAR_STARVING: color = YELLOW; return (vamp ? "Near Bloodless" : "Near Starving"); case HS_STARVING: default: color = RED; return (vamp ? "Bloodless" : "Starving"); } } static void _print_stats_mp(int x, int y) { // Calculate colour short mp_colour = HUD_VALUE_COLOUR; const bool boosted = you.duration[DUR_DIVINE_VIGOUR]; if (boosted) mp_colour = LIGHTBLUE; else { int mp_percent = (you.max_magic_points == 0 ? 100 : (you.magic_points * 100) / you.max_magic_points); for (unsigned int i = 0; i < Options.mp_colour.size(); ++i) if (mp_percent <= Options.mp_colour[i].first) mp_colour = Options.mp_colour[i].second; } cgotoxy(x+8, y, GOTO_STAT); textcolor(mp_colour); cprintf("%d", you.magic_points); if (!boosted) textcolor(HUD_VALUE_COLOUR); cprintf("/%d", you.max_magic_points); if (boosted) textcolor(HUD_VALUE_COLOUR); int col = _count_digits(you.magic_points) + _count_digits(you.max_magic_points) + 1; for (int i = 11-col; i > 0; i--) cprintf(" "); if (!Options.classic_hud) MP_Bar.draw(19, y, you.magic_points, you.max_magic_points); } static void _print_stats_hp(int x, int y) { const int max_max_hp = get_real_hp(true, true); // Calculate colour short hp_colour = HUD_VALUE_COLOUR; const bool boosted = you.duration[DUR_DIVINE_VIGOUR] || you.berserk(); if (boosted) hp_colour = LIGHTBLUE; else { const int hp_percent = (you.hp * 100) / (max_max_hp ? max_max_hp : you.hp); for (unsigned int i = 0; i < Options.hp_colour.size(); ++i) if (hp_percent <= Options.hp_colour[i].first) hp_colour = Options.hp_colour[i].second; } // 01234567890123456789 // Health: xxx/yyy (zzz) cgotoxy(x, y, GOTO_STAT); textcolor(HUD_CAPTION_COLOUR); cprintf(max_max_hp != you.hp_max ? "HP: " : "Health: "); textcolor(hp_colour); cprintf("%d", you.hp); if (!boosted) textcolor(HUD_VALUE_COLOUR); cprintf("/%d", you.hp_max); if (max_max_hp != you.hp_max) cprintf(" (%d)", max_max_hp); if (boosted) textcolor(HUD_VALUE_COLOUR); int col = wherex() - crawl_view.hudp.x; for (int i = 18-col; i > 0; i--) cprintf(" "); if (!Options.classic_hud) HP_Bar.draw(19, y, you.hp, you.hp_max); } short _get_stat_colour(stat_type stat) { int val = 0, max_val = 0; switch (stat) { case STAT_STRENGTH: val = you.strength; max_val = you.max_strength; break; case STAT_INTELLIGENCE: val = you.intel; max_val = you.max_intel; break; case STAT_DEXTERITY: val = you.dex; max_val = you.max_dex; break; default: ASSERT(false); } // Check the stat_colour option for warning thresholds. for (unsigned int i = 0; i < Options.stat_colour.size(); ++i) if (val <= Options.stat_colour[i].first) return (Options.stat_colour[i].second); // Stat is magically increased. if (you.duration[DUR_DIVINE_STAMINA] || stat == STAT_STRENGTH && you.duration[DUR_MIGHT] || stat == STAT_INTELLIGENCE && you.duration[DUR_BRILLIANCE] || stat == STAT_DEXTERITY && you.duration[DUR_AGILITY]) { return (LIGHTBLUE); // no end of effect warning } // Stat is degenerated. if (val < max_val) return (YELLOW); return (HUD_VALUE_COLOUR); } // XXX: alters state! Does more than just print! static void _print_stats_str(int x, int y) { if (you.strength < 0) you.strength = 0; else if (you.strength > 72) you.strength = 72; if (you.max_strength > 72) you.max_strength = 72; cgotoxy(x+5, y, GOTO_STAT); textcolor(_get_stat_colour(STAT_STRENGTH)); cprintf( "%d", you.strength ); if (you.strength != you.max_strength) cprintf( " (%d) ", you.max_strength ); else cprintf( " " ); burden_change(); } static void _print_stats_int(int x, int y) { if (you.intel < 0) you.intel = 0; else if (you.intel > 72) you.intel = 72; if (you.max_intel > 72) you.max_intel = 72; cgotoxy(x+5, y, GOTO_STAT); textcolor(_get_stat_colour(STAT_INTELLIGENCE)); cprintf( "%d", you.intel ); if (you.intel != you.max_intel) cprintf( " (%d) ", you.max_intel ); else cprintf( " " ); } static void _print_stats_dex(int x, int y) { if (you.dex < 0) you.dex = 0; else if (you.dex > 72) you.dex = 72; if (you.max_dex > 72) you.max_dex = 72; cgotoxy(x+5, y, GOTO_STAT); textcolor(_get_stat_colour(STAT_DEXTERITY)); cprintf( "%d", you.dex ); if (you.dex != you.max_dex) cprintf( " (%d) ", you.max_dex ); else cprintf( " " ); } static void _print_stats_ac(int x, int y) { // AC: cgotoxy(x+4, y, GOTO_STAT); if (you.duration[DUR_STONEMAIL]) textcolor(_dur_colour( BLUE, dur_expiring(DUR_STONEMAIL) )); else if (you.duration[DUR_ICY_ARMOUR] || you.duration[DUR_STONESKIN]) textcolor( LIGHTBLUE ); else if (you.duration[DUR_ICEMAIL_DEPLETED] > ICEMAIL_TIME / ICEMAIL_MAX) textcolor( RED ); else textcolor( HUD_VALUE_COLOUR ); cprintf( "%2d ", you.armour_class() ); // SH: (two lines lower) cgotoxy(x+4, y+2, GOTO_STAT); if (you.duration[DUR_CONDENSATION_SHIELD] || you.duration[DUR_DIVINE_SHIELD]) textcolor( LIGHTBLUE ); else textcolor( HUD_VALUE_COLOUR ); cprintf( "%2d ", player_shield_class() ); } static void _print_stats_ev(int x, int y) { cgotoxy(x+4, y, GOTO_STAT); textcolor(you.duration[DUR_PHASE_SHIFT] ? LIGHTBLUE : HUD_VALUE_COLOUR); cprintf( "%2d ", player_evasion() ); } static void _print_stats_wp(int y) { int col; std::string text; if (you.weapon()) { const item_def& wpn = *you.weapon(); col = wpn.colour; const std::string prefix = menu_colour_item_prefix(wpn); const int prefcol = menu_colour(wpn.name(DESC_INVENTORY), prefix); if (prefcol != -1) col = prefcol; text = wpn.name(DESC_INVENTORY, true, false, true); } else if (you.attribute[ATTR_TRANSFORMATION] == TRAN_BLADE_HANDS) { col = RED; text = "Blade Hands"; } else { col = LIGHTGREY; text = "Nothing wielded"; } cgotoxy(1, y, GOTO_STAT); textcolor(Options.status_caption_colour); cprintf("Wp: "); textcolor(col); int w = crawl_view.hudsz.x - 4; cprintf("%-*s", w, text.substr(0, w).c_str()); textcolor(LIGHTGREY); } static void _print_stats_qv(int y) { cgotoxy(1, y, GOTO_STAT); textcolor(Options.status_caption_colour); cprintf("Qv: "); int q = you.m_quiver->get_fire_item(); ASSERT(q >= -1 && q < ENDOFPACK); if (q != -1) { const item_def& quiver = you.inv[q]; textcolor(quiver.colour); const std::string prefix = menu_colour_item_prefix(quiver); const int prefcol = menu_colour(quiver.name(DESC_INVENTORY), prefix); if (prefcol != -1) textcolor(prefcol); cprintf("%s", quiver.name(DESC_INVENTORY, true) .substr(0, crawl_view.hudsz.x - 4) .c_str()); } else { textcolor(LIGHTGREY); cprintf("Nothing quivered"); } textcolor(LIGHTGREY); clear_to_end_of_line(); } struct status_light { status_light(int c, const char* t) : color(c), text(t) {} int color; const char* text; }; // The colour scheme for these flags is currently: // // - yellow, "orange", red for bad conditions // - light grey, white for god based conditions // - green, light green for good conditions // - blue, light blue for good enchantments // - magenta, light magenta for "better" enchantments (deflect, fly) // // Prints burden, hunger, // pray, holy, teleport, regen, insulation, fly/lev, invis, silence, // conf. touch, bargain, sage // confused, mesmerised, fire, poison, disease, rot, held, glow, swift, // fast, slow, breath // // Note the usage of bad_ench_colour() correspond to levels that // can be found in player.cc, ie those that the player can tell by // using the '@' command. Things like confusion and sticky flame // hide their amounts and are thus always the same colour (so // we're not really exposing any new information). --bwr static void _get_status_lights(std::vector& out) { #if DEBUG_DIAGNOSTICS { static char static_pos_buf[80]; snprintf(static_pos_buf, sizeof(static_pos_buf), "%2d,%2d", you.pos().x, you.pos().y ); out.push_back(status_light(LIGHTGREY, static_pos_buf)); static char static_hunger_buf[80]; snprintf(static_hunger_buf, sizeof(static_hunger_buf), "(%d:%d)", you.hunger - you.old_hunger, you.hunger ); out.push_back(status_light(LIGHTGREY, static_hunger_buf)); } #endif switch (you.burden_state) { case BS_OVERLOADED: out.push_back(status_light(RED, "Overloaded")); break; case BS_ENCUMBERED: out.push_back(status_light(LIGHTRED, "Encumbered")); break; case BS_UNENCUMBERED: break; } { int hunger_color; const char* hunger_text = _describe_hunger(hunger_color); if (hunger_text) out.push_back(status_light(hunger_color, hunger_text)); } if (you.duration[DUR_PRAYER]) out.push_back(status_light(WHITE, "Pray")); // no end of effect warning if (you.duration[DUR_TELEPORT]) out.push_back(status_light(LIGHTBLUE, "Tele")); if (you.duration[DUR_DEFLECT_MISSILES]) { int color = _dur_colour( MAGENTA, dur_expiring(DUR_DEFLECT_MISSILES) ); out.push_back(status_light(color, "DMsl")); } else if (you.duration[DUR_REPEL_MISSILES]) { int color = _dur_colour( BLUE, dur_expiring(DUR_REPEL_MISSILES) ); out.push_back(status_light(color, "RMsl")); } if (you.duration[DUR_REGENERATION]) { int color = _dur_colour( BLUE, dur_expiring(DUR_REGENERATION) ); out.push_back(status_light(color, you.attribute[ATTR_DIVINE_REGENERATION] ? "Regen MR" : "Regen")); } if (you.duration[DUR_INSULATION]) { int color = _dur_colour( BLUE, dur_expiring(DUR_INSULATION) ); out.push_back(status_light(color, "Ins")); } if (you.airborne()) { const bool perm = you.permanent_flight(); const bool expiring = (!perm && dur_expiring(DUR_LEVITATION)); if (wearing_amulet( AMU_CONTROLLED_FLIGHT )) { int color = _dur_colour( you.light_flight()? BLUE : MAGENTA, expiring); out.push_back(status_light(color, "Fly")); } else { int color = _dur_colour(BLUE, expiring); out.push_back(status_light(color, "Lev")); } } if (you.duration[DUR_INVIS]) { int color = _dur_colour( BLUE, dur_expiring(DUR_INVIS) ); out.push_back(status_light(color, "Invis")); } if (you.duration[DUR_SILENCE]) { int color = _dur_colour( BLUE, dur_expiring(DUR_SILENCE) ); out.push_back(status_light(color, "Sil")); } if (you.duration[DUR_CONFUSING_TOUCH]) { int color = _dur_colour( BLUE, dur_expiring(DUR_CONFUSING_TOUCH) ); out.push_back(status_light(color, "Touch")); } if (you.duration[DUR_BARGAIN]) { int color = _dur_colour( BLUE, dur_expiring(DUR_BARGAIN) ); out.push_back(status_light(color, "Brgn")); } if (you.duration[DUR_SAGE]) { int color = _dur_colour( BLUE, dur_expiring(DUR_SAGE) ); out.push_back(status_light(color, "Sage")); } if (you.duration[DUR_FIRE_SHIELD]) { int color = _dur_colour( BLUE, dur_expiring(DUR_FIRE_SHIELD) ); out.push_back(status_light(color, "RoF")); } if (you.duration[DUR_SLIMIFY]) { int color = _dur_colour(GREEN, dur_expiring(DUR_SLIMIFY)); out.push_back(status_light(color, "Slime")); } if (you.duration[DUR_SURE_BLADE]) out.push_back(status_light(BLUE, "Blade")); if (you.confused()) out.push_back(status_light(RED, "Conf")); if (you.duration[DUR_LOWERED_MR]) out.push_back(status_light(RED, "-MR")); // TODO: Differentiate between mermaids and sirens! if (you.beheld()) out.push_back(status_light(RED, "Mesm")); if (you.duration[DUR_LIQUID_FLAMES]) out.push_back(status_light(RED, "Fire")); if (you.duration[DUR_MISLED]) out.push_back(status_light(LIGHTMAGENTA, "Misled")); if (you.duration[DUR_POISONING]) { int color = _bad_ench_colour( you.duration[DUR_POISONING], 5, 10 ); out.push_back(status_light(color, "Pois")); } if (you.disease) { int color = _bad_ench_colour( you.disease, 40 * BASELINE_DELAY, 120 * BASELINE_DELAY); out.push_back(status_light(color, "Sick")); } if (you.rotting) { int color = _bad_ench_colour( you.rotting, 4, 8 ); out.push_back(status_light(color, "Rot")); } if (you.attribute[ATTR_HELD]) out.push_back(status_light(RED, "Held")); if (you.magic_contamination || you.backlit(false)) { int colour = LIGHTBLUE; if (you.magic_contamination > 0) { colour = (you.magic_contamination > 5 ? _bad_ench_colour( you.magic_contamination, 15, 25 ) : DARKGREY); } out.push_back(status_light(colour, "Glow")); } if (you.duration[DUR_SWIFTNESS]) { int color = _dur_colour( BLUE, dur_expiring(DUR_SWIFTNESS) ); out.push_back(status_light(color, "Swift")); } if (you.duration[DUR_SLOW] && !you.duration[DUR_HASTE]) out.push_back(status_light(RED, "Slow")); else if (you.duration[DUR_HASTE] && !you.duration[DUR_SLOW]) { int color = _dur_colour( BLUE, dur_expiring(DUR_HASTE) ); out.push_back(status_light(color, "Fast")); } if (you.duration[DUR_BREATH_WEAPON]) out.push_back(status_light(YELLOW, "BWpn")); } static void _print_status_lights(int y) { std::vector lights; static int last_number_of_lights = 0; _get_status_lights(lights); if (lights.size() == 0 && last_number_of_lights == 0) return; last_number_of_lights = lights.size(); size_t line_cur = y; const size_t line_end = crawl_view.hudsz.y+1; cgotoxy(1, line_cur, GOTO_STAT); ASSERT(wherex()-crawl_view.hudp.x == 0); size_t i_light = 0; while (true) { const int end_x = (wherex() - crawl_view.hudp.x) + (i_light < lights.size() ? strlen(lights[i_light].text) : 10000); if (end_x <= crawl_view.hudsz.x) { textcolor(lights[i_light].color); cprintf("%s", lights[i_light].text); if (end_x < crawl_view.hudsz.x) cprintf(" "); ++i_light; } else { clear_to_end_of_line(); ++line_cur; // Careful not to trip the )#(*$ cgotoxy ASSERT if (line_cur == line_end) break; cgotoxy(1, line_cur, GOTO_STAT); } } } #ifdef USE_TILE static bool _need_stats_printed() { return you.redraw_hit_points || you.redraw_magic_points || you.redraw_armour_class || you.redraw_evasion || you.redraw_strength || you.redraw_intelligence || you.redraw_dexterity || you.redraw_experience || you.wield_change || you.redraw_quiver; } #endif static short _get_exp_pool_colour(int pool) { if (pool < MAX_EXP_POOL/2) return (HUD_VALUE_COLOUR); else if (pool < MAX_EXP_POOL*3/4) return (YELLOW); else return (RED); } void print_stats(void) { textcolor(LIGHTGREY); // Displayed evasion is now tied to dex. if (you.redraw_dexterity) you.redraw_evasion = true; if (HP_Bar.wants_redraw()) you.redraw_hit_points = true; if (MP_Bar.wants_redraw()) you.redraw_magic_points = true; #ifdef USE_TILE bool has_changed = _need_stats_printed(); #endif if (you.redraw_hit_points) { you.redraw_hit_points = false; _print_stats_hp ( 1, 3); } if (you.redraw_magic_points) { you.redraw_magic_points = false; _print_stats_mp ( 1, 4); } if (you.redraw_armour_class) { you.redraw_armour_class = false; _print_stats_ac ( 1, 5); } if (you.redraw_evasion) { you.redraw_evasion = false; _print_stats_ev ( 1, 6); } if (you.redraw_strength) { you.redraw_strength = false; _print_stats_str(19, 5); } if (you.redraw_intelligence) { you.redraw_intelligence = false; _print_stats_int(19, 6); } if (you.redraw_dexterity) { you.redraw_dexterity = false; _print_stats_dex(19, 7); } int yhack = 0; // If Options.show_gold_turns, line 8 is Gold and Turns if (Options.show_gold_turns) { // Increase y-value for all following lines. yhack = 1; cgotoxy(1+6, 8, GOTO_STAT); textcolor(HUD_VALUE_COLOUR); cprintf("%-6d", you.gold); } if (you.redraw_experience) { cgotoxy(1,8 + yhack, GOTO_STAT); textcolor(Options.status_caption_colour); #if DEBUG_DIAGNOSTICS cprintf("XP: "); textcolor(_get_exp_pool_colour(you.exp_available)); cprintf("%d/%d (%d) ", you.skill_cost_level, you.exp_available, you.experience); #else cprintf("Exp Pool: "); textcolor(_get_exp_pool_colour(you.exp_available)); cprintf("%-6d", you.exp_available); #endif you.redraw_experience = false; } if (you.wield_change) { // weapon_change is set in a billion places; probably not all // of them actually mean the user changed their weapon. Calling // on_weapon_changed redundantly is normally OK; but if the user // is wielding a bow and throwing javelins, the on_weapon_changed // will switch them back to arrows, which is annoying. // Perhaps there should be another bool besides wield_change // that's set in fewer places? // Also, it's a little bogus to change simulation state in // render code. We should find a better place for this. you.m_quiver->on_weapon_changed(); _print_stats_wp(9 + yhack); } if (you.redraw_quiver || you.wield_change) { _print_stats_qv(10 + yhack); you.redraw_quiver = false; } you.wield_change = false; if (you.redraw_status_flags) { you.redraw_status_flags = 0; _print_status_lights(11 + yhack); } textcolor(LIGHTGREY); #ifdef USE_TILE if (has_changed) update_screen(); #else update_screen(); #endif } static std::string _level_description_string_hud() { const PlaceInfo& place = you.get_place_info(); std::string short_name = place.short_name(); if (place.level_type == LEVEL_DUNGEON && branches[place.branch].depth > 1) { short_name += make_stringf(":%d", player_branch_depth()); } // Indefinite articles else if (place.level_type == LEVEL_PORTAL_VAULT || place.level_type == LEVEL_LABYRINTH) { if (!you.level_type_name.empty()) { // If the level name is faking a dungeon depth // (i.e., "Ziggurat:3") then don't add an article if (you.level_type_name.find(":") != std::string::npos) short_name = you.level_type_name; else short_name = article_a(upcase_first(you.level_type_name), false); } else short_name.insert(0, "A "); } // Definite articles else if (place.level_type == LEVEL_ABYSS) { short_name.insert(0, "The "); } return short_name; } // For some odd reason, only redrawn on level change. void print_stats_level() { int ypos = 8; if (Options.show_gold_turns) ypos++; cgotoxy(19, ypos, GOTO_STAT); textcolor(HUD_CAPTION_COLOUR); cprintf("Place: "); textcolor(HUD_VALUE_COLOUR); #if DEBUG_DIAGNOSTICS cprintf( "(%d) ", you.your_level + 1 ); #endif cprintf("%s", _level_description_string_hud().c_str()); clear_to_end_of_line(); } void redraw_skill(const std::string &your_name, const std::string &class_name) { std::string title = your_name + " the " + class_name; unsigned int in_len = title.length(); const unsigned int WIDTH = crawl_view.hudsz.x; if (in_len > WIDTH) { in_len -= 3; // What we're getting back from removing "the". const unsigned int name_len = your_name.length(); std::string trimmed_name = your_name; // Squeeze name if required, the "- 8" is to not squeeze too much. if (in_len > WIDTH && (name_len - 8) > (in_len - WIDTH)) { trimmed_name = trimmed_name.substr(0, name_len - (in_len - WIDTH) - 1); } title = trimmed_name + ", " + class_name; } // Line 1: Foo the Bar *WIZARD* cgotoxy(1, 1, GOTO_STAT); textcolor( YELLOW ); if (title.size() > WIDTH) title.resize(WIDTH, ' '); cprintf( "%-*s", WIDTH, title.c_str() ); if (you.wizard) { textcolor( LIGHTBLUE ); cgotoxy(1 + crawl_view.hudsz.x-9, 1, GOTO_STAT); cprintf(" *WIZARD*"); } #ifdef DGL_SIMPLE_MESSAGING update_message_status(); #endif // Line 2: // Level N Minotaur [of God] textcolor( YELLOW ); cgotoxy(1, 2, GOTO_STAT); nowrap_eol_cprintf("Level %d %s", you.experience_level, species_name(you.species,you.experience_level).c_str()); if (you.religion != GOD_NO_GOD) { nowrap_eol_cprintf(" of %s", you.religion == GOD_JIYVA ? god_name_jiyva(true).c_str() : god_name(you.religion).c_str()); } clear_to_end_of_line(); textcolor( LIGHTGREY ); } void draw_border(void) { textcolor(HUD_CAPTION_COLOUR); clrscr(); redraw_skill(you.your_name, player_title()); textcolor(Options.status_caption_colour); //cgotoxy( 1, 3, GOTO_STAT); cprintf("Hp:"); cgotoxy( 1, 4, GOTO_STAT); cprintf("Magic:"); cgotoxy( 1, 5, GOTO_STAT); cprintf("AC:"); cgotoxy( 1, 6, GOTO_STAT); cprintf("EV:"); cgotoxy( 1, 7, GOTO_STAT); cprintf("SH:"); cgotoxy(19, 5, GOTO_STAT); cprintf("Str:"); cgotoxy(19, 6, GOTO_STAT); cprintf("Int:"); cgotoxy(19, 7, GOTO_STAT); cprintf("Dex:"); if (Options.show_gold_turns) { cgotoxy( 1, 8, GOTO_STAT); cprintf("Gold:"); cgotoxy(19, 8, GOTO_STAT); cprintf("Turn:"); } // Line 9 (or 8) is exp pool, Level } // ---------------------------------------------------------------------- // Monster pane // ---------------------------------------------------------------------- static bool _mons_hostile(const monsters *mon) { return (!mon->friendly() && !mon->neutral()); } static std::string _get_monster_name(const monster_info& m, int count) { std::string desc = ""; const monsters *mon = m.m_mon; bool adj = false; if (mon->friendly()) { desc += "friendly "; adj = true; } else if (mon->neutral()) { desc += "neutral "; adj = true; } std::string monpane_desc; int col; m.to_string(count, monpane_desc, col); if (count == 1) { if (!mon->is_named()) { desc = ((!adj && is_vowel(monpane_desc[0])) ? "an " : "a ") + desc; } else if (adj) desc = "the " + desc; } desc += monpane_desc; return (desc); } // Returns true if the first monster is more aggressive (in terms of // hostile/neutral/friendly) than the second, or, if both monsters share the // same attitude, if the first monster has a lower type. // If monster type and attitude are the same, return false. bool compare_monsters_attitude( const monsters *m1, const monsters *m2 ) { if (_mons_hostile(m1) && !_mons_hostile(m2)) return (true); if (m1->neutral()) { if (m2->friendly()) return (true); if (_mons_hostile(m2)) return (false); } if (m1->friendly() && !m2->friendly()) return (false); // If we get here then monsters have the same attitude. // FIXME: replace with difficulty comparison return (m1->type < m2->type); } // If past is true, the messages should be printed in the past tense // because they're needed for the morgue dump. std::string mpr_monster_list(bool past) { // Get monsters via the monster_pane_info, sorted by difficulty. std::vector mons; get_monster_info(mons); std::string msg = ""; if (mons.empty()) { msg = "There "; msg += (past ? "were" : "are"); msg += " no monsters in sight!"; return (msg); } std::sort(mons.begin(), mons.end(), monster_info::less_than_wrapper); std::vector describe; int count = 0; for (unsigned int i = 0; i < mons.size(); ++i) { if (i > 0 && monster_info::less_than(mons[i-1], mons[i])) { describe.push_back(_get_monster_name(mons[i-1], count).c_str()); count = 0; } count++; } describe.push_back(_get_monster_name(mons[mons.size()-1], count).c_str()); msg = "You "; msg += (past ? "could" : "can"); msg += " see "; if (describe.size() == 1) msg += describe[0]; else msg += comma_separated_line(describe.begin(), describe.end()); msg += "."; return (msg); } #ifndef USE_TILE static char _mlist_index_to_letter(int index) { index += 'a'; if (index >= 'b') index++; if (index >= 'h') index++; if (index >= 'j') index++; if (index >= 'k') index++; if (index >= 'l') index++; return (index); } static void _print_next_monster_desc(const std::vector& mons, int& start, bool zombified = false, int idx = -1) { // Skip forward to past the end of the range of identical monsters. unsigned int end; for (end = start + 1; end < mons.size(); ++end) { // Array is sorted, so if !(m1 < m2), m1 and m2 are "equal". if (monster_info::less_than(mons[start], mons[end], zombified)) break; } // Postcondition: all monsters in [start, end) are "equal" // Print info on the monsters we've found. { int printed = 0; // for targetting if (idx >= 0) { textcolor(WHITE); cprintf(stringize_glyph(_mlist_index_to_letter(idx)).c_str()); cprintf(" - "); printed += 4; } // One glyph for each monster. for (unsigned int i_mon = start; i_mon < end; i_mon++) { monster_info mi = mons[i_mon]; textcolor(mi.m_glyph_colour); // XXX: Hack to make the death cob (%) show up correctly. if (mi.m_glyph == '%') cprintf("%%"); else cprintf(stringize_glyph(mi.m_glyph).c_str()); ++printed; // Printing too many looks pretty bad, though. if (i_mon > 6) break; } textcolor(LIGHTGREY); const int count = (end - start); if (count == 1) { // Print an "icon" representing damage level. monster_info mi = mons[start]; int dam_color; switch (mi.m_damage_level) { // NOTE: In os x, light versions of foreground colors are OK, // but not background colors. So stick wth standards. case MDAM_DEAD: case MDAM_ALMOST_DEAD: case MDAM_SEVERELY_DAMAGED: dam_color = RED; break; case MDAM_HEAVILY_DAMAGED: dam_color = MAGENTA; break; case MDAM_MODERATELY_DAMAGED: dam_color = BROWN; break; case MDAM_LIGHTLY_DAMAGED: dam_color = GREEN; break; case MDAM_OKAY: dam_color = GREEN; break; default: dam_color = CYAN; break; } cprintf(" "); textbackground(dam_color); textcolor(dam_color); // FIXME: On Windows, printing a blank space here // doesn't give us the correct colours. So use and // underscore instead. Is this a bug with our interface // or with Windows? cprintf("_"); textbackground(BLACK); textcolor(LIGHTGREY); cprintf(" "); printed += 3; } else { textcolor(LIGHTGREY); cprintf(" "); printed += 2; } if (printed < crawl_view.mlistsz.x) { int desc_color; std::string desc; mons[start].to_string(count, desc, desc_color); textcolor(desc_color); desc.resize(crawl_view.mlistsz.x-printed, ' '); cprintf("%s", desc.c_str()); } } // Set start to the next un-described monster. start = end; textcolor(LIGHTGREY); } #endif #ifndef USE_TILE #define BOTTOM_JUSTIFY_MONSTER_LIST 0 // Returns -1 if the monster list is empty, 0 if there are so many monsters // they have to be consolidated, and 1 otherwise. int update_monster_pane() { if (!map_bounds(you.pos()) && !crawl_state.arena) return (-1); const int max_print = crawl_view.mlistsz.y; textbackground(BLACK); if (max_print <= 0) return (-1); std::vector mons; get_monster_info(mons); std::sort(mons.begin(), mons.end(), monster_info::less_than_wrapper); // Count how many groups of monsters there are. unsigned int lines_needed = mons.size(); for (unsigned int i = 1; i < mons.size(); i++) if (!monster_info::less_than(mons[i-1], mons[i])) --lines_needed; bool full_info = true; if (lines_needed > (unsigned int) max_print) { full_info = false; // Use type names rather than full names ("small zombie" vs // "rat zombie") in order to take up fewer lines. for (unsigned int i = 0; i < mons.size(); i++) mons[i].m_fullname = false; std::sort(mons.begin(), mons.end(), monster_info::less_than_wrapper); lines_needed = mons.size(); for (unsigned int i = 1; i < mons.size(); i++) if (!monster_info::less_than(mons[i-1], mons[i], false)) --lines_needed; } #if BOTTOM_JUSTIFY_MONSTER_LIST const int skip_lines = std::max(0, crawl_view.mlistsz.y-lines_needed); #else const int skip_lines = 0; #endif // Print the monsters! std::string blank; blank.resize(crawl_view.mlistsz.x, ' '); int i_mons = 0; for (int i_print = 0; i_print < max_print; ++i_print) { cgotoxy(1, 1 + i_print, GOTO_MLIST); // i_mons is incremented by _print_next_monster_desc if (i_print >= skip_lines && i_mons < (int) mons.size()) { _print_next_monster_desc(mons, i_mons, full_info, crawl_state.mlist_targetting ? i_print : -1); } else cprintf("%s", blank.c_str()); } if (i_mons < (int)mons.size()) { // Didn't get to all of them. cgotoxy(crawl_view.mlistsz.x - 4, crawl_view.mlistsz.y, GOTO_MLIST); cprintf(" ... "); } if (mons.empty()) return (-1); return (full_info); } #else // FIXME: Implement this for Tiles! int update_monster_pane() { return (false); } #endif const char* itosym1(int stat) { return ( (stat >= 1) ? "+ " : ". " ); } const char* itosym3(int stat) { return ( (stat >= 3) ? "+ + +" : (stat == 2) ? "+ + ." : (stat == 1) ? "+ . ." : (stat == 0) ? ". . ." : (stat == -1) ? "x . ." : (stat == -2) ? "x x ." : "x x x"); } static const char *s_equip_slot_names[] = { "Weapon", "Cloak", "Helmet", "Gloves", "Boots", "Shield", "Armour", "Left Ring", "Right Ring", "Amulet", }; const char *equip_slot_to_name(int equip) { if (equip == EQ_RINGS || equip == EQ_LEFT_RING || equip == EQ_RIGHT_RING) return "Ring"; if (equip == EQ_BOOTS && (you.species == SP_CENTAUR || you.species == SP_NAGA)) { return "Barding"; } if (equip < 0 || equip >= NUM_EQUIP) return ""; return s_equip_slot_names[equip]; } int equip_name_to_slot(const char *s) { for (int i = 0; i < NUM_EQUIP; ++i) if (!stricmp(s_equip_slot_names[i], s)) return i; return -1; } // Colour the string according to the level of an ability/resistance. // Take maximum possible level into account. static const char* _determine_colour_string( int level, int max_level ) { switch (level) { case 3: case 2: if (max_level > 1) return ""; // else fall-through case 1: return ""; case -2: case -3: if (max_level > 1) return ""; // else fall-through case -1: return ""; default: return ""; } } static std::string _status_mut_abilities(void); // helper for print_overview_screen static void _print_overview_screen_equip(column_composer& cols, std::vector& equip_chars) { const int e_order[] = { EQ_WEAPON, EQ_BODY_ARMOUR, EQ_SHIELD, EQ_HELMET, EQ_CLOAK, EQ_GLOVES, EQ_BOOTS, EQ_AMULET, EQ_RIGHT_RING, EQ_LEFT_RING }; char buf[100]; for (int i = 0; i < NUM_EQUIP; i++) { int eqslot = e_order[i]; char slot_name_lwr[15]; snprintf(slot_name_lwr, sizeof slot_name_lwr, "%s", equip_slot_to_name(eqslot)); strlwr(slot_name_lwr); char slot[15] = ""; // uncomment (and change 42 to 33) to bring back slot names // snprintf(slot, sizeof slot, "%-7s: ", equip_slot_to_name(eqslot); if (you.equip[ e_order[i] ] != -1) { // The player has something equipped. const int item_idx = you.equip[e_order[i]]; const item_def& item = you.inv[item_idx]; const bool melded = !player_wearing_slot(e_order[i]); // Colour melded equipment dark grey. const char* colname = melded ? "darkgrey" : colour_to_str(item.colour).c_str(); const char equip_char = index_to_letter(item_idx); snprintf(buf, sizeof buf, "%s%c - <%s>%s%s", slot, equip_char, colname, melded ? "melded " : "", item.name(DESC_PLAIN, true).substr(0,42).c_str(), colname); equip_chars.push_back(equip_char); } else if (e_order[i] == EQ_WEAPON && you.skills[SK_UNARMED_COMBAT]) { snprintf(buf, sizeof buf, "%s - Unarmed", slot); } else if (e_order[i] == EQ_WEAPON && you.attribute[ATTR_TRANSFORMATION] == TRAN_BLADE_HANDS) { snprintf(buf, sizeof buf, "%s - Blade Hands", slot); } else if (e_order[i] == EQ_BOOTS && (you.species == SP_NAGA || you.species == SP_CENTAUR)) { snprintf(buf, sizeof buf, "(no %s)", slot_name_lwr); } else if (!you_can_wear(e_order[i], true)) { snprintf(buf, sizeof buf, "(%s unavailable)", slot_name_lwr); } else if (!you_tran_can_wear(e_order[i], true)) { snprintf(buf, sizeof buf, "(%s currently unavailable)", slot_name_lwr); } else if (!you_can_wear(e_order[i])) { snprintf(buf, sizeof buf, "(%s restricted)", slot_name_lwr); } else { snprintf(buf, sizeof buf, "(no %s)", slot_name_lwr); } cols.add_formatted(2, buf, false); } } static std::string _overview_screen_title() { char title[50]; snprintf(title, sizeof title, " the %s ", player_title().c_str()); char race_class[50]; snprintf(race_class, sizeof race_class, "(%s %s)", species_name(you.species, you.experience_level).c_str(), you.class_name); char time_turns[50] = ""; if (you.real_time != -1) { const time_t curr = you.real_time + (time(NULL) - you.start_time); snprintf(time_turns, sizeof time_turns, " Turns: %ld, Time: %s", you.num_turns, make_time_string(curr, true).c_str() ); } int linelength = you.your_name.length() + strlen(title) + strlen(race_class) + strlen(time_turns); for (int count = 0; linelength >= get_number_of_cols() && count < 2; count++) { switch (count) { case 0: snprintf(race_class, sizeof race_class, "(%s%s)", get_species_abbrev(you.species), get_class_abbrev(you.char_class) ); break; case 1: strcpy(title, ""); break; default: break; } linelength = you.your_name.length() + strlen(title) + strlen(race_class) + strlen(time_turns); } std::string text; text = ""; text += you.your_name; text += title; text += race_class; text += std::string(get_number_of_cols() - linelength - 1, ' '); text += time_turns; text += "\n"; return text; } static std::vector _get_overview_stats() { char buf[1000]; // 4 columns column_composer cols1(4, 18, 28, 40); const bool boosted_hp = you.duration[DUR_DIVINE_VIGOUR] || you.berserk(); const bool boosted_mp = you.duration[DUR_DIVINE_VIGOUR]; const bool boosted_str = you.duration[DUR_DIVINE_STAMINA] || you.duration[DUR_MIGHT]; const bool boosted_int = you.duration[DUR_DIVINE_STAMINA] || you.duration[DUR_BRILLIANCE]; const bool boosted_dex = you.duration[DUR_DIVINE_STAMINA] || you.duration[DUR_AGILITY]; if (!player_rotted()) { if (boosted_hp) { snprintf(buf, sizeof buf, "HP %3d/%d", you.hp, you.hp_max); } else snprintf(buf, sizeof buf, "HP %3d/%d", you.hp, you.hp_max); } else { if (boosted_hp) { snprintf(buf, sizeof buf, "HP %3d/%d (%d)", you.hp, you.hp_max, get_real_hp(true, true)); } else { snprintf(buf, sizeof buf, "HP %3d/%d (%d)", you.hp, you.hp_max, get_real_hp(true, true)); } } cols1.add_formatted(0, buf, false); if (boosted_mp) { snprintf(buf, sizeof buf, "MP %3d/%d", you.magic_points, you.max_magic_points); } else { snprintf(buf, sizeof buf, "MP %3d/%d", you.magic_points, you.max_magic_points); } cols1.add_formatted(0, buf, false); snprintf(buf, sizeof buf, "Gold %d", you.gold); cols1.add_formatted(0, buf, false); snprintf(buf, sizeof buf, "AC %2d" , you.armour_class()); cols1.add_formatted(1, buf, false); snprintf(buf, sizeof buf, "EV %2d" , player_evasion()); cols1.add_formatted(1, buf, false); snprintf(buf, sizeof buf, "SH %2d", player_shield_class()); cols1.add_formatted(1, buf, false); if (you.strength == you.max_strength) { if (boosted_str) { snprintf(buf, sizeof buf, "Str %2d", you.strength); } else snprintf(buf, sizeof buf, "Str %2d", you.strength); } else { if (boosted_str) { snprintf(buf, sizeof buf, "Str %2d (%d)", you.strength, you.max_strength); } else snprintf(buf, sizeof buf, "Str %2d (%d)", you.strength, you.max_strength); } cols1.add_formatted(2, buf, false); if (you.intel == you.max_intel) { if (boosted_int) { snprintf(buf, sizeof buf, "Int %2d", you.intel); } else snprintf(buf, sizeof buf, "Int %2d", you.intel); } else { if (boosted_int) { snprintf(buf, sizeof buf, "Int %2d (%d)", you.intel, you.max_intel); } else snprintf(buf, sizeof buf, "Int %2d (%d)", you.intel, you.max_intel); } cols1.add_formatted(2, buf, false); if (you.dex == you.max_dex) { if (boosted_dex) { snprintf(buf, sizeof buf, "Dex %2d", you.dex); } else snprintf(buf, sizeof buf, "Dex %2d", you.dex); } else { if (boosted_dex) { snprintf(buf, sizeof buf, "Dex %2d (%d)", you.dex, you.max_dex); } else snprintf(buf, sizeof buf, "Dex %2d (%d)", you.dex, you.max_dex); } cols1.add_formatted(2, buf, false); char god_colour_tag[20]; god_colour_tag[0] = 0; std::string godpowers(god_name(you.religion)); if (you.religion == GOD_XOM) { snprintf(god_colour_tag, sizeof god_colour_tag, "<%s>", colour_to_str(god_colour(you.religion)).c_str()); if (you.gift_timeout == 0) godpowers += " - BORED"; else if (you.gift_timeout == 1) godpowers += " - getting BORED"; } else if (you.religion != GOD_NO_GOD) { if (player_under_penance()) strcpy(god_colour_tag, "*"); else { snprintf(god_colour_tag, sizeof god_colour_tag, "<%s>", colour_to_str(god_colour(you.religion)).c_str()); // piety rankings int prank = piety_rank() - 1; if (prank < 0 || you.religion == GOD_XOM) prank = 0; // Careful about overflow. We erase some of the god's name // if necessary. godpowers = godpowers.substr(0, 20) + " [" + std::string(prank, '*') + std::string(6 - prank, '.') + "]"; } } int xp_needed = (exp_needed(you.experience_level + 2) - you.experience) + 1; snprintf(buf, sizeof buf, "Exp: %d/%lu (%d)%s\n" "God: %s%s\n" "Spells: %2d memorised, %2d level%s left\n", you.experience_level, you.experience, you.exp_available, (you.experience_level < 27? make_stringf(", need: %d", xp_needed).c_str() : ""), god_colour_tag, godpowers.c_str(), you.spell_no, player_spell_levels(), (player_spell_levels() == 1) ? "" : "s"); cols1.add_formatted(3, buf, false); return cols1.formatted_lines(); } static std::vector _get_overview_resistances( std::vector &equip_chars, bool calc_unid = false) { char buf[1000]; // 3 columns, splits at columns 21, 38 column_composer cols(3, 21, 38); const int rfire = player_res_fire(calc_unid); const int rcold = player_res_cold(calc_unid); const int rlife = player_prot_life(calc_unid); const int rpois = player_res_poison(calc_unid); const int relec = player_res_electricity(calc_unid); const int rspir = player_spirit_shield(calc_unid); const int rsust = player_sust_abil(calc_unid); const int rmuta = (wearing_amulet(AMU_RESIST_MUTATION, calc_unid) || player_mutation_level(MUT_MUTATION_RESISTANCE) == 3 || you.religion == GOD_ZIN && you.piety >= 150); const int rrott = (you.res_rotting() || you.religion == GOD_ZIN && you.piety >= 150); snprintf(buf, sizeof buf, "%sRes.Fire : %s\n" "%sRes.Cold : %s\n" "%sLife Prot.: %s\n" "%sRes.Poison: %s\n" "%sRes.Elec. : %s\n" "%sSpirit.Shd: %s\n" "%sSust.Abil.: %s\n" "%sRes.Mut. : %s\n" "%sRes.Rott. : %s\n", _determine_colour_string(rfire, 3), itosym3(rfire), _determine_colour_string(rcold, 3), itosym3(rcold), _determine_colour_string(rlife, 3), itosym3(rlife), _determine_colour_string(rpois, 1), itosym1(rpois), _determine_colour_string(relec, 1), itosym1(relec), _determine_colour_string(rspir, 1), itosym1(rspir), _determine_colour_string(rsust, 1), itosym1(rsust), _determine_colour_string(rmuta, 1), itosym1(rmuta), _determine_colour_string(rrott, 1), itosym1(rrott)); cols.add_formatted(0, buf, false); int saplevel = player_mutation_level(MUT_SAPROVOROUS); const char* pregourmand; const char* postgourmand; if (wearing_amulet(AMU_THE_GOURMAND, calc_unid)) { pregourmand = "Gourmand : "; postgourmand = itosym1(1); saplevel = 1; } else { pregourmand = "Saprovore : "; postgourmand = itosym3(saplevel); } snprintf(buf, sizeof buf, "%s%s%s", _determine_colour_string(saplevel, 3), pregourmand, postgourmand); cols.add_formatted(0, buf, false); const int rinvi = you.can_see_invisible(calc_unid); const int rward = wearing_amulet(AMU_WARDING, calc_unid); const int rcons = player_item_conserve(calc_unid); const int rcorr = player_res_acid(calc_unid); const int rclar = player_mental_clarity(calc_unid); snprintf(buf, sizeof buf, "%sSee Invis. : %s\n" "%sWarding : %s\n" "%sConserve : %s\n" "%sRes.Corr. : %s\n" "%sClarity : %s\n" "\n", _determine_colour_string(rinvi, 1), itosym1(rinvi), _determine_colour_string(rward, 1), itosym1(rward), _determine_colour_string(rcons, 1), itosym1(rcons), _determine_colour_string(rcorr, 1), itosym1(rcorr), _determine_colour_string(rclar, 1), itosym1(rclar)); cols.add_formatted(1, buf, false); if (scan_artefacts(ARTP_PREVENT_TELEPORTATION, calc_unid)) { snprintf(buf, sizeof buf, "\n%sPrev.Telep.: %s", _determine_colour_string(-1, 1), itosym1(1)); } else { const int rrtel = !!player_teleport(calc_unid); snprintf(buf, sizeof buf, "\n%sRnd.Telep. : %s", _determine_colour_string(rrtel, 1), itosym1(rrtel)); } cols.add_formatted(1, buf, false); const int rctel = player_control_teleport(calc_unid); const int rlevi = you.airborne(); const int rcfli = wearing_amulet(AMU_CONTROLLED_FLIGHT, calc_unid); snprintf(buf, sizeof buf, "%sCtrl.Telep.: %s\n" "%sLevitation : %s\n" "%sCtrl.Flight: %s\n", _determine_colour_string(rctel, 1), itosym1(rctel), _determine_colour_string(rlevi, 1), itosym1(rlevi), _determine_colour_string(rcfli, 1), itosym1(rcfli)); cols.add_formatted(1, buf, false); _print_overview_screen_equip(cols, equip_chars); return cols.formatted_lines(); } // New scrollable status overview screen, including stats, mutations etc. char _get_overview_screen_results() { bool calc_unid = false; formatted_scroller overview; // Set flags, and don't use easy exit. overview.set_flags(MF_SINGLESELECT | MF_ALWAYS_SHOW_MORE | MF_NOWRAP, false); overview.set_more( formatted_string::parse_string( #ifdef USE_TILE "[ +/L-click : Page down. - : Page up." " Esc/R-click exits.]")); #else "[ + : Page down. - : Page up." " Esc exits.]")); #endif overview.set_tag("resists"); overview.add_text(_overview_screen_title()); { std::vector blines = _get_overview_stats(); for (unsigned int i = 0; i < blines.size(); ++i ) overview.add_item_formatted_string(blines[i]); overview.add_text(" "); } { std::vector equip_chars; std::vector blines = _get_overview_resistances(equip_chars, calc_unid); for (unsigned int i = 0; i < blines.size(); ++i ) { // Kind of a hack -- we don't really care what items these // hotkeys go to. So just pick the first few. const char hotkey = (i < equip_chars.size()) ? equip_chars[i] : 0; overview.add_item_formatted_string(blines[i], hotkey); } } overview.add_text(" "); overview.add_text(_status_mut_abilities()); std::vector results = overview.show(); return (results.size() > 0) ? results[0]->hotkeys[0] : 0; } std::string dump_overview_screen(bool full_id) { std::string text = formatted_string::parse_string(_overview_screen_title()); text += EOL; std::vector blines = _get_overview_stats(); for (unsigned int i = 0; i < blines.size(); ++i) { text += blines[i]; text += EOL; } text += EOL; std::vector equip_chars; blines = _get_overview_resistances(equip_chars, full_id); for (unsigned int i = 0; i < blines.size(); ++i) { text += blines[i]; text += EOL; } text += EOL; text += formatted_string::parse_string(_status_mut_abilities()); text += EOL; return text; } void print_overview_screen() { while (true) { char c = _get_overview_screen_results(); if (!c) { redraw_screen(); break; } item_def& item = you.inv[letter_to_index(c)]; describe_item(item, true); // loop around for another go. } } std::string _get_expiration_string(duration_type dur, const char* msg) { std::string help = msg; if (dur_expiring(dur)) help += " (expiring)"; return (help); } std::string stealth_desc(int stealth) { std::string prefix = (stealth < 10) ? "extremely un" : (stealth < 30) ? "very un" : (stealth < 60) ? "un" : (stealth < 90) ? "fairly " : (stealth < 120) ? "" : (stealth < 160) ? "quite " : (stealth < 220) ? "very " : (stealth < 300) ? "extremely " : (stealth < 400) ? "extraordinarily " : (stealth < 520) ? "incredibly " : "uncannily "; return (prefix + "stealthy"); } std::string magic_res_adjective(int mr) { return ((mr < 10) ? "not" : (mr < 30) ? "slightly" : (mr < 60) ? "somewhat" : (mr < 90) ? "quite" : (mr < 120) ? "very" : (mr < 150) ? "extremely" : (mr < 190) ? "extraordinarily" : (mr < 240) ? "incredibly" : (mr < 300) ? "uncannily" : "almost entirely"); } // Creates rows of short descriptions for current // status, mutations and abilities. std::string _status_mut_abilities() { //---------------------------- // print status information //---------------------------- std::string text = "@: "; std::vector status; // These are not so unreasonable anymore now that the new overview screen // is dumped. When the player dies while paralysed it's important // information. If so, move them to the front. (jpeg) if (you.paralysed()) status.push_back("paralysed"); if (you.petrified()) status.push_back("petrified"); if (you.asleep()) status.push_back("sleeping"); if (you.burden_state == BS_ENCUMBERED) status.push_back("burdened"); else if (you.burden_state == BS_OVERLOADED) status.push_back("overloaded"); if (you.duration[DUR_BREATH_WEAPON]) status.push_back("short of breath"); // TODO: Differentiate between mermaids and sirens! if (you.beheld()) status.push_back("mesmerised"); if (you.duration[DUR_LIQUID_FLAMES]) status.push_back("liquid flames"); if (you.duration[DUR_ICY_ARMOUR]) status.push_back(_get_expiration_string(DUR_ICY_ARMOUR, "icy armour")); if (you.duration[DUR_DEFLECT_MISSILES]) { status.push_back(_get_expiration_string(DUR_DEFLECT_MISSILES, "deflect missiles")); } else if (you.duration[DUR_REPEL_MISSILES]) { status.push_back(_get_expiration_string(DUR_REPEL_MISSILES, "repel missiles")); } if (you.duration[DUR_PRAYER]) status.push_back("praying"); if (you.disease && !you.duration[DUR_REGENERATION] || you.species == SP_VAMPIRE && you.hunger_state == HS_STARVING || player_mutation_level(MUT_SLOW_HEALING) == 3) { status.push_back("non-regenerating"); } else if (you.duration[DUR_REGENERATION] || you.species == SP_VAMPIRE && you.hunger_state != HS_SATIATED) { std::string help; if (you.disease) help = "recuperating"; else help = "regenerating"; if (you.species == SP_VAMPIRE && you.hunger_state != HS_SATIATED) { if (you.hunger_state < HS_SATIATED) help += " slowly"; else if (you.hunger_state >= HS_FULL) help += " quickly"; else if (you.hunger_state == HS_ENGORGED) help += " very quickly"; } if (dur_expiring(DUR_REGENERATION)) help += " (expires)"; status.push_back(help); } if (you.duration[DUR_STONEMAIL]) { status.push_back(_get_expiration_string(DUR_STONEMAIL, "stone mail")); } if (you.duration[DUR_STONESKIN]) status.push_back("stone skin"); if (you.duration[DUR_TELEPORT]) status.push_back("about to teleport"); if (you.duration[DUR_DEATH_CHANNEL]) { status.push_back(_get_expiration_string(DUR_DEATH_CHANNEL, "death channel")); } if (you.duration[DUR_PHASE_SHIFT]) status.push_back(_get_expiration_string(DUR_PHASE_SHIFT, "phasing")); if (you.duration[DUR_SILENCE]) status.push_back(_get_expiration_string(DUR_SILENCE, "silence")); if (you.duration[DUR_INVIS]) status.push_back(_get_expiration_string(DUR_INVIS, "invisible")); if (you.confused()) status.push_back("confused"); if (you.duration[DUR_EXHAUSTED]) status.push_back("exhausted"); if (you.duration[DUR_MIGHT]) status.push_back("mighty"); if (you.duration[DUR_BRILLIANCE]) status.push_back("brilliance"); if (you.duration[DUR_AGILITY]) status.push_back("agile"); if (you.duration[DUR_DIVINE_VIGOUR]) status.push_back("divinely vigorous"); if (you.duration[DUR_DIVINE_STAMINA]) status.push_back("divinely fortified"); if (you.berserk()) status.push_back("berserking"); if (you.airborne()) { std::string help; if (wearing_amulet( AMU_CONTROLLED_FLIGHT )) help += "flying"; else help += "levitating"; if (dur_expiring(DUR_LEVITATION) && !you.permanent_flight()) help += " (expires)"; status.push_back(help); } if (you.duration[DUR_BARGAIN]) status.push_back(_get_expiration_string(DUR_BARGAIN, "charismatic")); if (you.duration[DUR_SLAYING]) status.push_back("deadly"); // DUR_STEALTHY handled in stealth printout if (you.duration[DUR_SAGE]) { std::string help = "studying "; help += skill_name(you.sage_bonus_skill); status.push_back(_get_expiration_string(DUR_SAGE, help.c_str())); } if (you.duration[DUR_MAGIC_SHIELD]) status.push_back("shielded"); if (you.duration[DUR_FIRE_SHIELD]) { status.push_back(_get_expiration_string(DUR_FIRE_SHIELD, "immune to fire clouds")); } if (you.duration[DUR_POISONING]) { std::string help = (you.duration[DUR_POISONING] > 10) ? "extremely" : (you.duration[DUR_POISONING] > 5) ? "very" : (you.duration[DUR_POISONING] > 3) ? "quite" : "mildly"; help += " poisoned"; status.push_back(help); } if (you.disease) { int high = 120 * BASELINE_DELAY; int low = 40 * BASELINE_DELAY; std::string help = (you.disease > high) ? "badly " : (you.disease > low) ? "" : "mildly "; help += "diseased"; status.push_back(help); } if (you.magic_contamination > 0) { const int cont = get_contamination_level(); snprintf(info, INFO_SIZE, "%sglowing", (cont == 0) ? "very slightly " : (cont == 1) ? "slightly " : (cont == 2) ? "" : (cont == 3) ? "moderately " : (cont == 4) ? "heavily " : "really heavily "); status.push_back(info); } if (you.rotting || you.species == SP_GHOUL) status.push_back("rotting"); if (you.duration[DUR_CONFUSING_TOUCH]) { status.push_back(_get_expiration_string(DUR_CONFUSING_TOUCH, "confusing touch")); } if (you.duration[DUR_SLIMIFY]) status.push_back(_get_expiration_string(DUR_SLIMIFY, "slimy")); if (you.duration[DUR_SURE_BLADE]) status.push_back("bonded with blade"); int move_cost = (player_speed() * player_movement_speed()) / 10; if (move_cost != 10) { std::string help = (move_cost < 8) ? "very quick" : (move_cost < 10) ? "quick" : (move_cost < 13) ? "slow" : "very slow"; status.push_back(help); } if (you.duration[DUR_SLOW] && !you.duration[DUR_HASTE]) status.push_back("slowed"); else if (you.duration[DUR_HASTE] && !you.duration[DUR_SLOW]) status.push_back(_get_expiration_string(DUR_HASTE, "hasted")); else if (!you.duration[DUR_HASTE] && you.duration[DUR_SWIFTNESS]) status.push_back("swift"); if (you.attribute[ATTR_HELD]) status.push_back("held"); status.push_back(magic_res_adjective(you.res_magic()) + " resistant to magic"); // character evaluates their ability to sneak around: status.push_back(stealth_desc(check_stealth())); text += comma_separated_line(status.begin(), status.end(), ", ", ", "); if (you.duration[DUR_TRANSFORMATION]) { switch (you.attribute[ATTR_TRANSFORMATION]) { case TRAN_SPIDER: text += "\nYou are in spider-form."; break; case TRAN_BAT: text += "\nYou are in "; if (you.species == SP_VAMPIRE) text += "vampire "; text += "bat-form."; break; case TRAN_BLADE_HANDS: text += "\nYou have blades for hands."; break; case TRAN_STATUE: text += "\nYou are a statue."; break; case TRAN_ICE_BEAST: text += "\nYou are an ice creature."; break; case TRAN_DRAGON: text += "\nYou are in dragon-form."; break; case TRAN_LICH: text += "\nYou are in lich-form."; break; case TRAN_PIG: text += "\nYou are a filthy swine."; break; } if ((you.species != SP_VAMPIRE || you.attribute[ATTR_TRANSFORMATION] != TRAN_BAT) && dur_expiring(DUR_TRANSFORMATION)) { text += " (Expiring.)"; } } /* // Commenting out until this information is actually meaningful. (jpeg) const int to_hit = calc_your_to_hit( false ) * 2; snprintf( info, INFO_SIZE, "\n%s in your current equipment.", (to_hit < 1) ? "You are completely incapable of fighting" : (to_hit < 5) ? "Hitting even clumsy monsters is extremely awkward" : (to_hit < 10) ? "Hitting average monsters is awkward" : (to_hit < 15) ? "Hitting average monsters is difficult" : (to_hit < 20) ? "Hitting average monsters is hard" : (to_hit < 30) ? "Very agile monsters are a bit awkward to hit" : (to_hit < 45) ? "Very agile monsters are a bit difficult to hit" : (to_hit < 60) ? "Very agile monsters are a bit hard to hit" : (to_hit < 100) ? "You feel comfortable with your ability to fight" : "You feel confident with your ability to fight" ); text += info; */ if (you.duration[DUR_DEATHS_DOOR]) text += "\nYou are standing in death's doorway."; //---------------------------- // print mutation information //---------------------------- text += "\nA: "; int AC_change = 0; int EV_change = 0; int Str_change = 0; int Int_change = 0; int Dex_change = 0; std::vector mutations; switch (you.species) //mv: following code shows innate abilities - if any { case SP_MERFOLK: mutations.push_back("change form in water"); break; case SP_NAGA: // breathe poison replaces spit poison: if (!player_mutation_level(MUT_BREATHE_POISON)) mutations.push_back("spit poison"); else mutations.push_back("breathe poison"); break; case SP_KENKU: if (you.experience_level > 4) { std::string help = "able to fly"; if (you.experience_level > 14) help += " continuously"; mutations.push_back(help); } break; case SP_VAMPIRE: if (you.experience_level < 13 || you.hunger_state >= HS_SATIATED) break; // else fall-through case SP_MUMMY: mutations.push_back("in touch with death"); break; case SP_GREY_DRACONIAN: if (you.experience_level > 6) mutations.push_back("spiky tail"); break; case SP_GREEN_DRACONIAN: if (you.experience_level > 6) mutations.push_back("breathe poison"); break; case SP_RED_DRACONIAN: if (you.experience_level > 6) mutations.push_back("breathe fire"); break; case SP_WHITE_DRACONIAN: if (you.experience_level > 6) mutations.push_back("breathe frost"); break; case SP_BLACK_DRACONIAN: if (you.experience_level > 6) mutations.push_back("breathe lightning"); break; case SP_YELLOW_DRACONIAN: if (you.experience_level > 6) { mutations.push_back("spit acid"); mutations.push_back("acid resistance"); } break; case SP_PURPLE_DRACONIAN: if (you.experience_level > 6) mutations.push_back("breathe power"); break; case SP_MOTTLED_DRACONIAN: if (you.experience_level > 6) mutations.push_back("breathe sticky flames"); break; case SP_PALE_DRACONIAN: if (you.experience_level > 6) mutations.push_back("breathe steam"); break; default: break; } //end switch - innate abilities // a bit more stuff if (player_genus(GENPC_OGRE) || you.species == SP_TROLL || player_genus(GENPC_DRACONIAN) || you.species == SP_SPRIGGAN) { mutations.push_back("unfitting armour"); } if (beogh_water_walk()) mutations.push_back("water walking"); std::string current; for (unsigned i = 0; i < NUM_MUTATIONS; ++i) { int level = player_mutation_level((mutation_type) i); if (!level) continue; current = ""; bool lowered = (level < you.mutation[i]); switch (i) { case MUT_TOUGH_SKIN: AC_change += level; break; case MUT_STRONG: Str_change += level; break; case MUT_CLEVER: Int_change += level; break; case MUT_AGILE: Dex_change += level; break; case MUT_GREEN_SCALES: AC_change += 2*level-1; break; case MUT_BLACK_SCALES: AC_change += 3*level; Dex_change -= level; break; case MUT_GREY_SCALES: AC_change += level; break; case MUT_BONEY_PLATES: AC_change += level+1; Dex_change -= level; break; case MUT_REPULSION_FIELD: EV_change += 2*level-1; if (level == 3) current = "repel missiles"; break; case MUT_POISON_RESISTANCE: current = "poison resistance"; break; case MUT_SAPROVOROUS: snprintf(info, INFO_SIZE, "saprovore %d", level); current = info; break; case MUT_GOURMAND: current = "gourmand"; break; case MUT_CARNIVOROUS: snprintf(info, INFO_SIZE, "carnivore %d", level); current = info; break; case MUT_HERBIVOROUS: snprintf(info, INFO_SIZE, "herbivore %d", level); current = info; break; case MUT_HEAT_RESISTANCE: snprintf(info, INFO_SIZE, "fire resistance %d", level); current = info; break; case MUT_COLD_RESISTANCE: snprintf(info, INFO_SIZE, "cold resistance %d", level); current = info; break; case MUT_SHOCK_RESISTANCE: current = "electricity resistance"; break; case MUT_REGENERATION: snprintf(info, INFO_SIZE, "regeneration %d", level); current = info; break; case MUT_SLOW_HEALING: snprintf(info, INFO_SIZE, "slow healing %d", level); current = info; break; case MUT_FAST_METABOLISM: snprintf(info, INFO_SIZE, "fast metabolism %d", level); current = info; break; case MUT_SLOW_METABOLISM: snprintf(info, INFO_SIZE, "slow metabolism %d", level); current = info; break; case MUT_WEAK: Str_change -= level; break; case MUT_DOPEY: Int_change -= level; break; case MUT_CLUMSY: Dex_change -= level; break; case MUT_TELEPORT_CONTROL: current = "teleport control"; break; case MUT_TELEPORT: snprintf(info, INFO_SIZE, "teleportitis %d", level); current = info; break; case MUT_MAGIC_RESISTANCE: snprintf(info, INFO_SIZE, "magic resistance %d", level); current = info; break; case MUT_FAST: snprintf(info, INFO_SIZE, "speed %d", level); current = info; break; case MUT_ACUTE_VISION: current = "see invisible"; break; case MUT_DEFORMED: snprintf(info, INFO_SIZE, "deformed body %d", level); current = info; break; case MUT_TELEPORT_AT_WILL: snprintf(info, INFO_SIZE, "teleport at will %d", level); current = info; break; case MUT_SPIT_POISON: snprintf(info, INFO_SIZE, "spit poison %d", level); current = info; break; case MUT_PASSIVE_MAPPING: snprintf(info, INFO_SIZE, "sense surroundings %d", level); current = info; break; case MUT_BREATHE_FLAMES: snprintf(info, INFO_SIZE, "breathe flames %d", level); current = info; break; case MUT_BLINK: current = "blink"; break; case MUT_HORNS: snprintf(info, INFO_SIZE, "horns %d", level); current = info; break; case MUT_BEAK: current = "beak"; break; case MUT_STRONG_STIFF: Str_change += level; Dex_change -= level; break; case MUT_FLEXIBLE_WEAK: Str_change -= level; Dex_change += level; break; case MUT_SCREAM: snprintf(info, INFO_SIZE, "screaming %d", level); current = info; break; case MUT_CLARITY: current = "clarity"; break; case MUT_BERSERK: snprintf(info, INFO_SIZE, "berserk %d", level); current = info; break; case MUT_DETERIORATION: snprintf(info, INFO_SIZE, "deterioration %d", level); current = info; break; case MUT_BLURRY_VISION: snprintf(info, INFO_SIZE, "blurry vision %d", level); current = info; break; case MUT_MUTATION_RESISTANCE: snprintf(info, INFO_SIZE, "mutation resistance %d", level); current = info; break; case MUT_FRAIL: snprintf(info, INFO_SIZE, "-%d%% hp", level*10); current = info; break; case MUT_ROBUST: snprintf(info, INFO_SIZE, "+%d%% hp", level*10); current = info; break; case MUT_LOW_MAGIC: snprintf(info, INFO_SIZE, "-%d%% mp", level*10); current = info; break; case MUT_HIGH_MAGIC: snprintf(info, INFO_SIZE, "+%d%% mp", level*10); current = info; break; // demonspawn mutations case MUT_TORMENT_RESISTANCE: current = "torment resistance"; break; case MUT_STOCHASTIC_TORMENT_RESISTANCE: snprintf(info, INFO_SIZE, "%d%% torment resistance", level*20); current = info; break; case MUT_NEGATIVE_ENERGY_RESISTANCE: snprintf(info, INFO_SIZE, "life protection %d", level); current = info; break; case MUT_SUMMON_MINOR_DEMONS: current = "summon minor demons"; break; case MUT_SUMMON_DEMONS: current = "summon demons"; break; case MUT_HURL_HELLFIRE: current = "hurl hellfire"; break; case MUT_CALL_TORMENT: current = "call torment"; break; case MUT_RAISE_DEAD: current = "raise dead"; break; case MUT_CONTROL_DEMONS: current = "control demons"; break; case MUT_DEATH_STRENGTH: current = "draw strength from death and destruction"; break; case MUT_CHANNEL_HELL: current = "channel magical energy from Hell"; break; case MUT_DRAIN_LIFE: current = "drain life"; break; case MUT_THROW_FLAMES: current = "throw flames of Gehenna"; break; case MUT_THROW_FROST: current = "throw frost of Cocytus"; break; case MUT_SMITE: current = "invoke powers of Tartarus"; break; // end of demonspawn mutations case MUT_CLAWS: snprintf(info, INFO_SIZE, "claws %d", level); current = info; break; case MUT_FANGS: snprintf(info, INFO_SIZE, "sharp teeth %d", level); current = info; break; case MUT_HOOVES: current = "hooves"; break; case MUT_TALONS: current = "talons"; break; case MUT_BREATHE_POISON: current = "breathe poison"; break; case MUT_STINGER: snprintf(info, INFO_SIZE, "stinger %d", level); current = info; break; case MUT_BIG_WINGS: current = "large and strong wings"; break; // scales etc. -> calculate sum of AC bonus case MUT_RED_SCALES: AC_change += level; if (level == 3) AC_change++; break; case MUT_NACREOUS_SCALES: AC_change += 2*level-1; break; case MUT_GREY2_SCALES: AC_change += 2*level; Dex_change -= 1; if (level == 3) Dex_change--; break; case MUT_METALLIC_SCALES: AC_change += 3*level+1; if (level == 1) AC_change--; Dex_change -= level + 1; break; case MUT_BLACK2_SCALES: AC_change += 2*level-1; break; case MUT_WHITE_SCALES: AC_change += 2*level-1; break; case MUT_YELLOW_SCALES: AC_change += 2*level; Dex_change -= level-1; break; case MUT_BROWN_SCALES: AC_change += 2*level; if (level == 3) AC_change--; break; case MUT_BLUE_SCALES: AC_change += level; break; case MUT_PURPLE_SCALES: AC_change += 2*level; break; case MUT_SPECKLED_SCALES: AC_change += level; break; case MUT_ORANGE_SCALES: AC_change += level; if (level > 1) AC_change++; break; case MUT_INDIGO_SCALES: AC_change += 2*level-1; if (level == 1) AC_change++; break; case MUT_RED2_SCALES: AC_change += 2*level; if (level > 1) AC_change++; Dex_change -= level - 1; break; case MUT_IRIDESCENT_SCALES: AC_change += level; break; case MUT_PATTERNED_SCALES: AC_change += level; break; case MUT_SHAGGY_FUR: AC_change += level; if (level == 3) current = "shaggy fur"; break; default: break; } if (!current.empty()) { if (lowered) current = "" + current + ""; mutations.push_back(current); } } if (AC_change) { snprintf(info, INFO_SIZE, "AC %s%d", (AC_change > 0 ? "+" : ""), AC_change); mutations.push_back(info); } if (EV_change) { snprintf(info, INFO_SIZE, "EV +%d", EV_change); mutations.push_back(info); } if (Str_change) { snprintf(info, INFO_SIZE, "Str %s%d", (Str_change > 0 ? "+" : ""), Str_change); mutations.push_back(info); } if (Int_change) { snprintf(info, INFO_SIZE, "Int %s%d", (Int_change > 0 ? "+" : ""), Int_change); mutations.push_back(info); } if (Dex_change) { snprintf(info, INFO_SIZE, "Dex %s%d", (Dex_change > 0 ? "+" : ""), Dex_change); mutations.push_back(info); } if (mutations.empty()) text += "no striking features"; else { text += comma_separated_line(mutations.begin(), mutations.end(), ", ", ", "); } //---------------------------- // print ability information //---------------------------- text += print_abilities(); linebreak_string2(text, get_number_of_cols()); return text; }