Details | Last modification | View Log | RSS feed
| Rev | Author | Line No. | Line |
|---|---|---|---|
| 1 | pmbaty | 1 | /* |
| 2 | * This file is part of the DXX-Rebirth project <https://www.dxx-rebirth.com/>. |
||
| 3 | * It is copyright by its individual contributors, as recorded in the |
||
| 4 | * project's Git history. See COPYING.txt at the top level for license |
||
| 5 | * terms and a link to the Git history. |
||
| 6 | */ |
||
| 7 | /* |
||
| 8 | * |
||
| 9 | * Routines for displaying HUD messages... |
||
| 10 | * |
||
| 11 | */ |
||
| 12 | |||
| 13 | #include <stdio.h> |
||
| 14 | #include <string.h> |
||
| 15 | #include <stdlib.h> |
||
| 16 | |||
| 17 | #include "hudmsg.h" |
||
| 18 | #include "pstypes.h" |
||
| 19 | #include "u_mem.h" |
||
| 20 | #include "strutil.h" |
||
| 21 | #include "console.h" |
||
| 22 | #include "object.h" |
||
| 23 | #include "inferno.h" |
||
| 24 | #include "game.h" |
||
| 25 | #include "screens.h" |
||
| 26 | #include "gauges.h" |
||
| 27 | #include "physics.h" |
||
| 28 | #include "dxxerror.h" |
||
| 29 | #include "menu.h" // For the font. |
||
| 30 | #include "collide.h" |
||
| 31 | #include "newdemo.h" |
||
| 32 | #include "player.h" |
||
| 33 | #include "gamefont.h" |
||
| 34 | #include "screens.h" |
||
| 35 | #include "text.h" |
||
| 36 | #include "laser.h" |
||
| 37 | #include "args.h" |
||
| 38 | #include "playsave.h" |
||
| 39 | #include "countarray.h" |
||
| 40 | |||
| 41 | namespace { |
||
| 42 | constexpr std::integral_constant<unsigned, 150> HUD_MESSAGE_LENGTH{}; |
||
| 43 | |||
| 44 | struct hudmsg |
||
| 45 | { |
||
| 46 | fix time; |
||
| 47 | ntstring<HUD_MESSAGE_LENGTH> message; |
||
| 48 | template <typename M> |
||
| 49 | hudmsg(const fix& t, M &&m) : |
||
| 50 | time(t) |
||
| 51 | { |
||
| 52 | message.copy_if(m); |
||
| 53 | } |
||
| 54 | }; |
||
| 55 | |||
| 56 | struct hudmsg_array_t : public count_array_t<hudmsg, HUD_MAX_NUM_STOR> {}; |
||
| 57 | } |
||
| 58 | |||
| 59 | static hudmsg_array_t HUD_messages; |
||
| 60 | |||
| 61 | |||
| 62 | int HUD_toolong = 0; |
||
| 63 | static int HUD_color = -1; |
||
| 64 | static int HUD_init_message_literal_worth_showing(int class_flag, const char *message); |
||
| 65 | |||
| 66 | void HUD_clear_messages() |
||
| 67 | { |
||
| 68 | HUD_messages.clear(); |
||
| 69 | HUD_toolong = 0; |
||
| 70 | HUD_color = -1; |
||
| 71 | } |
||
| 72 | |||
| 73 | namespace dsx { |
||
| 74 | // ---------------------------------------------------------------------------- |
||
| 75 | // Writes a message on the HUD and checks its timer. |
||
| 76 | void HUD_render_message_frame(grs_canvas &canvas) |
||
| 77 | { |
||
| 78 | int y; |
||
| 79 | |||
| 80 | HUD_toolong = 0; |
||
| 81 | |||
| 82 | if (HUD_messages.empty()) |
||
| 83 | return; |
||
| 84 | |||
| 85 | auto expired = [](hudmsg &h) -> int { |
||
| 86 | if (h.time <= FrameTime) |
||
| 87 | return 1; |
||
| 88 | h.time -= FrameTime; |
||
| 89 | return 0; |
||
| 90 | }; |
||
| 91 | HUD_messages.erase_if(expired); |
||
| 92 | |||
| 93 | // display last $HUD_MAX_NUM_DISP messages on the list |
||
| 94 | if (!HUD_messages.empty()) |
||
| 95 | { |
||
| 96 | if (HUD_color == -1) |
||
| 97 | HUD_color = BM_XRGB(0,28,0); |
||
| 98 | gr_set_fontcolor(canvas, HUD_color, -1); |
||
| 99 | y = FSPACY(1); |
||
| 100 | |||
| 101 | auto &game_font = *GAME_FONT; |
||
| 102 | const auto &&line_spacing = LINE_SPACING(game_font, game_font); |
||
| 103 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 104 | if (PlayerCfg.GuidedInBigWindow && |
||
| 105 | LevelUniqueObjectState.Guided_missile.get_player_active_guided_missile(LevelUniqueObjectState.get_objects().vmptr, Player_num) != nullptr) |
||
| 106 | y += line_spacing; |
||
| 107 | #endif |
||
| 108 | |||
| 109 | hudmsg_array_t::iterator i, e = HUD_messages.end(); |
||
| 110 | if (HUD_messages.size() < HUD_MAX_NUM_DISP) |
||
| 111 | i = HUD_messages.begin(); |
||
| 112 | else |
||
| 113 | i = e - HUD_MAX_NUM_DISP; |
||
| 114 | if (strlen(i->message) > 38) |
||
| 115 | HUD_toolong = 1; |
||
| 116 | for (; i != e; ++i ) { |
||
| 117 | gr_string(canvas, game_font, 0x8000, y, &i->message[0]); |
||
| 118 | y += line_spacing; |
||
| 119 | } |
||
| 120 | } |
||
| 121 | } |
||
| 122 | } |
||
| 123 | |||
| 124 | static int is_worth_showing(int class_flag) |
||
| 125 | { |
||
| 126 | if (PlayerCfg.NoRedundancy && (class_flag & HM_REDUNDANT)) |
||
| 127 | return 0; |
||
| 128 | |||
| 129 | if (PlayerCfg.MultiMessages && (Game_mode & GM_MULTI) && !(class_flag & HM_MULTI)) |
||
| 130 | return 0; |
||
| 131 | return 1; |
||
| 132 | } |
||
| 133 | |||
| 134 | // Call to flash a message on the HUD. Returns true if message drawn. |
||
| 135 | // (message might not be drawn if previous message was same) |
||
| 136 | int HUD_init_message_va(int class_flag, const char * format, va_list args) |
||
| 137 | { |
||
| 138 | if (!is_worth_showing(class_flag)) |
||
| 139 | return 0; |
||
| 140 | |||
| 141 | #ifndef macintosh |
||
| 142 | char message[HUD_MESSAGE_LENGTH+1] = ""; |
||
| 143 | #else |
||
| 144 | char message[1024] = ""; |
||
| 145 | #endif |
||
| 146 | |||
| 147 | #ifndef macintosh |
||
| 148 | vsnprintf(message, sizeof(char)*HUD_MESSAGE_LENGTH, format, args); |
||
| 149 | #else |
||
| 150 | vsprintf(message, format, args); |
||
| 151 | #endif |
||
| 152 | int r = HUD_init_message_literal_worth_showing(class_flag, message); |
||
| 153 | if (r) |
||
| 154 | con_puts(CON_HUD, message); |
||
| 155 | return r; |
||
| 156 | } |
||
| 157 | |||
| 158 | |||
| 159 | static int HUD_init_message_literal_worth_showing(int class_flag, const char *message) |
||
| 160 | { |
||
| 161 | // check if message is already in list and bail out if so |
||
| 162 | if (!HUD_messages.empty()) |
||
| 163 | { |
||
| 164 | hudmsg_array_t::iterator i, e = HUD_messages.end(); |
||
| 165 | // if "normal" message, only check if it's the same at the most recent one, if marked as "may duplicate" check whole list |
||
| 166 | if (class_flag & HM_MAYDUPL) |
||
| 167 | i = HUD_messages.begin(); |
||
| 168 | else |
||
| 169 | i = e - 1; |
||
| 170 | for (; i != e; ++i) |
||
| 171 | { |
||
| 172 | if (!d_stricmp(message, i->message)) |
||
| 173 | { |
||
| 174 | i->time = F1_0*2; // keep redundant message in list |
||
| 175 | if (std::distance(i, e) < HUD_MAX_NUM_DISP) // if redundant message on display, update them all |
||
| 176 | { |
||
| 177 | if (HUD_messages.size() < HUD_MAX_NUM_DISP) |
||
| 178 | i = HUD_messages.begin(); |
||
| 179 | else |
||
| 180 | i = HUD_messages.end() - HUD_MAX_NUM_DISP; |
||
| 181 | for (unsigned j = 1; i != e; ++i, j++) |
||
| 182 | i->time = F1_0*(j*2); |
||
| 183 | } |
||
| 184 | return 0; |
||
| 185 | } |
||
| 186 | } |
||
| 187 | } |
||
| 188 | |||
| 189 | if (HUD_messages.size() >= HUD_MAX_NUM_STOR) |
||
| 190 | { |
||
| 191 | std::move(HUD_messages.begin() + 1, HUD_messages.end(), HUD_messages.begin()); |
||
| 192 | HUD_messages.pop_back(); |
||
| 193 | } |
||
| 194 | fix t; |
||
| 195 | if (HUD_messages.size() + 1 < HUD_MAX_NUM_DISP) |
||
| 196 | t = F1_0*3; // one message - display 3 secs |
||
| 197 | else |
||
| 198 | { |
||
| 199 | hudmsg_array_t::iterator e = HUD_messages.end(), i = e - HUD_MAX_NUM_DISP; |
||
| 200 | for (unsigned j = 1; ++i != e; j++) // multiple messages - display 2 seconds each |
||
| 201 | i->time = F1_0*(j*2); |
||
| 202 | t = F1_0 * ((HUD_MAX_NUM_DISP + 1) * 2); |
||
| 203 | } |
||
| 204 | HUD_messages.emplace_back(t, message); |
||
| 205 | |||
| 206 | if (HUD_color == -1) |
||
| 207 | HUD_color = BM_XRGB(0,28,0); |
||
| 208 | |||
| 209 | if (Newdemo_state == ND_STATE_RECORDING ) |
||
| 210 | newdemo_record_hud_message( message ); |
||
| 211 | |||
| 212 | return 1; |
||
| 213 | } |
||
| 214 | |||
| 215 | int (HUD_init_message)(int class_flag, const char * format, ... ) |
||
| 216 | { |
||
| 217 | int ret; |
||
| 218 | va_list args; |
||
| 219 | |||
| 220 | va_start(args, format); |
||
| 221 | ret = HUD_init_message_va(class_flag, format, args); |
||
| 222 | va_end(args); |
||
| 223 | |||
| 224 | return ret; |
||
| 225 | } |
||
| 226 | |||
| 227 | int HUD_init_message_literal(int class_flag, const char *str) |
||
| 228 | { |
||
| 229 | if (!is_worth_showing(class_flag)) |
||
| 230 | return 0; |
||
| 231 | int r = HUD_init_message_literal_worth_showing(class_flag, str); |
||
| 232 | if (r) |
||
| 233 | con_puts(CON_HUD, str); |
||
| 234 | return r; |
||
| 235 | } |
||
| 236 | |||
| 237 | void player_dead_message(grs_canvas &canvas) |
||
| 238 | { |
||
| 239 | if (Player_dead_state == player_dead_state::exploded) |
||
| 240 | { |
||
| 241 | if (get_local_player().lives == 1) |
||
| 242 | { |
||
| 243 | int x, y, w, h; |
||
| 244 | auto &huge_font = *HUGE_FONT; |
||
| 245 | gr_get_string_size(huge_font, TXT_GAME_OVER, &w, &h, nullptr); |
||
| 246 | const int gw = w; |
||
| 247 | const int gh = h; |
||
| 248 | w += 20; |
||
| 249 | h += 8; |
||
| 250 | x = (canvas.cv_bitmap.bm_w - w ) / 2; |
||
| 251 | y = (canvas.cv_bitmap.bm_h - h ) / 2; |
||
| 252 | |||
| 253 | gr_settransblend(canvas, 14, gr_blend::normal); |
||
| 254 | const uint8_t color = BM_XRGB(0, 0, 0); |
||
| 255 | gr_rect(canvas, x, y, x + w, y + h, color); |
||
| 256 | gr_settransblend(canvas, GR_FADE_OFF, gr_blend::normal); |
||
| 257 | |||
| 258 | gr_string(canvas, huge_font, 0x8000, (canvas.cv_bitmap.bm_h - h) / 2 + h / 8, TXT_GAME_OVER, gw, gh); |
||
| 259 | } |
||
| 260 | |||
| 261 | if (HUD_color == -1) |
||
| 262 | HUD_color = BM_XRGB(0,28,0); |
||
| 263 | gr_set_fontcolor(canvas, HUD_color, -1); |
||
| 264 | auto &game_font = *GAME_FONT; |
||
| 265 | gr_string(canvas, game_font, 0x8000, canvas.cv_bitmap.bm_h - LINE_SPACING(game_font, game_font), PlayerCfg.RespawnMode == RespawnPress::Any ? TXT_PRESS_ANY_KEY : "Press fire key or button to continue..."); |
||
| 266 | } |
||
| 267 | } |