diff --git a/doc/fixes37.0 b/doc/fixes37.0 index c068816be..d56d5c7b6 100644 --- a/doc/fixes37.0 +++ b/doc/fixes37.0 @@ -1,4 +1,4 @@ -NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.338 $ $NHDT-Date: 1603509297 2020/10/24 03:14:57 $ +NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.341 $ $NHDT-Date: 1603666043 2020/10/25 22:47:23 $ General Fixes and Modified Features ----------------------------------- @@ -600,6 +600,7 @@ Qt: clicking on the paper doll runs the #seeall command (inventory of wielded words, same set of things whose tiles are used to populate the doll) Qt: clicking on the status window runs the #attributes command (^X) Qt: add a Search button to the toolbar +Qt: support the 'hitpointbar' option NetHack Community Patches (or Variation) Included diff --git a/src/options.c b/src/options.c index 39e69c9f1..f00907912 100644 --- a/src/options.c +++ b/src/options.c @@ -1,4 +1,4 @@ -/* NetHack 3.7 options.c $NHDT-Date: 1599893947 2020/09/12 06:59:07 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.473 $ */ +/* NetHack 3.7 options.c $NHDT-Date: 1603666043 2020/10/25 22:47:23 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.478 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Michael Allison, 2008. */ /* NetHack may be freely redistributed. See license for details. */ @@ -4607,6 +4607,12 @@ char *op; /* [is reassessment really needed here?] */ status_initialize(REASSESS_ONLY); g.opt_need_redraw = TRUE; +#ifdef QT_GRAPHICS + } else if (WINDOWPORT("Qt")) { + /* Qt doesn't support HILITE_STATUS or FLUSH_STATUS so fails + VIA_WINDOWPORT(), but it does support WC2_HITPOINTBAR */ + g.context.botlx = TRUE; +#endif } break; case opt_color: @@ -7619,7 +7625,8 @@ doset() /* changing options via menu by Per Liboriussen */ check_gold_symbol(); reglyph_darkroom(); (void) doredraw(); - } else if (g.context.botl || g.context.botlx) { + } + if (g.context.botl || g.context.botlx) { bot(); } return 0; diff --git a/win/Qt/qt_bind.cpp b/win/Qt/qt_bind.cpp index 5fb65f980..b31465ad5 100644 --- a/win/Qt/qt_bind.cpp +++ b/win/Qt/qt_bind.cpp @@ -931,12 +931,11 @@ static void Qt_positionbar(char *) {} struct window_procs Qt_procs = { "Qt", - WC_COLOR | WC_HILITE_PET - | WC_ASCII_MAP | WC_TILED_MAP - | WC_FONT_MAP | WC_TILE_FILE | WC_TILE_WIDTH | WC_TILE_HEIGHT - | WC_POPUP_DIALOG - | WC_PLAYER_SELECTION | WC_SPLASH_SCREEN, - 0L, + (WC_COLOR | WC_HILITE_PET + | WC_ASCII_MAP | WC_TILED_MAP + | WC_FONT_MAP | WC_TILE_FILE | WC_TILE_WIDTH | WC_TILE_HEIGHT + | WC_POPUP_DIALOG | WC_PLAYER_SELECTION | WC_SPLASH_SCREEN), + (WC2_HITPOINTBAR), {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, /* color availability */ nethack_qt_::NetHackQtBind::qt_init_nhwindows, nethack_qt_::NetHackQtBind::qt_player_selection, diff --git a/win/Qt/qt_stat.cpp b/win/Qt/qt_stat.cpp index 02fb15772..cfccb439d 100644 --- a/win/Qt/qt_stat.cpp +++ b/win/Qt/qt_stat.cpp @@ -2,7 +2,7 @@ // Qt4 conversion copyright (c) Ray Chason, 2012-2014. // NetHack may be freely redistributed. See license for details. -// qt_stat.cpp -- bindings between the Qt 4 interface and the main code +// qt_stat.cpp -- status window, upper right portion of the overall window extern "C" { #include "hack.h" @@ -26,10 +26,6 @@ extern const char *hu_stat[]; /* from eat.c */ namespace nethack_qt_ { NetHackQtStatusWindow::NetHackQtStatusWindow() : - // Notes: - // Alignment needs -2 init value, because -1 is an alignment. - // Armor Class is an schar, so 256 is out of range. - // Blank value is 0 and should never change. name(this,"(name)"), dlevel(this,"(dlevel)"), str(this, "Str"), @@ -62,10 +58,14 @@ NetHackQtStatusWindow::NetHackQtStatusWindow() : lev(this,"Lev"), fly(this,"Fly"), ride(this,"Ride"), + hpbar_health(this), + hpbar_injury(this), hline1(this), hline2(this), hline3(this), - first_set(true) + cursy(0), + first_set(true), + alreadyfullhp(false) { p_str = QPixmap(str_xpm); p_str = QPixmap(str_xpm); @@ -127,6 +127,7 @@ NetHackQtStatusWindow::NetHackQtStatusWindow() : fly.setIcon(p_fly); ride.setIcon(p_ride); + // separator lines hline1.setFrameStyle(QFrame::HLine|QFrame::Sunken); hline2.setFrameStyle(QFrame::HLine|QFrame::Sunken); hline3.setFrameStyle(QFrame::HLine|QFrame::Sunken); @@ -134,11 +135,14 @@ NetHackQtStatusWindow::NetHackQtStatusWindow() : hline2.setLineWidth(1); hline3.setLineWidth(1); + QHBoxLayout *hpbar = InitHitpointBar(); + #if 1 //RLC name.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); dlevel.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); QVBoxLayout *vbox = new QVBoxLayout(); vbox->setSpacing(0); + vbox->addLayout(hpbar); vbox->addWidget(&name); vbox->addWidget(&dlevel); vbox->addWidget(&hline1); @@ -390,6 +394,130 @@ void NetHackQtStatusWindow::fadeHighlighting() ride.dissipateHighlight(); } +// hitpointbar: two panels: left==current health, right==missing max health +QHBoxLayout *NetHackQtStatusWindow::InitHitpointBar() +{ + hpbar_health.setFrameStyle(QFrame::NoFrame); + hpbar_health.setMaximumHeight(9); + hpbar_health.setAutoFillBackground(true); + if (!iflags.wc2_hitpointbar) + hpbar_health.hide(); + + hpbar_injury.setFrameStyle(QFrame::NoFrame); + /* health portion has thickness 9, injury portion just 3 */ + hpbar_injury.setMaximumHeight(3); + hpbar_injury.setContentsMargins(0, 3, 0, 3); // left,top,right,bottom + hpbar_injury.setAutoFillBackground(true); + hpbar_injury.hide(); // only shown when hitpointbar is On and uhp < uhpmax + + QHBoxLayout *hpbar = new QHBoxLayout; + hpbar->setSpacing(0); + hpbar->setMargin(0); + hpbar->addWidget(&hpbar_health); + hpbar->setAlignment(&hpbar_health, Qt::AlignLeft); + hpbar->addWidget(&hpbar_injury); + hpbar->setAlignment(&hpbar_injury, Qt::AlignRight); + return hpbar; // caller will add our result to vbox layout +} + +// when hitpoint bar is enabled, calculate and draw it, otherwise remove it +void NetHackQtStatusWindow::HitpointBar() +{ + // a style sheet is used to specify color for otherwise blank labels; + // barcolors[][*]: column [0=left] is current health, [1=right] is injury + static const char + *styleformat = "QLabel { background-color : %s ; color : transparent ;" + " min-width : %d ; max-width %d }", + *barcolors[6][2] = { + { "black", "black" }, // 100% /* second black never shown */ + { "blue", "darkBlue" }, //75..99 + // gray is darker than darkGray for some reason (at least on OSX)... + // green and orange would look better if they were lighter/brighter + { "green", "gray" }, //50..74 + { "yellow", "darkGray" }, //25..49 + { "#ff7f00", "lightGray" }, //10..24 /* #ff7f00=="orange" */ + { "red", "white" }, // 0..9 + }; + + /* + * tty and curses use inverse video characters in the left portion + * of the name+rank string to reflect hero's health. We draw a + * separate line above the name+rank field instead. The left side + * of the line indicates current health. The right side is only + * shown when injured and indicates missing amount of maximum health. + */ + if (iflags.wc2_hitpointbar) { + int colorindx, w, + ihp = Upolyd ? u.mh : u.uhp, + ihpmax = Upolyd ? u.mhmax : u.uhpmax; + ihp = std::max(std::min(ihp, ihpmax), 0); + int pct = 100 * ihp / ihpmax, + lox = hline1.x(), + hix = lox + hline1.width() - 1; + QRect geoH = hpbar_health.geometry(), + geoI = hpbar_injury.geometry(); + QString styleH, styleI; + + if (ihp < ihpmax) { + // health is less than full; + // use red for extreme low health even if the percentage is + // above the usual threshold (which will happen when maximum + // health is very low); do a similar threshold override for + // orange even though it can be distracting for low level hero + colorindx = (pct < 10 || ihp < 5) ? 5 // red | white + : (pct < 25 || ihp < 10 ) ? 4 // orange | lightGray + : (pct < 50) ? 3 // yellow | darkGray* + : (pct < 75) ? 2 // green | gray* + : 1; // blue | darkBlue + + int pxl_health = (hix - lox + 1) * ihp / ihpmax; + geoH.setRight(std::min(lox + pxl_health - 1, hix)); + hpbar_health.setGeometry(geoH); + w = geoH.right() - geoH.left() + 1; // might yield 0 (ie, if dead) + styleH.sprintf(styleformat, barcolors[colorindx][0], w, w); + hpbar_health.setStyleSheet(styleH); + // style sheet should be doing this but width was sticking at full + hpbar_health.setMaximumWidth(w); + hpbar_health.show(); // don't need to hide() if/when width is 0 + + int oldleft = geoI.left(); + geoI.setLeft(geoH.right() + 1); + geoI.setRight(hix); + hpbar_injury.setGeometry(geoI); + w = geoI.right() - geoI.left() + 1; + styleI.sprintf(styleformat, barcolors[colorindx][1], w, w); + hpbar_injury.setStyleSheet(styleI); + if (geoI.left() != oldleft) + hpbar_injury.move(geoI.left(), geoI.top()); + hpbar_injury.show(); + + alreadyfullhp = false; + } else if (!alreadyfullhp) { // skip if unchanged + // health is full + colorindx = 0; // black | (not used) + + hpbar_injury.hide(); + geoI.setLeft(hix); // hix + 1 + hpbar_injury.setGeometry(geoI); + + geoH.setRight(hix); + hpbar_health.setGeometry(geoH); + w = geoH.right() - geoH.left() + 1; + styleH.sprintf(styleformat, barcolors[colorindx][0], w, w); + hpbar_health.setStyleSheet(styleH); + hpbar_health.setMaximumWidth(w); // (see above) + hpbar_health.show(); + + alreadyfullhp = true; + } + } else { + // hitpoint bar is disabled + hpbar_health.hide(); + hpbar_injury.hide(); + alreadyfullhp = false; + } +} + /* * Update the displayed status. The current code in botl.c updates * two lines of information. Both lines are always updated one after @@ -413,6 +541,8 @@ void NetHackQtStatusWindow::updateStats() if (cursy != 0) return; /* do a complete update when line 0 is done */ + HitpointBar(); + int st = ACURR(A_STR); if (st > STR18(100)) { buf.sprintf("Str:%d", st - 100); // 19..25 diff --git a/win/Qt/qt_stat.h b/win/Qt/qt_stat.h index a8d27cb95..7a3715b3d 100644 --- a/win/Qt/qt_stat.h +++ b/win/Qt/qt_stat.h @@ -104,6 +104,9 @@ private: NetHackQtLabelledIcon fly; NetHackQtLabelledIcon ride; + QLabel hpbar_health; // hit point bar, left half + QLabel hpbar_injury; // hit point bar, right half + QFrame hline1; QFrame hline2; QFrame hline3; @@ -111,7 +114,10 @@ private: int cursy; bool first_set; + bool alreadyfullhp; + QHBoxLayout *InitHitpointBar(); + void HitpointBar(); void nullOut(); void updateStats(); void checkTurnEvents();