1 /*
2 * Portions of this file are copyright Rebirth contributors and licensed as
3 * described in COPYING.txt.
4 * Portions of this file are copyright Parallax Software and licensed
5 * according to the Parallax license below.
6 * See COPYING.txt for license details.
7
8 THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
9 SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO
10 END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
11 ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
12 IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
13 SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
14 FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
15 CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS
16 AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
17 COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED.
18 */
19
20 /*
21 *
22 * Inferno gauge drivers
23 *
24 */
25
26 #include <algorithm>
27 #include <cmath>
28 #include <stdio.h>
29 #include <string.h>
30 #include <stdlib.h>
31 #include <stdarg.h>
32
33 #include "hudmsg.h"
34 #include "inferno.h"
35 #include "game.h"
36 #include "gauges.h"
37 #include "physics.h"
38 #include "dxxerror.h"
39 #include "object.h"
40 #include "newdemo.h"
41 #include "player.h"
42 #include "gamefont.h"
43 #include "bm.h"
44 #include "text.h"
45 #include "powerup.h"
46 #include "sounds.h"
47 #include "multi.h"
48 #include "endlevel.h"
49 #include "controls.h"
50 #include "text.h"
51 #include "render.h"
52 #include "piggy.h"
53 #include "laser.h"
54 #include "weapon.h"
55 #include "common/3d/globvars.h"
56 #include "playsave.h"
57 #include "rle.h"
58 #if DXX_USE_OGL
59 #include "ogl_init.h"
60 #endif
61 #include "args.h"
62 #include "vclip.h"
63 #include "compiler-range_for.h"
64 #include "d_levelstate.h"
65 #include "partial_range.h"
66 #include <utility>
67
68 using std::min;
69
70 namespace {
71
72 enum class gauge_screen_resolution : uint8_t
73 {
74 low,
75 high,
76 };
77
78 class local_multires_gauge_graphic
79 {
80 public:
81 const gauge_screen_resolution hiresmode = gauge_screen_resolution{HIRESMODE};
is_hires() const82 bool is_hires() const
83 {
84 return hiresmode != gauge_screen_resolution::low;
85 }
86 template <typename T>
get(T h,T l) const87 T get(T h, T l) const
88 {
89 return is_hires() ? h : l;
90 }
91 template <typename T>
rget(const T & h,const T & l) const92 const T &rget(const T &h, const T &l) const
93 {
94 return is_hires() ? h : l;
95 }
96 };
97
show_cloak_invul_timer()98 static bool show_cloak_invul_timer()
99 {
100 return PlayerCfg.CloakInvulTimer && Newdemo_state != ND_STATE_PLAYBACK;
101 }
102
103 static void draw_ammo_info(grs_canvas &, unsigned x, unsigned y, unsigned ammo_count);
104
105 union weapon_index
106 {
107 primary_weapon_index_t primary;
108 secondary_weapon_index_t secondary;
weapon_index()109 constexpr weapon_index() :
110 primary(static_cast<primary_weapon_index_t>(~0u))
111 {
112 }
weapon_index(const primary_weapon_index_t p)113 constexpr weapon_index(const primary_weapon_index_t p) :
114 primary(p)
115 {
116 }
weapon_index(const secondary_weapon_index_t s)117 constexpr weapon_index(const secondary_weapon_index_t s) :
118 secondary(s)
119 {
120 }
operator !=(const weapon_index w) const121 constexpr bool operator!=(const weapon_index w) const
122 {
123 return primary != w.primary;
124 }
operator ==(const weapon_index w) const125 constexpr bool operator==(const weapon_index w) const
126 {
127 return primary == w.primary;
128 }
129 };
130
131 }
132
133 //bitmap numbers for gauges
134 #define GAUGE_SHIELDS 0 //0..9, in decreasing order (100%,90%...0%)
135 #define GAUGE_INVULNERABLE 10 //10..19
136 #define N_INVULNERABLE_FRAMES 10
137 #define GAUGE_ENERGY_LEFT 21
138 #define GAUGE_ENERGY_RIGHT 22
139 #define GAUGE_NUMERICAL 23
140 #define GAUGE_BLUE_KEY 24
141 #define GAUGE_GOLD_KEY 25
142 #define GAUGE_RED_KEY 26
143 #define GAUGE_BLUE_KEY_OFF 27
144 #define GAUGE_GOLD_KEY_OFF 28
145 #define GAUGE_RED_KEY_OFF 29
146 #define SB_GAUGE_BLUE_KEY 30
147 #define SB_GAUGE_GOLD_KEY 31
148 #define SB_GAUGE_RED_KEY 32
149 #define SB_GAUGE_BLUE_KEY_OFF 33
150 #define SB_GAUGE_GOLD_KEY_OFF 34
151 #define SB_GAUGE_RED_KEY_OFF 35
152 #define SB_GAUGE_ENERGY 36
153 #define GAUGE_LIVES 37
154 #define GAUGE_SHIPS 38
155 #define RETICLE_CROSS 46
156 #define RETICLE_PRIMARY 48
157 #define RETICLE_SECONDARY 51
158 #define GAUGE_HOMING_WARNING_ON 56
159 #define GAUGE_HOMING_WARNING_OFF 57
160 #define KEY_ICON_BLUE 68
161 #define KEY_ICON_YELLOW 69
162 #define KEY_ICON_RED 70
163
164 //Coordinats for gauges
165 #if defined(DXX_BUILD_DESCENT_I)
166 #define GAUGE_BLUE_KEY_X_L 45
167 #define GAUGE_BLUE_KEY_X_H 91
168 #define GAUGE_GOLD_KEY_X_L 44
169 #define GAUGE_GOLD_KEY_X_H 89
170 #define GAUGE_RED_KEY_X_L 43
171 #define GAUGE_RED_KEY_X_H 87
172 #define GAUGE_RED_KEY_Y_H 417
173 #define LEFT_ENERGY_GAUGE_X_H 137
174 #define RIGHT_ENERGY_GAUGE_X ((multires_gauge_graphic.get(380, 190)))
175 #elif defined(DXX_BUILD_DESCENT_II)
176 #define GAUGE_AFTERBURNER 20
177 #define SB_GAUGE_AFTERBURNER 71
178 #define FLAG_ICON_RED 72
179 #define FLAG_ICON_BLUE 73
180 #define GAUGE_BLUE_KEY_X_L 272
181 #define GAUGE_BLUE_KEY_X_H 535
182 #define GAUGE_GOLD_KEY_X_L 273
183 #define GAUGE_GOLD_KEY_X_H 537
184 #define GAUGE_RED_KEY_X_L 274
185 #define GAUGE_RED_KEY_X_H 539
186 #define GAUGE_RED_KEY_Y_H 416
187 #define LEFT_ENERGY_GAUGE_X_H 138
188 #define RIGHT_ENERGY_GAUGE_X ((multires_gauge_graphic.get(379, 190)))
189 #endif
190
191 #define GAUGE_BLUE_KEY_Y_L 152
192 #define GAUGE_BLUE_KEY_Y_H 374
193 #define GAUGE_BLUE_KEY_X ((multires_gauge_graphic.get(GAUGE_BLUE_KEY_X_H, GAUGE_BLUE_KEY_X_L)))
194 #define GAUGE_BLUE_KEY_Y ((multires_gauge_graphic.get(GAUGE_BLUE_KEY_Y_H, GAUGE_BLUE_KEY_Y_L)))
195 #define GAUGE_GOLD_KEY_Y_L 162
196 #define GAUGE_GOLD_KEY_Y_H 395
197 #define GAUGE_GOLD_KEY_X ((multires_gauge_graphic.get(GAUGE_GOLD_KEY_X_H, GAUGE_GOLD_KEY_X_L)))
198 #define GAUGE_GOLD_KEY_Y ((multires_gauge_graphic.get(GAUGE_GOLD_KEY_Y_H, GAUGE_GOLD_KEY_Y_L)))
199 #define GAUGE_RED_KEY_Y_L 172
200 #define GAUGE_RED_KEY_X ((multires_gauge_graphic.get(GAUGE_RED_KEY_X_H, GAUGE_RED_KEY_X_L)))
201 #define GAUGE_RED_KEY_Y ((multires_gauge_graphic.get(GAUGE_RED_KEY_Y_H, GAUGE_RED_KEY_Y_L)))
202 #define SB_GAUGE_KEYS_X_L 11
203 #define SB_GAUGE_KEYS_X_H 26
204 #define SB_GAUGE_KEYS_X ((multires_gauge_graphic.get(SB_GAUGE_KEYS_X_H, SB_GAUGE_KEYS_X_L)))
205 #define SB_GAUGE_BLUE_KEY_Y_L 153
206 #define SB_GAUGE_GOLD_KEY_Y_L 169
207 #define SB_GAUGE_RED_KEY_Y_L 185
208 #define SB_GAUGE_BLUE_KEY_Y_H 390
209 #define SB_GAUGE_GOLD_KEY_Y_H 422
210 #define SB_GAUGE_RED_KEY_Y_H 454
211 #define SB_GAUGE_BLUE_KEY_Y ((multires_gauge_graphic.get(SB_GAUGE_BLUE_KEY_Y_H, SB_GAUGE_BLUE_KEY_Y_L)))
212 #define SB_GAUGE_GOLD_KEY_Y ((multires_gauge_graphic.get(SB_GAUGE_GOLD_KEY_Y_H, SB_GAUGE_GOLD_KEY_Y_L)))
213 #define SB_GAUGE_RED_KEY_Y ((multires_gauge_graphic.get(SB_GAUGE_RED_KEY_Y_H, SB_GAUGE_RED_KEY_Y_L)))
214 #define LEFT_ENERGY_GAUGE_X_L 70
215 #define LEFT_ENERGY_GAUGE_Y_L 131
216 #define LEFT_ENERGY_GAUGE_W_L 64
217 #define LEFT_ENERGY_GAUGE_H_L 8
218 #define LEFT_ENERGY_GAUGE_Y_H 314
219 #define LEFT_ENERGY_GAUGE_W_H 133
220 #define LEFT_ENERGY_GAUGE_H_H 21
221 #define LEFT_ENERGY_GAUGE_X ((multires_gauge_graphic.get(LEFT_ENERGY_GAUGE_X_H, LEFT_ENERGY_GAUGE_X_L)))
222 #define LEFT_ENERGY_GAUGE_Y ((multires_gauge_graphic.get(LEFT_ENERGY_GAUGE_Y_H, LEFT_ENERGY_GAUGE_Y_L)))
223 #define LEFT_ENERGY_GAUGE_W ((multires_gauge_graphic.get(LEFT_ENERGY_GAUGE_W_H, LEFT_ENERGY_GAUGE_W_L)))
224 #define LEFT_ENERGY_GAUGE_H ((multires_gauge_graphic.get(LEFT_ENERGY_GAUGE_H_H, LEFT_ENERGY_GAUGE_H_L)))
225 #define RIGHT_ENERGY_GAUGE_Y ((multires_gauge_graphic.get(314, 131)))
226 #define RIGHT_ENERGY_GAUGE_W ((multires_gauge_graphic.get(133, 64)))
227 #define RIGHT_ENERGY_GAUGE_H ((multires_gauge_graphic.get(21, 8)))
228
229 #if defined(DXX_BUILD_DESCENT_I)
230 #define SB_ENERGY_GAUGE_Y ((multires_gauge_graphic.get(390, 155)))
231 #define SB_ENERGY_GAUGE_H ((multires_gauge_graphic.get(82, 41)))
232 #define SB_ENERGY_NUM_Y ((multires_gauge_graphic.get(457, 190)))
233 #define SHIELD_GAUGE_Y ((multires_gauge_graphic.get(374, 155)))
234 #define SB_SHIELD_NUM_Y (SB_SHIELD_GAUGE_Y-((multires_gauge_graphic.get(16, 7)))) //156 -- MWA used to be hard coded to 156
235 #define NUMERICAL_GAUGE_Y ((multires_gauge_graphic.get(316, 130)))
236 #define PRIMARY_W_PIC_X ((multires_gauge_graphic.get(135, 64)))
237 #define SECONDARY_W_PIC_X ((multires_gauge_graphic.get(405, 234)))
238 #define SECONDARY_W_PIC_Y ((multires_gauge_graphic.get(370, 154)))
239 #define SECONDARY_W_TEXT_X multires_gauge_graphic.get(462, 207)
240 #define SECONDARY_W_TEXT_Y multires_gauge_graphic.get(400, 157)
241 #define SECONDARY_AMMO_X multires_gauge_graphic.get(475, 213)
242 #define SECONDARY_AMMO_Y multires_gauge_graphic.get(425, 171)
243 #define SB_LIVES_X ((multires_gauge_graphic.get(550, 266)))
244 #define SB_LIVES_Y ((multires_gauge_graphic.get(450, 185)))
245 #define SB_SCORE_RIGHT_H 605
246 #define BOMB_COUNT_X ((multires_gauge_graphic.get(468, 210)))
247 #define PRIMARY_W_BOX_RIGHT_H 241
248 #define SECONDARY_W_BOX_RIGHT_L 264 //(SECONDARY_W_BOX_LEFT+54)
249 #define SECONDARY_W_BOX_LEFT_H 403
250 #define SECONDARY_W_BOX_TOP_H 364
251 #define SECONDARY_W_BOX_RIGHT_H 531
252 #define SB_PRIMARY_W_BOX_TOP_L 154
253 #define SB_PRIMARY_W_BOX_BOT_L (195)
254 #define SB_SECONDARY_W_BOX_TOP_L 154
255 #define SB_SECONDARY_W_BOX_RIGHT_L (SB_SECONDARY_W_BOX_LEFT_L+54)
256 #define SB_SECONDARY_W_BOX_BOT_L (153+42)
257 #define SB_SECONDARY_AMMO_X (SB_SECONDARY_W_BOX_LEFT + (multires_gauge_graphic.get(14, 11))) //(212+9)
258 #define GET_GAUGE_INDEX(x) (Gauges[x].index)
259
260 #elif defined(DXX_BUILD_DESCENT_II)
261 #define AFTERBURNER_GAUGE_X_L 45-1
262 #define AFTERBURNER_GAUGE_Y_L 158
263 #define AFTERBURNER_GAUGE_H_L 32
264 #define AFTERBURNER_GAUGE_X_H 88
265 #define AFTERBURNER_GAUGE_Y_H 378
266 #define AFTERBURNER_GAUGE_H_H 65
267 #define AFTERBURNER_GAUGE_X ((multires_gauge_graphic.get(AFTERBURNER_GAUGE_X_H, AFTERBURNER_GAUGE_X_L)))
268 #define AFTERBURNER_GAUGE_Y ((multires_gauge_graphic.get(AFTERBURNER_GAUGE_Y_H, AFTERBURNER_GAUGE_Y_L)))
269 #define AFTERBURNER_GAUGE_H ((multires_gauge_graphic.get(AFTERBURNER_GAUGE_H_H, AFTERBURNER_GAUGE_H_L)))
270 #define SB_AFTERBURNER_GAUGE_X ((multires_gauge_graphic.get(196, 98)))
271 #define SB_AFTERBURNER_GAUGE_Y ((multires_gauge_graphic.get(445, 184)))
272 #define SB_AFTERBURNER_GAUGE_W ((multires_gauge_graphic.get(33, 16)))
273 #define SB_AFTERBURNER_GAUGE_H ((multires_gauge_graphic.get(29, 13)))
274 #define SB_ENERGY_GAUGE_Y ((multires_gauge_graphic.get(381, 155-2)))
275 #define SB_ENERGY_GAUGE_H ((multires_gauge_graphic.get(60, 29)))
276 #define SB_ENERGY_NUM_Y ((multires_gauge_graphic.get(457, 175)))
277 #define SHIELD_GAUGE_Y ((multires_gauge_graphic.get(375, 155)))
278 #define SB_SHIELD_NUM_Y (SB_SHIELD_GAUGE_Y-((multires_gauge_graphic.get(16, 8)))) //156 -- MWA used to be hard coded to 156
279 #define NUMERICAL_GAUGE_Y ((multires_gauge_graphic.get(314, 130)))
280 #define PRIMARY_W_PIC_X ((multires_gauge_graphic.get(135-10, 64)))
281 #define SECONDARY_W_PIC_X ((multires_gauge_graphic.get(466, 234)))
282 #define SECONDARY_W_PIC_Y ((multires_gauge_graphic.get(374, 154)))
283 #define SECONDARY_W_TEXT_X multires_gauge_graphic.get(413, 207)
284 #define SECONDARY_W_TEXT_Y multires_gauge_graphic.get(378, 157)
285 #define SECONDARY_AMMO_X multires_gauge_graphic.get(428, 213)
286 #define SECONDARY_AMMO_Y multires_gauge_graphic.get(407, 171)
287 #define SB_LIVES_X ((multires_gauge_graphic.get(550-10-3, 266)))
288 #define SB_LIVES_Y ((multires_gauge_graphic.get(450-3, 185)))
289 #define SB_SCORE_RIGHT_H (605+8)
290 #define BOMB_COUNT_X ((multires_gauge_graphic.get(546, 275)))
291 #define PRIMARY_W_BOX_RIGHT_H 242
292 #define SECONDARY_W_BOX_RIGHT_L 263 //(SECONDARY_W_BOX_LEFT+54)
293 #define SECONDARY_W_BOX_LEFT_H 404
294 #define SECONDARY_W_BOX_TOP_H 363
295 #define SECONDARY_W_BOX_RIGHT_H 529
296 #define SB_PRIMARY_W_BOX_TOP_L 153
297 #define SB_PRIMARY_W_BOX_BOT_L (195+1)
298 #define SB_SECONDARY_W_BOX_TOP_L 153
299 #define SB_SECONDARY_W_BOX_RIGHT_L (SB_SECONDARY_W_BOX_LEFT_L+54+1)
300 #define SB_SECONDARY_W_BOX_BOT_L (SB_SECONDARY_W_BOX_TOP_L+43)
301 #define SB_SECONDARY_AMMO_X (SB_SECONDARY_W_BOX_LEFT + (multires_gauge_graphic.get(14 - 4, 11))) //(212+9)
302 #define GET_GAUGE_INDEX(x) ((multires_gauge_graphic.rget(Gauges_hires, Gauges)[x].index))
303
304 #endif
305
306 // MOVEME
307 #define HOMING_WARNING_X ((multires_gauge_graphic.get(13, 7)))
308 #define HOMING_WARNING_Y ((multires_gauge_graphic.get(416, 171)))
309
310 #define SB_ENERGY_GAUGE_X ((multires_gauge_graphic.get(196, 98)))
311 #define SB_ENERGY_GAUGE_W ((multires_gauge_graphic.get(32, 16)))
312 #define SHIP_GAUGE_X (SHIELD_GAUGE_X+((multires_gauge_graphic.get(11, 5))))
313 #define SHIP_GAUGE_Y (SHIELD_GAUGE_Y+((multires_gauge_graphic.get(10, 5))))
314 #define SHIELD_GAUGE_X ((multires_gauge_graphic.get(292, 146)))
315 #define SB_SHIELD_GAUGE_X ((multires_gauge_graphic.get(247, 123))) //139
316 #define SB_SHIELD_GAUGE_Y ((multires_gauge_graphic.get(395, 163)))
317 #define SB_SHIP_GAUGE_X (SB_SHIELD_GAUGE_X+((multires_gauge_graphic.get(11, 5))))
318 #define SB_SHIP_GAUGE_Y (SB_SHIELD_GAUGE_Y+((multires_gauge_graphic.get(10, 5))))
319 #define SB_SHIELD_NUM_X (SB_SHIELD_GAUGE_X+((multires_gauge_graphic.get(21, 12)))) //151
320 #define NUMERICAL_GAUGE_X ((multires_gauge_graphic.get(308, 154)))
321 #define PRIMARY_W_PIC_Y ((multires_gauge_graphic.get(370, 154)))
322 #define PRIMARY_W_TEXT_X multires_gauge_graphic.get(182, 87)
323 #define PRIMARY_W_TEXT_Y multires_gauge_graphic.get(378, 157)
324 #define PRIMARY_AMMO_X multires_gauge_graphic.get(186, 93)
325 #define PRIMARY_AMMO_Y multires_gauge_graphic.get(407, 171)
326 #define SB_LIVES_LABEL_X ((multires_gauge_graphic.get(475, 237)))
327 #define SB_SCORE_RIGHT_L 301
328 #define SB_SCORE_RIGHT ((multires_gauge_graphic.get(SB_SCORE_RIGHT_H, SB_SCORE_RIGHT_L)))
329 #define SB_SCORE_Y ((multires_gauge_graphic.get(398, 158)))
330 #define SB_SCORE_LABEL_X ((multires_gauge_graphic.get(475, 237)))
331 #define SB_SCORE_ADDED_RIGHT ((multires_gauge_graphic.get(SB_SCORE_RIGHT_H, SB_SCORE_RIGHT_L)))
332 #define SB_SCORE_ADDED_Y ((multires_gauge_graphic.get(413, 165)))
333 #define BOMB_COUNT_Y ((multires_gauge_graphic.get(445, 186)))
334 #define SB_BOMB_COUNT_X ((multires_gauge_graphic.get(342, 171)))
335 #define SB_BOMB_COUNT_Y ((multires_gauge_graphic.get(458, 191)))
336
337 // defining box boundries for weapon pictures
338 #define PRIMARY_W_BOX_LEFT_L 63
339 #define PRIMARY_W_BOX_TOP_L 151 //154
340 #define PRIMARY_W_BOX_RIGHT_L (PRIMARY_W_BOX_LEFT_L+58)
341 #define PRIMARY_W_BOX_BOT_L (PRIMARY_W_BOX_TOP_L+42)
342 #define PRIMARY_W_BOX_LEFT_H 121
343 #define PRIMARY_W_BOX_TOP_H 364
344 #define PRIMARY_W_BOX_BOT_H (PRIMARY_W_BOX_TOP_H+106) //470
345 #define PRIMARY_W_BOX_LEFT ((multires_gauge_graphic.get(PRIMARY_W_BOX_LEFT_H, PRIMARY_W_BOX_LEFT_L)))
346 #define PRIMARY_W_BOX_TOP ((multires_gauge_graphic.get(PRIMARY_W_BOX_TOP_H, PRIMARY_W_BOX_TOP_L)))
347 #define PRIMARY_W_BOX_RIGHT ((multires_gauge_graphic.get(PRIMARY_W_BOX_RIGHT_H, PRIMARY_W_BOX_RIGHT_L)))
348 #define PRIMARY_W_BOX_BOT ((multires_gauge_graphic.get(PRIMARY_W_BOX_BOT_H, PRIMARY_W_BOX_BOT_L)))
349 #define SECONDARY_W_BOX_LEFT_L 202 //207
350 #define SECONDARY_W_BOX_TOP_L 151
351 #define SECONDARY_W_BOX_BOT_L (SECONDARY_W_BOX_TOP_L+42)
352 #define SECONDARY_W_BOX_BOT_H (SECONDARY_W_BOX_TOP_H+106) //470
353 #define SECONDARY_W_BOX_LEFT ((multires_gauge_graphic.get(SECONDARY_W_BOX_LEFT_H, SECONDARY_W_BOX_LEFT_L)))
354 #define SECONDARY_W_BOX_TOP ((multires_gauge_graphic.get(SECONDARY_W_BOX_TOP_H, SECONDARY_W_BOX_TOP_L)))
355 #define SECONDARY_W_BOX_RIGHT ((multires_gauge_graphic.get(SECONDARY_W_BOX_RIGHT_H, SECONDARY_W_BOX_RIGHT_L)))
356 #define SECONDARY_W_BOX_BOT ((multires_gauge_graphic.get(SECONDARY_W_BOX_BOT_H, SECONDARY_W_BOX_BOT_L)))
357 #define SB_PRIMARY_W_BOX_LEFT_L 34 //50
358 #define SB_PRIMARY_W_BOX_RIGHT_L (SB_PRIMARY_W_BOX_LEFT_L+55)
359 #define SB_PRIMARY_W_BOX_LEFT_H 68
360 #define SB_PRIMARY_W_BOX_TOP_H 381
361 #define SB_PRIMARY_W_BOX_RIGHT_H 179
362 #define SB_PRIMARY_W_BOX_BOT_H 473
363 #define SB_PRIMARY_W_BOX_LEFT ((multires_gauge_graphic.get(SB_PRIMARY_W_BOX_LEFT_H, SB_PRIMARY_W_BOX_LEFT_L)))
364 #define SB_SECONDARY_W_BOX_LEFT_L 169
365 #define SB_SECONDARY_W_BOX_LEFT_H 338
366 #define SB_SECONDARY_W_BOX_TOP_H 381
367 #define SB_SECONDARY_W_BOX_RIGHT_H 449
368 #define SB_SECONDARY_W_BOX_BOT_H 473
369 #define SB_SECONDARY_W_BOX_LEFT ((multires_gauge_graphic.get(SB_SECONDARY_W_BOX_LEFT_H, SB_SECONDARY_W_BOX_LEFT_L))) //210
370 #define SB_PRIMARY_W_PIC_X (SB_PRIMARY_W_BOX_LEFT+1) //51
371 #define SB_PRIMARY_W_PIC_Y ((multires_gauge_graphic.get(382, 154)))
372 #define SB_PRIMARY_W_TEXT_X (SB_PRIMARY_W_BOX_LEFT + multires_gauge_graphic.get(50, 24)) //(51+23)
373 #define SB_PRIMARY_W_TEXT_Y (multires_gauge_graphic.get(390, 157))
374 #define SB_PRIMARY_AMMO_X (SB_PRIMARY_W_BOX_LEFT + multires_gauge_graphic.get(58, 30)) //((SB_PRIMARY_W_BOX_LEFT+33)-3) //(51+32)
375 #define SB_PRIMARY_AMMO_Y multires_gauge_graphic.get(410, 171)
376 #define SB_SECONDARY_W_PIC_X ((multires_gauge_graphic.get(385, (SB_SECONDARY_W_BOX_LEFT+27)))) //(212+27)
377 #define SB_SECONDARY_W_PIC_Y ((multires_gauge_graphic.get(382, 154)))
378 #define SB_SECONDARY_W_TEXT_X (SB_SECONDARY_W_BOX_LEFT + 2) //212
379 #define SB_SECONDARY_W_TEXT_Y multires_gauge_graphic.get(390, 157)
380 #define SB_SECONDARY_AMMO_Y multires_gauge_graphic.get(414, 171)
381
382 #define FADE_SCALE (2*i2f(GR_FADE_LEVELS)/REARM_TIME) // fade out and back in REARM_TIME, in fade levels per seconds (int)
383
384 #define COCKPIT_PRIMARY_BOX ((multires_gauge_graphic.get(4, 0)))
385 #define COCKPIT_SECONDARY_BOX ((multires_gauge_graphic.get(5, 1)))
386 #define SB_PRIMARY_BOX ((multires_gauge_graphic.get(6, 2)))
387 #define SB_SECONDARY_BOX ((multires_gauge_graphic.get(7, 3)))
388
389 // scaling gauges
390 #define BASE_WIDTH(G) ((G).get(640, 320))
391 #define BASE_HEIGHT(G) ((G).get(480, 200))
392 namespace {
393
394 enum class weapon_box_state : uint8_t
395 {
396 set, //in correct state
397 fading_out,
398 fading_in,
399 };
400 static_assert(static_cast<unsigned>(weapon_box_state::set) == 0, "weapon_box_states must start at zero");
401
402 #if DXX_USE_OGL
403 template <char tag>
404 class hud_scale_float;
405 #else
406 struct hud_unscaled_int
407 {
operator ()__anon808099050211::hud_unscaled_int408 long operator()(const unsigned i) const
409 {
410 return i;
411 }
412 };
413
414 template <char tag>
415 using hud_scale_float = hud_unscaled_int;
416 #endif
417
418 using hud_ar_scale_float = hud_scale_float<'a'>;
419 using hud_x_scale_float = hud_scale_float<'x'>;
420 using hud_y_scale_float = hud_scale_float<'y'>;
421
422 #if DXX_USE_OGL
423 class base_hud_scaled_int
424 {
425 const long v;
426 public:
base_hud_scaled_int(const long l)427 explicit constexpr base_hud_scaled_int(const long l) :
428 v(l)
429 {
430 }
operator long() const431 operator long() const
432 {
433 return v;
434 }
435 };
436
437 template <char>
438 class hud_scaled_int : public base_hud_scaled_int
439 {
440 public:
441 DXX_INHERIT_CONSTRUCTORS(hud_scaled_int, base_hud_scaled_int);
442 };
443
444 class base_hud_scale_float
445 {
446 protected:
447 const double scale;
operator ()(const int i) const448 long operator()(const int i) const
449 {
450 return (this->scale * static_cast<double>(i)) + 0.5;
451 }
get() const452 double get() const
453 {
454 return scale;
455 }
456 public:
base_hud_scale_float(const double s)457 constexpr base_hud_scale_float(const double s) :
458 scale(s)
459 {
460 }
461 };
462
463 template <char tag>
464 class hud_scale_float : base_hud_scale_float
465 {
466 public:
467 using scaled = hud_scaled_int<tag>;
468 using base_hud_scale_float::get;
469 DXX_INHERIT_CONSTRUCTORS(hud_scale_float, base_hud_scale_float);
operator ()(const int i) const470 scaled operator()(const int i) const
471 {
472 return scaled(this->base_hud_scale_float::operator()(i));
473 }
474 };
475
HUD_SCALE_X(const unsigned screen_width,const local_multires_gauge_graphic multires_gauge_graphic)476 static hud_x_scale_float HUD_SCALE_X(const unsigned screen_width, const local_multires_gauge_graphic multires_gauge_graphic)
477 {
478 return static_cast<double>(screen_width) / BASE_WIDTH(multires_gauge_graphic);
479 }
480
HUD_SCALE_Y(const unsigned screen_height,const local_multires_gauge_graphic multires_gauge_graphic)481 static hud_y_scale_float HUD_SCALE_Y(const unsigned screen_height, const local_multires_gauge_graphic multires_gauge_graphic)
482 {
483 return static_cast<double>(screen_height) / BASE_HEIGHT(multires_gauge_graphic);
484 }
485
HUD_SCALE_AR(const hud_x_scale_float x,const hud_y_scale_float y)486 static hud_ar_scale_float HUD_SCALE_AR(const hud_x_scale_float x, const hud_y_scale_float y)
487 {
488 return std::min(x.get(), y.get());
489 }
490
HUD_SCALE_AR(const unsigned screen_width,const unsigned screen_height,const local_multires_gauge_graphic multires_gauge_graphic)491 static hud_ar_scale_float HUD_SCALE_AR(const unsigned screen_width, const unsigned screen_height, const local_multires_gauge_graphic multires_gauge_graphic)
492 {
493 return HUD_SCALE_AR(HUD_SCALE_X(screen_width, multires_gauge_graphic), HUD_SCALE_Y(screen_height, multires_gauge_graphic));
494 }
495
496 #define draw_numerical_display_draw_context hud_draw_context_hs
497 #else
498 #define hud_bitblt_free(canvas,x,y,w,h,bm) hud_bitblt_free(canvas,x,y,bm)
499 #define draw_numerical_display_draw_context hud_draw_context_hs_mr
500
HUD_SCALE_AR(hud_x_scale_float,hud_y_scale_float)501 static hud_ar_scale_float HUD_SCALE_AR(hud_x_scale_float, hud_y_scale_float)
502 {
503 return {};
504 }
505
HUD_SCALE_AR(unsigned,unsigned,local_multires_gauge_graphic)506 static hud_ar_scale_float HUD_SCALE_AR(unsigned, unsigned, local_multires_gauge_graphic)
507 {
508 return {};
509 }
510 #endif
511
512 struct CockpitWeaponBoxFrameBitmaps : std::array<grs_subbitmap_ptr, 2> // Overlay subbitmaps for both weapon boxes
513 {
514 /* `decoded_full_cockpit_image` is a member instead of a local in
515 * the initializer function because it must remain allocated. It
516 * must remain allocated because the subbitmaps refer to the data
517 * managed by `decoded_full_cockpit_image`, so destroying it would
518 * leave the subbitmaps dangling. For OpenGL, the texture would be
519 * dangling. For SDL-only, the bm_data pointers would be dangling.
520 */
521 #if DXX_USE_OGL
522 /* Use bare grs_bitmap because the caller has special rules for
523 * managing the memory pointed at by `bm_data`.
524 */
525 grs_bitmap decoded_full_cockpit_image;
526 #else
527 /* Use grs_main_bitmap because the bitmap follows the standard
528 * rules.
529 */
530 grs_main_bitmap decoded_full_cockpit_image;
531 #endif
532 };
533
534 static CockpitWeaponBoxFrameBitmaps WinBoxOverlay;
535
536 }
537
538 #if defined(DXX_BUILD_DESCENT_I)
539 #define PAGE_IN_GAUGE(x,g) PAGE_IN_GAUGE(x)
540 std::array<bitmap_index, MAX_GAUGE_BMS_MAC> Gauges; // Array of all gauge bitmaps.
541 #elif defined(DXX_BUILD_DESCENT_II)
542 #define PAGE_IN_GAUGE PAGE_IN_GAUGE
543 std::array<bitmap_index, MAX_GAUGE_BMS> Gauges, // Array of all gauge bitmaps.
544 Gauges_hires; // hires gauges
545 #endif
546
547 namespace dsx {
PAGE_IN_GAUGE(int x,const local_multires_gauge_graphic multires_gauge_graphic)548 static inline void PAGE_IN_GAUGE(int x, const local_multires_gauge_graphic multires_gauge_graphic)
549 {
550 const auto &g =
551 #if defined(DXX_BUILD_DESCENT_II)
552 multires_gauge_graphic.is_hires() ? Gauges_hires :
553 #endif
554 Gauges;
555 PIGGY_PAGE_IN(g[x]);
556 }
557 }
558
559 static int score_display;
560 static fix score_time;
561 static laser_level old_laser_level;
562 static int invulnerable_frame;
563 int Color_0_31_0 = -1;
564
565 namespace dcx {
566
567 namespace {
568
569 struct hud_draw_context_canvas
570 {
571 grs_canvas &canvas;
hud_draw_context_canvasdcx::__anon808099050311::hud_draw_context_canvas572 hud_draw_context_canvas(grs_canvas &c) :
573 canvas(c)
574 {
575 }
576 };
577
578 struct hud_draw_context_multires
579 {
580 const local_multires_gauge_graphic multires_gauge_graphic;
hud_draw_context_multiresdcx::__anon808099050311::hud_draw_context_multires581 hud_draw_context_multires(const local_multires_gauge_graphic mr) :
582 multires_gauge_graphic(mr)
583 {
584 }
585 };
586
587 struct hud_draw_context_mr : hud_draw_context_canvas, hud_draw_context_multires
588 {
hud_draw_context_mrdcx::__anon808099050311::hud_draw_context_mr589 hud_draw_context_mr(grs_canvas &c, const local_multires_gauge_graphic mr) :
590 hud_draw_context_canvas(c), hud_draw_context_multires(mr)
591 {
592 }
593 };
594
595 struct hud_draw_context_xyscale
596 {
597 const hud_x_scale_float xscale;
598 const hud_y_scale_float yscale;
599 #if DXX_USE_OGL
hud_draw_context_xyscaledcx::__anon808099050311::hud_draw_context_xyscale600 constexpr hud_draw_context_xyscale(const hud_x_scale_float x, const hud_y_scale_float y) :
601 xscale(x), yscale(y)
602 {
603 }
604 #else
hud_draw_context_xyscaledcx::__anon808099050311::hud_draw_context_xyscale605 constexpr hud_draw_context_xyscale() :
606 xscale{}, yscale{}
607 {
608 }
609 #endif
610 };
611
612 struct hud_draw_context_hs_mr : hud_draw_context_mr, hud_draw_context_xyscale
613 {
614 #if DXX_USE_OGL
hud_draw_context_hs_mrdcx::__anon808099050311::hud_draw_context_hs_mr615 hud_draw_context_hs_mr(grs_canvas &c, const unsigned screen_width, const unsigned screen_height, const local_multires_gauge_graphic multires_gauge_graphic) :
616 hud_draw_context_mr(c, multires_gauge_graphic),
617 hud_draw_context_xyscale(HUD_SCALE_X(screen_width, multires_gauge_graphic), HUD_SCALE_Y(screen_height, multires_gauge_graphic))
618 {
619 }
620 #else
621 hud_draw_context_hs_mr(grs_canvas &c, unsigned, unsigned, const local_multires_gauge_graphic multires_gauge_graphic) :
622 hud_draw_context_mr(c, multires_gauge_graphic)
623 {
624 }
625 #endif
626 };
627
628 struct hud_draw_context_hs : hud_draw_context_canvas, hud_draw_context_xyscale
629 {
hud_draw_context_hsdcx::__anon808099050311::hud_draw_context_hs630 hud_draw_context_hs(const hud_draw_context_hs_mr &hudctx) :
631 hud_draw_context_canvas(hudctx.canvas), hud_draw_context_xyscale(hudctx)
632 {
633 }
634 };
635
636 struct gauge_box
637 {
638 int left,top;
639 int right,bot; //maximal box
640 };
641
642 enum class gauge_hud_type : uint_fast32_t
643 {
644 cockpit,
645 statusbar,
646 // fullscreen mode handled separately
647 };
648
649 constexpr enumerated_array<
650 enumerated_array<
651 enumerated_array<gauge_box, 2, gauge_hud_type>,
652 2, gauge_inset_window_view>,
653 2, gauge_screen_resolution> gauge_boxes = {{{
654 {{{
655 {{{
656 {PRIMARY_W_BOX_LEFT_L,PRIMARY_W_BOX_TOP_L,PRIMARY_W_BOX_RIGHT_L,PRIMARY_W_BOX_BOT_L},
657 {SB_PRIMARY_W_BOX_LEFT_L,SB_PRIMARY_W_BOX_TOP_L,SB_PRIMARY_W_BOX_RIGHT_L,SB_PRIMARY_W_BOX_BOT_L},
658 }}},
659
660 {{{
661 {SECONDARY_W_BOX_LEFT_L,SECONDARY_W_BOX_TOP_L,SECONDARY_W_BOX_RIGHT_L,SECONDARY_W_BOX_BOT_L},
662 {SB_SECONDARY_W_BOX_LEFT_L,SB_SECONDARY_W_BOX_TOP_L,SB_SECONDARY_W_BOX_RIGHT_L,SB_SECONDARY_W_BOX_BOT_L},
663 }}},
664 }}},
665
666 {{{
667 {{{
668 {PRIMARY_W_BOX_LEFT_H,PRIMARY_W_BOX_TOP_H,PRIMARY_W_BOX_RIGHT_H,PRIMARY_W_BOX_BOT_H},
669 {SB_PRIMARY_W_BOX_LEFT_H,SB_PRIMARY_W_BOX_TOP_H,SB_PRIMARY_W_BOX_RIGHT_H,SB_PRIMARY_W_BOX_BOT_H},
670 }}},
671
672 {{{
673 {SECONDARY_W_BOX_LEFT_H,SECONDARY_W_BOX_TOP_H,SECONDARY_W_BOX_RIGHT_H,SECONDARY_W_BOX_BOT_H},
674 {SB_SECONDARY_W_BOX_LEFT_H,SB_SECONDARY_W_BOX_TOP_H,SB_SECONDARY_W_BOX_RIGHT_H,SB_SECONDARY_W_BOX_BOT_H},
675 }}}
676 }}}
677 }}};
678
679 struct d_gauge_span
680 {
681 unsigned l, r;
682 };
683
684 struct dspan
685 {
686 d_gauge_span l, r;
687 };
688
689 //store delta x values from left of box
690 const std::array<dspan, 43> weapon_windows_lowres = {{
691 {{71,114}, {208,255}},
692 {{69,116}, {206,257}},
693 {{68,117}, {205,258}},
694 {{66,118}, {204,259}},
695 {{66,119}, {203,260}},
696 {{66,119}, {203,260}},
697 {{65,119}, {203,260}},
698 {{65,119}, {203,260}},
699 {{65,119}, {203,260}},
700 {{65,119}, {203,261}},
701 {{65,119}, {203,261}},
702 {{65,119}, {203,261}},
703 {{65,119}, {203,261}},
704 {{65,119}, {203,261}},
705 {{65,119}, {203,261}},
706 {{64,119}, {203,261}},
707 {{64,119}, {203,261}},
708 {{64,119}, {203,261}},
709 {{64,119}, {203,262}},
710 {{64,119}, {203,262}},
711 {{64,119}, {203,262}},
712 {{64,119}, {203,262}},
713 {{64,119}, {203,262}},
714 {{64,119}, {203,262}},
715 {{63,119}, {203,262}},
716 {{63,118}, {203,262}},
717 {{63,118}, {204,263}},
718 {{63,118}, {204,263}},
719 {{63,118}, {204,263}},
720 {{63,118}, {204,263}},
721 {{63,118}, {204,263}},
722 {{63,118}, {204,263}},
723 {{63,118}, {204,263}},
724 {{63,118}, {204,263}},
725 {{63,118}, {204,263}},
726 {{63,118}, {204,263}},
727 {{63,118}, {204,263}},
728 {{63,117}, {204,263}},
729 {{63,117}, {205,263}},
730 {{64,116}, {206,262}},
731 {{65,115}, {207,261}},
732 {{66,113}, {208,260}},
733 {{68,111}, {211,255}},
734 }};
735
736 //store delta x values from left of box
737 const std::array<dspan, 107> weapon_windows_hires = {{
738 {{141,231}, {416,509}},
739 {{139,234}, {413,511}},
740 {{137,235}, {412,513}},
741 {{136,237}, {410,514}},
742 {{135,238}, {409,515}},
743 {{134,239}, {408,516}},
744 {{133,240}, {407,517}},
745 {{132,240}, {407,518}},
746 {{131,241}, {406,519}},
747 {{131,241}, {406,519}},
748 {{130,242}, {405,520}},
749 {{129,242}, {405,521}},
750 {{129,242}, {405,521}},
751 {{129,243}, {404,521}},
752 {{128,243}, {404,522}},
753 {{128,243}, {404,522}},
754 {{128,243}, {404,522}},
755 {{128,243}, {404,522}},
756 {{128,243}, {404,522}},
757 {{127,243}, {404,523}},
758 {{127,243}, {404,523}},
759 {{127,243}, {404,523}},
760 {{127,243}, {404,523}},
761 {{127,243}, {404,523}},
762 {{127,243}, {404,523}},
763 {{127,243}, {404,523}},
764 {{127,243}, {404,523}},
765 {{127,243}, {404,523}},
766 {{127,243}, {404,523}},
767 {{126,243}, {404,524}},
768 {{126,243}, {404,524}},
769 {{126,243}, {404,524}},
770 {{126,243}, {404,524}},
771 {{126,242}, {405,524}},
772 {{126,242}, {405,524}},
773 {{126,242}, {405,524}},
774 {{126,242}, {405,524}},
775 {{126,242}, {405,524}},
776 {{126,242}, {405,524}},
777 {{125,242}, {405,525}},
778 {{125,242}, {405,525}},
779 {{125,242}, {405,525}},
780 {{125,242}, {405,525}},
781 {{125,242}, {405,525}},
782 {{125,242}, {405,525}},
783 {{125,242}, {405,525}},
784 {{125,242}, {405,525}},
785 {{125,242}, {405,525}},
786 {{125,242}, {405,525}},
787 {{124,242}, {405,526}},
788 {{124,242}, {405,526}},
789 {{124,241}, {406,526}},
790 {{124,241}, {406,526}},
791 {{124,241}, {406,526}},
792 {{124,241}, {406,526}},
793 {{124,241}, {406,526}},
794 {{124,241}, {406,526}},
795 {{124,241}, {406,526}},
796 {{124,241}, {406,526}},
797 {{124,241}, {406,527}},
798 {{123,241}, {406,527}},
799 {{123,241}, {406,527}},
800 {{123,241}, {406,527}},
801 {{123,241}, {406,527}},
802 {{123,241}, {406,527}},
803 {{123,241}, {406,527}},
804 {{123,241}, {406,527}},
805 {{123,241}, {406,527}},
806 {{123,241}, {406,527}},
807 {{123,241}, {406,527}},
808 {{123,241}, {406,527}},
809 {{123,241}, {406,527}},
810 {{122,241}, {406,528}},
811 {{122,241}, {406,528}},
812 {{122,240}, {407,528}},
813 {{122,240}, {407,528}},
814 {{122,240}, {407,528}},
815 {{122,240}, {407,528}},
816 {{122,240}, {407,528}},
817 {{122,240}, {407,528}},
818 {{122,240}, {407,528}},
819 {{122,240}, {407,529}},
820 {{121,240}, {407,529}},
821 {{121,240}, {407,529}},
822 {{121,240}, {407,529}},
823 {{121,240}, {407,529}},
824 {{121,240}, {407,529}},
825 {{121,240}, {407,529}},
826 {{121,240}, {407,529}},
827 {{121,239}, {408,529}},
828 {{121,239}, {408,529}},
829 {{121,239}, {408,529}},
830 {{121,238}, {409,529}},
831 {{121,238}, {409,529}},
832 {{121,238}, {409,529}},
833 {{122,237}, {410,529}},
834 {{122,237}, {410,528}},
835 {{123,236}, {411,527}},
836 {{123,235}, {412,527}},
837 {{124,234}, {413,526}},
838 {{125,233}, {414,525}},
839 {{126,232}, {415,524}},
840 {{126,231}, {416,524}},
841 {{128,230}, {417,522}},
842 {{130,228}, {419,521}},
843 {{131,226}, {422,519}},
844 {{133,223}, {424,518}},
845 }};
846
847 struct gauge_inset_window
848 {
849 fix fade_value = 0;
850 weapon_index old_weapon = {};
851 weapon_box_state box_state = weapon_box_state::set;
852 #if defined(DXX_BUILD_DESCENT_II)
853 weapon_box_user user = weapon_box_user::weapon;
854 uint8_t overlap_dirty = 0;
855 fix time_static_played = 0;
856 #endif
857 };
858
859 enumerated_array<gauge_inset_window, 2, gauge_inset_window_view> inset_window;
860
hud_bitblt_free(grs_canvas & canvas,const unsigned x,const unsigned y,const unsigned w,const unsigned h,grs_bitmap & bm)861 static inline void hud_bitblt_free(grs_canvas &canvas, const unsigned x, const unsigned y, const unsigned w, const unsigned h, grs_bitmap &bm)
862 {
863 #if DXX_USE_OGL
864 ogl_ubitmapm_cs(canvas, x, y, w, h, bm, ogl_colors::white);
865 #else
866 gr_ubitmapm(canvas, x, y, bm);
867 #endif
868 }
869
hud_bitblt_scaled_xy(const hud_draw_context_hs hudctx,const unsigned x,const unsigned y,grs_bitmap & bm)870 static void hud_bitblt_scaled_xy(const hud_draw_context_hs hudctx, const unsigned x, const unsigned y, grs_bitmap &bm)
871 {
872 hud_bitblt_free(hudctx.canvas, x, y, hudctx.xscale(bm.bm_w), hudctx.yscale(bm.bm_h), bm);
873 }
874
hud_bitblt(const hud_draw_context_hs hudctx,const unsigned x,const unsigned y,grs_bitmap & bm)875 static void hud_bitblt(const hud_draw_context_hs hudctx, const unsigned x, const unsigned y, grs_bitmap &bm)
876 {
877 hud_bitblt_scaled_xy(hudctx, hudctx.xscale(x), hudctx.yscale(y), bm);
878 }
879
880 }
881
882 }
883
884 namespace dsx {
885
886 namespace {
887
888 #if defined(DXX_BUILD_DESCENT_I)
889 #define hud_gauge_bitblt_draw_context hud_draw_context_hs
890 #elif defined(DXX_BUILD_DESCENT_II)
891 #define hud_gauge_bitblt_draw_context hud_draw_context_hs_mr
892 #endif
hud_gauge_bitblt(const hud_gauge_bitblt_draw_context hudctx,const unsigned x,const unsigned y,const unsigned gauge)893 static void hud_gauge_bitblt(const hud_gauge_bitblt_draw_context hudctx, const unsigned x, const unsigned y, const unsigned gauge)
894 {
895 #if defined(DXX_BUILD_DESCENT_II)
896 auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
897 #endif
898 PAGE_IN_GAUGE(gauge, multires_gauge_graphic);
899 hud_bitblt(hudctx, x, y, GameBitmaps[GET_GAUGE_INDEX(gauge)]);
900 }
901
902 class draw_keys_state
903 {
904 const hud_draw_context_hs_mr hudctx;
905 const player_flags player_key_flags;
906 public:
draw_keys_state(const hud_draw_context_hs_mr hc,const player_flags f)907 draw_keys_state(const hud_draw_context_hs_mr hc, const player_flags f) :
908 hudctx(hc), player_key_flags(f)
909 {
910 }
911 void draw_all_cockpit_keys();
912 void draw_all_statusbar_keys();
913 protected:
draw_one_key(const unsigned x,const unsigned y,const unsigned gauge,const PLAYER_FLAG flag) const914 void draw_one_key(const unsigned x, const unsigned y, const unsigned gauge, const PLAYER_FLAG flag) const
915 {
916 hud_gauge_bitblt(hudctx, x, y, (player_key_flags & flag) ? gauge : (gauge + 3));
917 }
918 };
919
920 }
921
922 }
923
924 namespace {
925
hud_show_score(grs_canvas & canvas,const player_info & player_info,const game_mode_flags Game_mode)926 static void hud_show_score(grs_canvas &canvas, const player_info &player_info, const game_mode_flags Game_mode)
927 {
928 char score_str[20];
929
930 if (HUD_toolong)
931 return;
932
933 const char *label;
934 int value;
935 if ((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP))
936 {
937 label = TXT_KILLS;
938 value = player_info.net_kills_total;
939 } else {
940 label = TXT_SCORE;
941 value = player_info.mission.score;
942 }
943 snprintf(score_str, sizeof(score_str), "%s: %5d", label, value);
944
945 if (Color_0_31_0 == -1)
946 Color_0_31_0 = BM_XRGB(0,31,0);
947 gr_set_fontcolor(canvas, Color_0_31_0, -1);
948
949 auto &game_font = *GAME_FONT;
950 const auto &&[w, h] = gr_get_string_size(game_font, score_str);
951 gr_string(canvas, game_font, canvas.cv_bitmap.bm_w - w - FSPACX(1), FSPACY(1), score_str, w, h);
952 }
953
hud_show_timer_count(grs_canvas & canvas,const game_mode_flags Game_mode)954 static void hud_show_timer_count(grs_canvas &canvas, const game_mode_flags Game_mode)
955 {
956 auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
957
958 if (HUD_toolong)
959 return;
960
961 if (!(Game_mode & GM_NETWORK))
962 return;
963
964 if (!Netgame.PlayTimeAllowed.count())
965 return;
966
967 if (LevelUniqueControlCenterState.Control_center_destroyed)
968 return;
969
970 if (Netgame.PlayTimeAllowed < ThisLevelTime)
971 return;
972
973 const auto TicksUntilPlayTimeAllowedElapses = Netgame.PlayTimeAllowed - ThisLevelTime;
974 const auto SecondsUntilPlayTimeAllowedElapses = f2i(TicksUntilPlayTimeAllowedElapses.count());
975 if (SecondsUntilPlayTimeAllowedElapses >= 0)
976 {
977 if (Color_0_31_0 == -1)
978 Color_0_31_0 = BM_XRGB(0,31,0);
979
980 gr_set_fontcolor(canvas, Color_0_31_0, -1);
981
982 char score_str[20];
983 snprintf(score_str, sizeof(score_str), "T - %5d", SecondsUntilPlayTimeAllowedElapses + 1);
984 const auto &&[w, h] = gr_get_string_size(*canvas.cv_font, score_str);
985 gr_string(canvas, *canvas.cv_font, canvas.cv_bitmap.bm_w - w - FSPACX(12), LINE_SPACING(*canvas.cv_font, *GAME_FONT) + FSPACY(1), score_str, w, h);
986 }
987 }
988
hud_show_score_added(grs_canvas & canvas,const game_mode_flags Game_mode)989 static void hud_show_score_added(grs_canvas &canvas, const game_mode_flags Game_mode)
990 {
991 int color;
992
993 if ((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP))
994 return;
995
996 if (score_display == 0)
997 return;
998
999 score_time -= FrameTime;
1000 if (score_time > 0) {
1001 color = f2i(score_time * 20) + 12;
1002
1003 if (color < 10) color = 12;
1004 if (color > 31) color = 30;
1005
1006 color = color - (color % 4);
1007
1008 char score_buf[32];
1009 const auto score_str = cheats.enabled
1010 ? TXT_CHEATER
1011 : (snprintf(score_buf, sizeof(score_buf), "%5d", score_display), score_buf);
1012
1013 gr_set_fontcolor(canvas, BM_XRGB(0, color, 0), -1);
1014 auto &game_font = *GAME_FONT;
1015 const auto &&[w, h] = gr_get_string_size(game_font, score_str);
1016 gr_string(canvas, game_font, canvas.cv_bitmap.bm_w - w - FSPACX(PlayerCfg.CockpitMode[1] == CM_FULL_SCREEN ? 1 : 12), LINE_SPACING(game_font, game_font) + FSPACY(1), score_str, w, h);
1017 } else {
1018 score_time = 0;
1019 score_display = 0;
1020 }
1021 }
1022
sb_show_score(const hud_draw_context_hs_mr hudctx,const player_info & player_info,const bool is_multiplayer_non_cooperative)1023 static void sb_show_score(const hud_draw_context_hs_mr hudctx, const player_info &player_info, const bool is_multiplayer_non_cooperative)
1024 {
1025 char score_str[20];
1026
1027 auto &canvas = hudctx.canvas;
1028 gr_set_fontcolor(canvas, BM_XRGB(0, 20, 0), -1);
1029
1030 auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
1031 const auto y = hudctx.yscale(SB_SCORE_Y);
1032 auto &game_font = *GAME_FONT;
1033 gr_printf(canvas, game_font, hudctx.xscale(SB_SCORE_LABEL_X), y, "%s:", is_multiplayer_non_cooperative ? TXT_KILLS : TXT_SCORE);
1034
1035 snprintf(score_str, sizeof(score_str), "%5d",
1036 is_multiplayer_non_cooperative
1037 ? player_info.net_kills_total
1038 : (gr_set_fontcolor(canvas, BM_XRGB(0, 31, 0), -1), player_info.mission.score));
1039 const auto &&[w, h] = gr_get_string_size(game_font, score_str);
1040
1041 const auto scaled_score_right = hudctx.xscale(SB_SCORE_RIGHT);
1042 const auto x = scaled_score_right - w - FSPACX(1);
1043
1044 //erase old score
1045 const uint8_t color = BM_XRGB(0, 0, 0);
1046 gr_rect(canvas, x, y, scaled_score_right, y + LINE_SPACING(game_font, game_font), color);
1047
1048 gr_string(canvas, game_font, x, y, score_str, w, h);
1049 }
1050
sb_show_score_added(const hud_draw_context_hs_mr hudctx)1051 static void sb_show_score_added(const hud_draw_context_hs_mr hudctx)
1052 {
1053 static int x;
1054 static int last_score_display = -1;
1055
1056 if (score_display == 0)
1057 return;
1058
1059 auto &canvas = hudctx.canvas;
1060 auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
1061
1062 score_time -= FrameTime;
1063 if (score_time > 0) {
1064 if (score_display != last_score_display)
1065 last_score_display = score_display;
1066
1067 int color;
1068 color = f2i(score_time * 20) + 10;
1069
1070 if (color < 10) color = 10;
1071 if (color > 31) color = 31;
1072
1073 char score_buf[32];
1074 const auto score_str = cheats.enabled
1075 ? TXT_CHEATER
1076 : (snprintf(score_buf, sizeof(score_buf), "%5d", score_display), score_buf);
1077
1078 auto &game_font = *GAME_FONT;
1079 const auto &&[w, h] = gr_get_string_size(game_font, score_str);
1080 x = hudctx.xscale(SB_SCORE_ADDED_RIGHT) - w - FSPACX(1);
1081 gr_set_fontcolor(canvas, BM_XRGB(0, color, 0), -1);
1082 gr_string(canvas, game_font, x, hudctx.yscale(SB_SCORE_ADDED_Y), score_str, w, h);
1083 } else {
1084 //erase old score
1085 const uint8_t color = BM_XRGB(0, 0, 0);
1086 const auto scaled_score_y = hudctx.yscale(SB_SCORE_ADDED_Y);
1087 gr_rect(canvas, x, scaled_score_y, hudctx.xscale(SB_SCORE_ADDED_RIGHT), scaled_score_y + LINE_SPACING(*canvas.cv_font, *GAME_FONT), color);
1088 score_time = 0;
1089 score_display = 0;
1090 }
1091 }
1092
1093 }
1094
1095 namespace dsx {
1096
1097 // -----------------------------------------------------------------------------
play_homing_warning(const player_info & player_info)1098 void play_homing_warning(const player_info &player_info)
1099 {
1100 fix beep_delay;
1101 static fix64 Last_warning_beep_time = 0; // Time we last played homing missile warning beep.
1102
1103 if (Endlevel_sequence || Player_dead_state != player_dead_state::no)
1104 return;
1105
1106 const auto homing_object_dist = player_info.homing_object_dist;
1107 if (homing_object_dist >= 0) {
1108 beep_delay = homing_object_dist / 128;
1109 if (beep_delay > F1_0)
1110 beep_delay = F1_0;
1111 else if (beep_delay < F1_0/8)
1112 beep_delay = F1_0/8;
1113
1114 if (GameTime64 - Last_warning_beep_time > beep_delay/2 || Last_warning_beep_time > GameTime64) {
1115 digi_play_sample( SOUND_HOMING_WARNING, F1_0 );
1116 Last_warning_beep_time = GameTime64;
1117 }
1118 }
1119 }
1120
1121 }
1122
1123 namespace {
1124
1125 // -----------------------------------------------------------------------------
show_homing_warning(const hud_draw_context_hs_mr hudctx,const int homing_object_dist)1126 static void show_homing_warning(const hud_draw_context_hs_mr hudctx, const int homing_object_dist)
1127 {
1128 unsigned gauge;
1129 if (Endlevel_sequence)
1130 {
1131 gauge = GAUGE_HOMING_WARNING_OFF;
1132 }
1133 else
1134 {
1135 gauge = ((GameTime64 & 0x4000) && homing_object_dist >= 0)
1136 ? GAUGE_HOMING_WARNING_ON
1137 : GAUGE_HOMING_WARNING_OFF;
1138 }
1139 auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
1140 hud_gauge_bitblt(hudctx, HOMING_WARNING_X, HOMING_WARNING_Y, gauge);
1141 }
1142
hud_show_homing_warning(grs_canvas & canvas,const int homing_object_dist)1143 static void hud_show_homing_warning(grs_canvas &canvas, const int homing_object_dist)
1144 {
1145 if (homing_object_dist >= 0)
1146 {
1147 if (GameTime64 & 0x4000) {
1148 gr_set_fontcolor(canvas, BM_XRGB(0, 31, 0), -1);
1149 auto &game_font = *GAME_FONT;
1150 gr_string(canvas, game_font, 0x8000, canvas.cv_bitmap.bm_h - LINE_SPACING(*canvas.cv_font, *GAME_FONT), TXT_LOCK);
1151 }
1152 }
1153 }
1154
hud_show_keys(const hud_draw_context_mr hudctx,const hud_ar_scale_float hud_scale_ar,const player_info & player_info)1155 static void hud_show_keys(const hud_draw_context_mr hudctx, const hud_ar_scale_float hud_scale_ar, const player_info &player_info)
1156 {
1157 const auto player_key_flags = player_info.powerup_flags;
1158 if (!(player_key_flags & (PLAYER_FLAGS_BLUE_KEY | PLAYER_FLAGS_GOLD_KEY | PLAYER_FLAGS_RED_KEY)))
1159 return;
1160 class gauge_key
1161 {
1162 grs_bitmap *const bm;
1163 public:
1164 gauge_key(const unsigned key_icon, const local_multires_gauge_graphic multires_gauge_graphic) :
1165 bm(&GameBitmaps[static_cast<void>(multires_gauge_graphic), PAGE_IN_GAUGE(key_icon, multires_gauge_graphic), GET_GAUGE_INDEX(key_icon)])
1166 {
1167 }
1168 grs_bitmap *operator->() const
1169 {
1170 return bm;
1171 }
1172 operator grs_bitmap &() const
1173 {
1174 return *bm;
1175 }
1176 };
1177 auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
1178 const gauge_key blue(KEY_ICON_BLUE, multires_gauge_graphic);
1179 const unsigned y = hud_scale_ar(GameBitmaps[ GET_GAUGE_INDEX(GAUGE_LIVES) ].bm_h + 2) + FSPACY(1);
1180
1181 const unsigned blue_bitmap_width = blue->bm_w;
1182 const auto &&fspacx2 = FSPACX(2);
1183 auto &canvas = hudctx.canvas;
1184 if (player_key_flags & PLAYER_FLAGS_BLUE_KEY)
1185 hud_bitblt_free(canvas, fspacx2, y, hud_scale_ar(blue_bitmap_width), hud_scale_ar(blue->bm_h), blue);
1186
1187 if (!(player_key_flags & (PLAYER_FLAGS_GOLD_KEY | PLAYER_FLAGS_RED_KEY)))
1188 return;
1189 const gauge_key yellow(KEY_ICON_YELLOW, multires_gauge_graphic);
1190 const unsigned yellow_bitmap_width = yellow->bm_w;
1191 if (player_key_flags & PLAYER_FLAGS_GOLD_KEY)
1192 hud_bitblt_free(canvas, fspacx2 + hud_scale_ar(blue_bitmap_width + 3), y, hud_scale_ar(yellow_bitmap_width), hud_scale_ar(yellow->bm_h), yellow);
1193
1194 if (player_key_flags & PLAYER_FLAGS_RED_KEY)
1195 {
1196 const gauge_key red(KEY_ICON_RED, multires_gauge_graphic);
1197 hud_bitblt_free(canvas, fspacx2 + hud_scale_ar(blue_bitmap_width + yellow_bitmap_width + 6), y, hud_scale_ar(red->bm_w), hud_scale_ar(red->bm_h), red);
1198 }
1199 }
1200
1201 #if defined(DXX_BUILD_DESCENT_II)
hud_show_orbs(grs_canvas & canvas,const player_info & player_info,const local_multires_gauge_graphic multires_gauge_graphic)1202 static void hud_show_orbs(grs_canvas &canvas, const player_info &player_info, const local_multires_gauge_graphic multires_gauge_graphic)
1203 {
1204 if (game_mode_hoard()) {
1205 const auto &&fspacy1 = FSPACY(1);
1206 int x, y = LINE_SPACING(*canvas.cv_font, *GAME_FONT) + fspacy1;
1207 const auto &&hud_scale_ar = HUD_SCALE_AR(grd_curscreen->get_screen_width(), grd_curscreen->get_screen_height(), multires_gauge_graphic);
1208 if (PlayerCfg.CockpitMode[1] == CM_FULL_COCKPIT) {
1209 x = (SWIDTH/18);
1210 }
1211 else
1212 {
1213 x = FSPACX(2);
1214 if (PlayerCfg.CockpitMode[1] == CM_STATUS_BAR) {
1215 }
1216 else if (PlayerCfg.CockpitMode[1] == CM_FULL_SCREEN) {
1217 y = hud_scale_ar(GameBitmaps[ GET_GAUGE_INDEX(GAUGE_LIVES) ].bm_h + GameBitmaps[ GET_GAUGE_INDEX(KEY_ICON_RED) ].bm_h + 4) + fspacy1;
1218 }
1219 else
1220 {
1221 Int3(); //what sort of cockpit?
1222 return;
1223 }
1224 }
1225
1226 gr_set_fontcolor(canvas, BM_XRGB(0, 31, 0), -1);
1227 auto &bm = Orb_icons[multires_gauge_graphic.is_hires()];
1228 const auto &&scaled_width = hud_scale_ar(bm.bm_w);
1229 hud_bitblt_free(canvas, x, y, scaled_width, hud_scale_ar(bm.bm_h), bm);
1230 gr_printf(canvas, *canvas.cv_font, x + scaled_width, y, " x %d", player_info.hoard.orbs);
1231 }
1232 }
1233
hud_show_flag(grs_canvas & canvas,const player_info & player_info,const local_multires_gauge_graphic multires_gauge_graphic)1234 static void hud_show_flag(grs_canvas &canvas, const player_info &player_info, const local_multires_gauge_graphic multires_gauge_graphic)
1235 {
1236 if (game_mode_capture_flag() && (player_info.powerup_flags & PLAYER_FLAGS_FLAG)) {
1237 int x, y = GameBitmaps[ GET_GAUGE_INDEX(GAUGE_LIVES) ].bm_h + 2, icon;
1238 const auto &&fspacy1 = FSPACY(1);
1239 if (PlayerCfg.CockpitMode[1] == CM_FULL_COCKPIT) {
1240 x = (SWIDTH/10);
1241 }
1242 else
1243 {
1244 x = FSPACX(2);
1245 if (PlayerCfg.CockpitMode[1] == CM_STATUS_BAR) {
1246 }
1247 else if (PlayerCfg.CockpitMode[1] == CM_FULL_SCREEN) {
1248 y += GameBitmaps[GET_GAUGE_INDEX(KEY_ICON_RED)].bm_h + 2;
1249 }
1250 else
1251 {
1252 Int3(); //what sort of cockpit?
1253 return;
1254 }
1255 }
1256
1257 icon = (get_team(Player_num) == TEAM_BLUE)?FLAG_ICON_RED:FLAG_ICON_BLUE;
1258 auto &bm = GameBitmaps[GET_GAUGE_INDEX(icon)];
1259 PAGE_IN_GAUGE(icon, multires_gauge_graphic);
1260 const auto &&hud_scale_ar = HUD_SCALE_AR(grd_curscreen->get_screen_width(), grd_curscreen->get_screen_height(), multires_gauge_graphic);
1261 hud_bitblt_free(canvas, x, hud_scale_ar(y) + fspacy1, hud_scale_ar(bm.bm_w), hud_scale_ar(bm.bm_h), bm);
1262 }
1263 }
1264 #endif
1265
hud_show_energy(grs_canvas & canvas,const player_info & player_info,const grs_font & game_font,const unsigned current_y)1266 static void hud_show_energy(grs_canvas &canvas, const player_info &player_info, const grs_font &game_font, const unsigned current_y)
1267 {
1268 auto &energy = player_info.energy;
1269 if (PlayerCfg.HudMode == HudType::Standard || PlayerCfg.HudMode == HudType::Alternate1)
1270 {
1271 gr_set_fontcolor(canvas, BM_XRGB(0, 31, 0), -1);
1272 gr_printf(canvas, game_font, FSPACX(1), current_y, "%s: %i", TXT_ENERGY, f2ir(energy));
1273 }
1274
1275 if (Newdemo_state == ND_STATE_RECORDING)
1276 newdemo_record_player_energy(f2ir(energy));
1277 }
1278
1279 #if defined(DXX_BUILD_DESCENT_I)
1280 #define convert_1s(s)
1281 #elif defined(DXX_BUILD_DESCENT_II)
hud_show_afterburner(grs_canvas & canvas,const player_info & player_info,const grs_font & game_font,const unsigned current_y)1282 static void hud_show_afterburner(grs_canvas &canvas, const player_info &player_info, const grs_font &game_font, const unsigned current_y)
1283 {
1284 if (! (player_info.powerup_flags & PLAYER_FLAGS_AFTERBURNER))
1285 return; //don't draw if don't have
1286
1287 gr_set_fontcolor(canvas, BM_XRGB(0, 31, 0), -1);
1288 gr_printf(canvas, game_font, FSPACX(1), current_y, "burn: %d%%" , fixmul(Afterburner_charge, 100));
1289
1290 if (Newdemo_state==ND_STATE_RECORDING )
1291 newdemo_record_player_afterburner(Afterburner_charge);
1292 }
1293
1294 //convert '1' characters to special wide ones
1295 #define convert_1s(s) do {char *p=s; while ((p=strchr(p,'1')) != NULL) *p=132;} while(0)
1296 #endif
1297
SECONDARY_WEAPON_NAMES_VERY_SHORT(const unsigned u)1298 static inline const char *SECONDARY_WEAPON_NAMES_VERY_SHORT(const unsigned u)
1299 {
1300 switch(u)
1301 {
1302 default:
1303 Int3();
1304 DXX_BOOST_FALLTHROUGH;
1305 case CONCUSSION_INDEX: return TXT_CONCUSSION;
1306 case HOMING_INDEX: return TXT_HOMING;
1307 case PROXIMITY_INDEX: return TXT_PROXBOMB;
1308 case SMART_INDEX: return TXT_SMART;
1309 case MEGA_INDEX: return TXT_MEGA;
1310 #if defined(DXX_BUILD_DESCENT_II)
1311 case SMISSILE1_INDEX: return "Flash";
1312 case GUIDED_INDEX: return "Guided";
1313 case SMART_MINE_INDEX: return "SmrtMine";
1314 case SMISSILE4_INDEX: return "Mercury";
1315 case SMISSILE5_INDEX: return "Shaker";
1316 #endif
1317 }
1318 }
1319
1320 }
1321
1322 namespace dsx {
1323
1324 namespace {
1325
show_bomb_count(grs_canvas & canvas,const player_info & player_info,const int x,const int y,const int bg_color,const int always_show,const int right_align)1326 static void show_bomb_count(grs_canvas &canvas, const player_info &player_info, const int x, const int y, const int bg_color, const int always_show, const int right_align)
1327 {
1328 #if defined(DXX_BUILD_DESCENT_I)
1329 if (!PlayerCfg.BombGauge)
1330 return;
1331 #endif
1332
1333 const auto bomb = which_bomb(player_info);
1334 int count = player_info.secondary_ammo[bomb];
1335
1336 count = min(count,99); //only have room for 2 digits - cheating give 200
1337
1338 if (always_show && count == 0) //no bombs, draw nothing on HUD
1339 return;
1340
1341 gr_set_fontcolor(canvas, count
1342 ? (bomb == PROXIMITY_INDEX
1343 ? gr_find_closest_color(55, 0, 0)
1344 : BM_XRGB(59, 50, 21)
1345 )
1346 : bg_color, //erase by drawing in background color
1347 bg_color);
1348
1349 char txt[5];
1350 snprintf(txt, sizeof(txt), "B:%02d", count);
1351 //convert to wide '1'
1352 std::replace(&txt[2], &txt[4], '1', '\x84');
1353
1354 const auto &&[w, h] = gr_get_string_size(*canvas.cv_font, txt);
1355 gr_string(canvas, *canvas.cv_font, right_align ? x - w : x, y, txt, w, h);
1356 }
1357 }
1358 }
1359
draw_primary_ammo_info(const hud_draw_context_hs_mr hudctx,const unsigned ammo_count)1360 static void draw_primary_ammo_info(const hud_draw_context_hs_mr hudctx, const unsigned ammo_count)
1361 {
1362 int x, y;
1363 auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
1364 if (PlayerCfg.CockpitMode[1] == CM_STATUS_BAR)
1365 x = SB_PRIMARY_AMMO_X, y = SB_PRIMARY_AMMO_Y;
1366 else
1367 x = PRIMARY_AMMO_X, y = PRIMARY_AMMO_Y;
1368 draw_ammo_info(hudctx.canvas, hudctx.xscale(x), hudctx.yscale(y), ammo_count);
1369 }
1370
1371 namespace dcx {
1372 namespace {
1373
1374 constexpr rgb_t hud_rgb_red = {40, 0, 0};
1375 constexpr rgb_t hud_rgb_green = {0, 30, 0};
1376 constexpr rgb_t hud_rgb_dimgreen = {0, 12, 0};
1377 constexpr rgb_t hud_rgb_gray = {6, 6, 6};
1378 }
1379 }
1380
1381 namespace dsx {
1382 #if defined(DXX_BUILD_DESCENT_II)
1383 constexpr rgb_t hud_rgb_yellow = {30, 30, 0};
1384 #endif
1385
1386 namespace {
1387
1388 [[nodiscard]]
hud_get_primary_weapon_fontcolor(const player_info & player_info,const primary_weapon_index_t consider_weapon)1389 static rgb_t hud_get_primary_weapon_fontcolor(const player_info &player_info, const primary_weapon_index_t consider_weapon)
1390 {
1391 if (player_info.Primary_weapon == consider_weapon)
1392 return hud_rgb_red;
1393 else{
1394 if (player_has_primary_weapon(player_info, consider_weapon).has_weapon())
1395 {
1396 #if defined(DXX_BUILD_DESCENT_II)
1397 const auto is_super = (consider_weapon >= 5);
1398 const int base_weapon = is_super ? consider_weapon - 5 : consider_weapon;
1399 if (player_info.Primary_last_was_super & (1 << base_weapon))
1400 {
1401 if (is_super)
1402 return hud_rgb_green;
1403 else
1404 return hud_rgb_yellow;
1405 }
1406 else if (is_super)
1407 return hud_rgb_yellow;
1408 else
1409 #endif
1410 return hud_rgb_green;
1411 }
1412 else
1413 return hud_rgb_gray;
1414 }
1415 }
1416
hud_set_primary_weapon_fontcolor(const player_info & player_info,const primary_weapon_index_t consider_weapon,grs_canvas & canvas)1417 static void hud_set_primary_weapon_fontcolor(const player_info &player_info, const primary_weapon_index_t consider_weapon, grs_canvas &canvas)
1418 {
1419 auto rgb = hud_get_primary_weapon_fontcolor(player_info, consider_weapon);
1420 gr_set_fontcolor(canvas, gr_find_closest_color(rgb.r, rgb.g, rgb.b), -1);
1421 }
1422
1423 [[nodiscard]]
hud_get_secondary_weapon_fontcolor(const player_info & player_info,const int consider_weapon)1424 static rgb_t hud_get_secondary_weapon_fontcolor(const player_info &player_info, const int consider_weapon)
1425 {
1426 if (player_info.Secondary_weapon == consider_weapon)
1427 return hud_rgb_red;
1428 else{
1429 if (player_info.secondary_ammo[consider_weapon])
1430 {
1431 #if defined(DXX_BUILD_DESCENT_II)
1432 const auto is_super = (consider_weapon >= 5);
1433 const int base_weapon = is_super ? consider_weapon - 5 : consider_weapon;
1434 if (player_info.Secondary_last_was_super & (1 << base_weapon))
1435 {
1436 if (is_super)
1437 return hud_rgb_green;
1438 else
1439 return hud_rgb_yellow;
1440 }
1441 else if (is_super)
1442 return hud_rgb_yellow;
1443 else
1444 #endif
1445 return hud_rgb_green;
1446 }
1447 else
1448 return hud_rgb_dimgreen;
1449 }
1450 }
1451
hud_set_secondary_weapon_fontcolor(const player_info & player_info,const unsigned consider_weapon,grs_canvas & canvas)1452 static void hud_set_secondary_weapon_fontcolor(const player_info &player_info, const unsigned consider_weapon, grs_canvas &canvas)
1453 {
1454 auto rgb = hud_get_secondary_weapon_fontcolor(player_info, consider_weapon);
1455 gr_set_fontcolor(canvas, gr_find_closest_color(rgb.r, rgb.g, rgb.b), -1);
1456 }
1457
1458 [[nodiscard]]
hud_get_vulcan_ammo_fontcolor(const player_info & player_info,const unsigned has_weapon_uses_vulcan_ammo)1459 static rgb_t hud_get_vulcan_ammo_fontcolor(const player_info &player_info, const unsigned has_weapon_uses_vulcan_ammo)
1460 {
1461 if (weapon_index_uses_vulcan_ammo(player_info.Primary_weapon))
1462 return hud_rgb_red;
1463 else if (has_weapon_uses_vulcan_ammo)
1464 return hud_rgb_green;
1465 else
1466 return hud_rgb_gray;
1467 }
1468
hud_set_vulcan_ammo_fontcolor(const player_info & player_info,const unsigned has_weapon_uses_vulcan_ammo,grs_canvas & canvas)1469 static void hud_set_vulcan_ammo_fontcolor(const player_info &player_info, const unsigned has_weapon_uses_vulcan_ammo, grs_canvas &canvas)
1470 {
1471 auto rgb = hud_get_vulcan_ammo_fontcolor(player_info, has_weapon_uses_vulcan_ammo);
1472 gr_set_fontcolor(canvas, gr_find_closest_color(rgb.r, rgb.g, rgb.b), -1);
1473 }
1474
hud_printf_vulcan_ammo(grs_canvas & canvas,const player_info & player_info,const int x,const int y)1475 static void hud_printf_vulcan_ammo(grs_canvas &canvas, const player_info &player_info, const int x, const int y)
1476 {
1477 const unsigned primary_weapon_flags = player_info.primary_weapon_flags;
1478 const auto vulcan_mask = HAS_VULCAN_FLAG;
1479 #if defined(DXX_BUILD_DESCENT_I)
1480 const auto gauss_mask = vulcan_mask;
1481 #elif defined(DXX_BUILD_DESCENT_II)
1482 const auto gauss_mask = HAS_GAUSS_FLAG;
1483 #endif
1484 const auto fmt_vulcan_ammo = vulcan_ammo_scale(player_info.vulcan_ammo);
1485 const unsigned has_weapon_uses_vulcan_ammo = (primary_weapon_flags & (gauss_mask | vulcan_mask));
1486 if (!has_weapon_uses_vulcan_ammo && !fmt_vulcan_ammo)
1487 return;
1488 hud_set_vulcan_ammo_fontcolor(player_info, has_weapon_uses_vulcan_ammo, canvas);
1489 const char c =
1490 #if defined(DXX_BUILD_DESCENT_II)
1491 ((primary_weapon_flags & gauss_mask) && ((player_info.Primary_last_was_super & (1 << primary_weapon_index_t::VULCAN_INDEX)) || !(primary_weapon_flags & vulcan_mask)))
1492 ? 'G'
1493 :
1494 #endif
1495 (primary_weapon_flags & vulcan_mask)
1496 ? 'V'
1497 : 'A'
1498 ;
1499 gr_printf(canvas, *canvas.cv_font, x, y, "%c:%u", c, fmt_vulcan_ammo);
1500 }
1501
hud_show_primary_weapons_mode(grs_canvas & canvas,const player_info & player_info,const int vertical,const int orig_x,const int orig_y)1502 static void hud_show_primary_weapons_mode(grs_canvas &canvas, const player_info &player_info, const int vertical, const int orig_x, const int orig_y)
1503 {
1504 int x=orig_x,y=orig_y;
1505
1506 const auto &&line_spacing = LINE_SPACING(*canvas.cv_font, *GAME_FONT);
1507 if (vertical){
1508 y += line_spacing * 4;
1509 }
1510
1511 const auto &&fspacx = FSPACX();
1512 const auto &&fspacx3 = fspacx(3);
1513 const auto &&fspacy2 = FSPACY(2);
1514 {
1515 for (uint_fast32_t ui = 5; ui --;)
1516 {
1517 const auto i = static_cast<primary_weapon_index_t>(ui);
1518 const char *txtweapon;
1519 char weapon_str[10];
1520 hud_set_primary_weapon_fontcolor(player_info, i, canvas);
1521 switch(i)
1522 {
1523 case primary_weapon_index_t::LASER_INDEX:
1524 {
1525 snprintf(weapon_str, sizeof(weapon_str), "%c%u", (player_info.powerup_flags & PLAYER_FLAGS_QUAD_LASERS) ? 'Q' : 'L', static_cast<unsigned>(player_info.laser_level) + 1);
1526 txtweapon = weapon_str;
1527 }
1528 break;
1529 case primary_weapon_index_t::VULCAN_INDEX:
1530 txtweapon = "V";
1531 break;
1532 case primary_weapon_index_t::SPREADFIRE_INDEX:
1533 txtweapon = "S";
1534 break;
1535 case primary_weapon_index_t::PLASMA_INDEX:
1536 txtweapon = "P";
1537 break;
1538 case primary_weapon_index_t::FUSION_INDEX:
1539 txtweapon = "F";
1540 break;
1541 default:
1542 continue;
1543 }
1544 const auto &&[w, h] = gr_get_string_size(*canvas.cv_font, txtweapon);
1545 if (vertical){
1546 y -= h + fspacy2;
1547 }else
1548 x -= w + fspacx3;
1549 gr_string(canvas, *canvas.cv_font, x, y, txtweapon, w, h);
1550 if (i == primary_weapon_index_t::VULCAN_INDEX)
1551 {
1552 /*
1553 * In Descent 1, this will always draw the ammo, but the
1554 * position depends on fullscreen and, if in fullscreen,
1555 * whether in vertical mode.
1556 *
1557 * In Descent 2, this will draw in non-fullscreen and in
1558 * fullscreen non-vertical, but not in fullscreen
1559 * vertical. The fullscreen vertical case is handled
1560 * specially in a large Descent2 block below.
1561 */
1562 int vx, vy;
1563 if (PlayerCfg.CockpitMode[1] == CM_FULL_SCREEN ? (
1564 vertical ? (
1565 #if defined(DXX_BUILD_DESCENT_I)
1566 vx = x, vy = y, true
1567 #else
1568 false
1569 #endif
1570 ) : (
1571 vx = x, vy = y - line_spacing, true
1572 )
1573 ) : (
1574 vx = x - (w + fspacx3), vy = y - ((h + fspacy2) * 2), true
1575 )
1576 )
1577 hud_printf_vulcan_ammo(canvas, player_info, vx, vy);
1578 }
1579 }
1580 }
1581 #if defined(DXX_BUILD_DESCENT_II)
1582 x = orig_x;
1583 y = orig_y;
1584 if (vertical)
1585 {
1586 x += fspacx(15);
1587 y += line_spacing * 4;
1588 }
1589 else
1590 {
1591 y += line_spacing;
1592 }
1593
1594 {
1595 for (uint_fast32_t ui = 10; ui -- != 5;)
1596 {
1597 const auto i = static_cast<primary_weapon_index_t>(ui);
1598 const char *txtweapon;
1599 char weapon_str[10];
1600 hud_set_primary_weapon_fontcolor(player_info, i, canvas);
1601 switch(i)
1602 {
1603 case primary_weapon_index_t::SUPER_LASER_INDEX:
1604 txtweapon = " ";
1605 break;
1606 case primary_weapon_index_t::GAUSS_INDEX:
1607 txtweapon = "G";
1608 break;
1609 case primary_weapon_index_t::HELIX_INDEX:
1610 txtweapon = "H";
1611 break;
1612 case primary_weapon_index_t::PHOENIX_INDEX:
1613 txtweapon = "P";
1614 break;
1615 case primary_weapon_index_t::OMEGA_INDEX:
1616 if (PlayerCfg.CockpitMode[1] == CM_FULL_SCREEN && (player_info.primary_weapon_flags & HAS_OMEGA_FLAG))
1617 {
1618 snprintf(weapon_str, sizeof(weapon_str), "O%3i", player_info.Omega_charge * 100 / MAX_OMEGA_CHARGE);
1619 txtweapon = weapon_str;
1620 }
1621 else
1622 txtweapon = "O";
1623 break;
1624 default:
1625 continue;
1626 }
1627 const auto &&[w, h] = gr_get_string_size(*canvas.cv_font, txtweapon);
1628 if (vertical){
1629 y -= h + fspacy2;
1630 }else
1631 x -= w + fspacx3;
1632 if (i == primary_weapon_index_t::SUPER_LASER_INDEX)
1633 {
1634 if (vertical && (PlayerCfg.CockpitMode[1]==CM_FULL_SCREEN))
1635 hud_printf_vulcan_ammo(canvas, player_info, x, y);
1636 continue;
1637 }
1638 gr_string(canvas, *canvas.cv_font, x, y, txtweapon, w, h);
1639 }
1640 }
1641 #endif
1642 gr_set_fontcolor(canvas, BM_XRGB(0, 31, 0), -1);
1643 }
1644
hud_show_secondary_weapons_mode(grs_canvas & canvas,const player_info & player_info,const unsigned vertical,const int orig_x,const int orig_y)1645 static void hud_show_secondary_weapons_mode(grs_canvas &canvas, const player_info &player_info, const unsigned vertical, const int orig_x, const int orig_y)
1646 {
1647 int x=orig_x,y=orig_y;
1648
1649 const auto &&line_spacing = LINE_SPACING(*canvas.cv_font, *GAME_FONT);
1650 if (vertical){
1651 y += line_spacing * 4;
1652 }
1653
1654 const auto &&fspacx = FSPACX();
1655 const auto &&fspacx3 = fspacx(3);
1656 const auto &&fspacy2 = FSPACY(2);
1657 auto &secondary_ammo = player_info.secondary_ammo;
1658 {
1659 for (uint_fast32_t ui = 5; ui --;)
1660 {
1661 const auto i = static_cast<secondary_weapon_index_t>(ui);
1662 char weapon_str[10];
1663 hud_set_secondary_weapon_fontcolor(player_info, i, canvas);
1664 snprintf(weapon_str, sizeof(weapon_str), "%i", secondary_ammo[i]);
1665 const auto &&[w, h] = gr_get_string_size(*canvas.cv_font, weapon_str);
1666 if (vertical){
1667 y -= h + fspacy2;
1668 }else
1669 x -= w + fspacx3;
1670 gr_string(canvas, *canvas.cv_font, x, y, weapon_str, w, h);
1671 }
1672 }
1673
1674 #if defined(DXX_BUILD_DESCENT_II)
1675 x = orig_x;
1676 y = orig_y;
1677 if (vertical)
1678 {
1679 x += fspacx(15);
1680 y += line_spacing * 4;
1681 }
1682 else
1683 {
1684 y += line_spacing;
1685 }
1686
1687 {
1688 for (uint_fast32_t ui = 10; ui -- != 5;)
1689 {
1690 const auto i = static_cast<secondary_weapon_index_t>(ui);
1691 char weapon_str[10];
1692 hud_set_secondary_weapon_fontcolor(player_info, i, canvas);
1693 snprintf(weapon_str, sizeof(weapon_str), "%u", secondary_ammo[i]);
1694 const auto &&[w, h] = gr_get_string_size(*canvas.cv_font, weapon_str);
1695 if (vertical){
1696 y -= h + fspacy2;
1697 }else
1698 x -= w + fspacx3;
1699 gr_string(canvas, *canvas.cv_font, x, y, weapon_str, w, h);
1700 }
1701 }
1702 #endif
1703 gr_set_fontcolor(canvas, BM_XRGB(0, 31, 0), -1);
1704 }
1705
hud_show_weapons(grs_canvas & canvas,const object & plrobj,const grs_font & game_font,const bool is_multiplayer)1706 static void hud_show_weapons(grs_canvas &canvas, const object &plrobj, const grs_font &game_font, const bool is_multiplayer)
1707 {
1708 auto &player_info = plrobj.ctype.player_info;
1709 int y;
1710 const char *weapon_name;
1711 char weapon_str[32];
1712
1713 gr_set_fontcolor(canvas, BM_XRGB(0, 31, 0), -1);
1714
1715 y = canvas.cv_bitmap.bm_h;
1716
1717 const auto &&line_spacing = LINE_SPACING(game_font, game_font);
1718 if (is_multiplayer)
1719 y -= line_spacing * 4;
1720
1721 if (PlayerCfg.HudMode == HudType::Alternate1)
1722 {
1723 #if defined(DXX_BUILD_DESCENT_I)
1724 constexpr unsigned multiplier = 1;
1725 #elif defined(DXX_BUILD_DESCENT_II)
1726 constexpr unsigned multiplier = 2;
1727 #endif
1728 hud_show_primary_weapons_mode(canvas, player_info, 0, canvas.cv_bitmap.bm_w, y - (line_spacing * 2 * multiplier));
1729 hud_show_secondary_weapons_mode(canvas, player_info, 0, canvas.cv_bitmap.bm_w, y - (line_spacing * multiplier));
1730 return;
1731 }
1732 const auto &&fspacx = FSPACX();
1733 if (PlayerCfg.HudMode == HudType::Alternate2)
1734 {
1735 int x1;
1736 const auto w = gr_get_string_size(game_font, "V1000").width;
1737 const auto w0 = gr_get_string_size(game_font, "0 ").width;
1738 y = canvas.cv_bitmap.bm_h / 1.75;
1739 x1 = canvas.cv_bitmap.bm_w / 2.1 - (fspacx(40) + w);
1740 const auto x2 = canvas.cv_bitmap.bm_w / 1.9 + (fspacx(42) + w0);
1741 hud_show_primary_weapons_mode(canvas, player_info, 1, x1, y);
1742 hud_show_secondary_weapons_mode(canvas, player_info, 1, x2, y);
1743 gr_set_fontcolor(canvas, BM_XRGB(14, 14, 23), -1);
1744 gr_printf(canvas, game_font, x2, y - (line_spacing * 4), "%i", f2ir(plrobj.shields));
1745 gr_set_fontcolor(canvas, BM_XRGB(25, 18, 6), -1);
1746 gr_printf(canvas, game_font, x1, y - (line_spacing * 4), "%i", f2ir(player_info.energy));
1747 }
1748 else
1749 {
1750 const char *disp_primary_weapon_name;
1751 const auto Primary_weapon = player_info.Primary_weapon;
1752
1753 weapon_name = PRIMARY_WEAPON_NAMES_SHORT(Primary_weapon);
1754 switch (Primary_weapon) {
1755 case primary_weapon_index_t::LASER_INDEX:
1756 {
1757 const auto level = static_cast<unsigned>(player_info.laser_level) + 1;
1758 if (player_info.powerup_flags & PLAYER_FLAGS_QUAD_LASERS)
1759 snprintf(weapon_str, sizeof(weapon_str), "%s %s %u", TXT_QUAD, weapon_name, level);
1760 else
1761 snprintf(weapon_str, sizeof(weapon_str), "%s %u", weapon_name, level);
1762 }
1763 disp_primary_weapon_name = weapon_str;
1764 break;
1765
1766 case primary_weapon_index_t::VULCAN_INDEX:
1767 #if defined(DXX_BUILD_DESCENT_II)
1768 case primary_weapon_index_t::GAUSS_INDEX:
1769 #endif
1770 snprintf(weapon_str, sizeof(weapon_str), "%s: %u", weapon_name, vulcan_ammo_scale(player_info.vulcan_ammo));
1771 convert_1s(weapon_str);
1772 disp_primary_weapon_name = weapon_str;
1773 break;
1774
1775 case primary_weapon_index_t::SPREADFIRE_INDEX:
1776 case primary_weapon_index_t::PLASMA_INDEX:
1777 case primary_weapon_index_t::FUSION_INDEX:
1778 #if defined(DXX_BUILD_DESCENT_II)
1779 case primary_weapon_index_t::HELIX_INDEX:
1780 case primary_weapon_index_t::PHOENIX_INDEX:
1781 #endif
1782 disp_primary_weapon_name = weapon_name;
1783 break;
1784 #if defined(DXX_BUILD_DESCENT_II)
1785 case primary_weapon_index_t::OMEGA_INDEX:
1786 snprintf(weapon_str, sizeof(weapon_str), "%s: %03i", weapon_name, player_info.Omega_charge * 100 / MAX_OMEGA_CHARGE);
1787 convert_1s(weapon_str);
1788 disp_primary_weapon_name = weapon_str;
1789 break;
1790
1791 case primary_weapon_index_t::SUPER_LASER_INDEX: //no such thing as super laser
1792 #endif
1793 default:
1794 Int3();
1795 disp_primary_weapon_name = "";
1796 break;
1797 }
1798
1799 const auto &&bmwx = canvas.cv_bitmap.bm_w - fspacx(1);
1800 {
1801 const auto &&[w, h] = gr_get_string_size(game_font, disp_primary_weapon_name);
1802 gr_string(canvas, game_font, bmwx - w, y - (line_spacing * 2), disp_primary_weapon_name, w, h);
1803 }
1804 const char *disp_secondary_weapon_name;
1805
1806 auto &Secondary_weapon = player_info.Secondary_weapon;
1807 disp_secondary_weapon_name = SECONDARY_WEAPON_NAMES_VERY_SHORT(Secondary_weapon);
1808
1809 snprintf(weapon_str, sizeof(weapon_str), "%s %u", disp_secondary_weapon_name, player_info.secondary_ammo[Secondary_weapon]);
1810 {
1811 const auto &&[w, h] = gr_get_string_size(game_font, weapon_str);
1812 gr_string(canvas, game_font, bmwx - w, y - line_spacing, weapon_str, w, h);
1813 }
1814
1815 show_bomb_count(canvas, player_info, bmwx, y - (line_spacing * 3), -1, 1, 1);
1816 }
1817 }
1818 }
1819 }
1820
1821 namespace {
1822
hud_show_cloak_invuln(grs_canvas & canvas,const player_flags player_flags,const fix64 cloak_time,const fix64 invulnerable_time,const unsigned base_y)1823 static void hud_show_cloak_invuln(grs_canvas &canvas, const player_flags player_flags, const fix64 cloak_time, const fix64 invulnerable_time, const unsigned base_y)
1824 {
1825 if (!(player_flags & (PLAYER_FLAGS_CLOAKED | PLAYER_FLAGS_INVULNERABLE)))
1826 return;
1827 gr_set_fontcolor(canvas, BM_XRGB(0, 31, 0), -1);
1828 const auto &&line_spacing = LINE_SPACING(*canvas.cv_font, *GAME_FONT);
1829 const auto gametime64 = GameTime64;
1830 const auto &&fspacx1 = FSPACX(1);
1831
1832 const auto cloak_invul_timer = show_cloak_invul_timer();
1833 const auto a = [&](const fix64 effect_end, int y, const char *txt) {
1834 if (cloak_invul_timer)
1835 gr_printf(canvas, *canvas.cv_font, fspacx1, y, "%s: %lu", txt, static_cast<unsigned long>(effect_end / F1_0));
1836 else
1837 gr_string(canvas, *canvas.cv_font, fspacx1, y, txt);
1838 };
1839
1840 if (player_flags & PLAYER_FLAGS_CLOAKED)
1841 {
1842 const fix64 effect_end = cloak_time + CLOAK_TIME_MAX - gametime64;
1843 if (effect_end > F1_0*3 || gametime64 & 0x8000)
1844 {
1845 a(effect_end, base_y, TXT_CLOAKED);
1846 }
1847 }
1848
1849 if (player_flags & PLAYER_FLAGS_INVULNERABLE)
1850 {
1851 const fix64 effect_end = invulnerable_time + INVULNERABLE_TIME_MAX - gametime64;
1852 if (effect_end > F1_0*4 || gametime64 & 0x8000)
1853 {
1854 a(effect_end, base_y - line_spacing, TXT_INVULNERABLE);
1855 }
1856 }
1857 }
1858
hud_show_shield(grs_canvas & canvas,const object & plrobj,const grs_font & game_font,const unsigned current_y)1859 static void hud_show_shield(grs_canvas &canvas, const object &plrobj, const grs_font &game_font, const unsigned current_y)
1860 {
1861 if (PlayerCfg.HudMode == HudType::Standard || PlayerCfg.HudMode == HudType::Alternate1)
1862 {
1863 gr_set_fontcolor(canvas, BM_XRGB(0, 31, 0), -1);
1864
1865 const auto shields = plrobj.shields;
1866 gr_printf(canvas, game_font, FSPACX(1), current_y, "%s: %i", TXT_SHIELD, shields >= 0 ? f2ir(shields) : 0);
1867 }
1868
1869 if (Newdemo_state==ND_STATE_RECORDING )
1870 newdemo_record_player_shields(f2ir(plrobj.shields));
1871 }
1872
1873 //draw the icons for number of lives
hud_show_lives(const hud_draw_context_hs_mr hudctx,const hud_ar_scale_float hud_scale_ar,const player_info & player_info,const bool is_multiplayer)1874 static void hud_show_lives(const hud_draw_context_hs_mr hudctx, const hud_ar_scale_float hud_scale_ar, const player_info &player_info, const bool is_multiplayer)
1875 {
1876 if (HUD_toolong)
1877 return;
1878 if (Newdemo_state == ND_STATE_PLAYBACK)
1879 return;
1880
1881 const int x = (PlayerCfg.CockpitMode[1] == CM_FULL_COCKPIT)
1882 ? static_cast<int>(hudctx.xscale(7))
1883 : static_cast<int>(FSPACX(2));
1884
1885 auto &canvas = hudctx.canvas;
1886 auto &game_font = *GAME_FONT;
1887 if (is_multiplayer)
1888 {
1889 gr_set_fontcolor(canvas, BM_XRGB(0, 31, 0), -1);
1890 gr_printf(canvas, game_font, x, FSPACY(1), "%s: %d", TXT_DEATHS, player_info.net_killed_total);
1891 }
1892 else if (const uint16_t lives = get_local_player().lives - 1)
1893 {
1894 gr_set_curfont(canvas, game_font);
1895 gr_set_fontcolor(canvas, BM_XRGB(0, 20, 0), -1);
1896 #if defined(DXX_BUILD_DESCENT_II)
1897 auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
1898 #endif
1899 PAGE_IN_GAUGE(GAUGE_LIVES, multires_gauge_graphic);
1900 auto &bm = GameBitmaps[GET_GAUGE_INDEX(GAUGE_LIVES)];
1901 const auto &&fspacy1 = FSPACY(1);
1902 hud_bitblt_free(canvas, x, fspacy1, hud_scale_ar(bm.bm_w), hud_scale_ar(bm.bm_h), bm);
1903 auto &game_font = *GAME_FONT;
1904 gr_printf(canvas, game_font, hud_scale_ar(bm.bm_w) + x, fspacy1, " x %hu", lives);
1905 }
1906 }
1907
sb_show_lives(const hud_draw_context_hs_mr hudctx,const hud_ar_scale_float hud_scale_ar,const player_info & player_info,const bool is_multiplayer)1908 static void sb_show_lives(const hud_draw_context_hs_mr hudctx, const hud_ar_scale_float hud_scale_ar, const player_info &player_info, const bool is_multiplayer)
1909 {
1910 if (Newdemo_state == ND_STATE_PLAYBACK)
1911 return;
1912 auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
1913 const auto y = SB_LIVES_Y;
1914
1915 auto &canvas = hudctx.canvas;
1916 gr_set_fontcolor(canvas, BM_XRGB(0, 20, 0), -1);
1917 const auto scaled_y = hudctx.yscale(y);
1918 auto &game_font = *GAME_FONT;
1919 gr_printf(canvas, game_font, hudctx.xscale(SB_LIVES_LABEL_X), scaled_y, "%s:", is_multiplayer ? TXT_DEATHS : TXT_LIVES);
1920
1921 const uint8_t color = BM_XRGB(0,0,0);
1922 const auto scaled_score_right = hudctx.xscale(SB_SCORE_RIGHT);
1923 if (is_multiplayer)
1924 {
1925 char killed_str[20];
1926 static std::array<int, 4> last_x{{SB_SCORE_RIGHT_L, SB_SCORE_RIGHT_L, SB_SCORE_RIGHT_H, SB_SCORE_RIGHT_H}};
1927
1928 snprintf(killed_str, sizeof(killed_str), "%5d", player_info.net_killed_total);
1929 const auto &&[w, h] = gr_get_string_size(game_font, killed_str);
1930 const auto x = scaled_score_right - w - FSPACX(1);
1931 gr_rect(canvas, std::exchange(last_x[multires_gauge_graphic.is_hires()], x), scaled_y, scaled_score_right, scaled_y + LINE_SPACING(game_font, game_font), color);
1932 gr_string(canvas, game_font, x, scaled_y, killed_str, w, h);
1933 return;
1934 }
1935
1936 const int x = SB_LIVES_X;
1937 //erase old icons
1938 auto &bm = GameBitmaps[GET_GAUGE_INDEX(GAUGE_LIVES)];
1939 const auto scaled_x = hudctx.xscale(x);
1940 gr_rect(canvas, scaled_x, scaled_y, scaled_score_right, hudctx.yscale(y + bm.bm_h), color);
1941
1942 if (const uint16_t lives = get_local_player().lives - 1)
1943 {
1944 PAGE_IN_GAUGE(GAUGE_LIVES, multires_gauge_graphic);
1945 const auto scaled_width = hud_scale_ar(bm.bm_w);
1946 hud_bitblt_free(canvas, scaled_x, scaled_y, scaled_width, hud_scale_ar(bm.bm_h), bm);
1947 gr_printf(canvas, game_font, scaled_x + scaled_width, scaled_y, " x %hu", lives);
1948 }
1949 }
1950
1951 #ifndef RELEASE
show_time(grs_canvas & canvas,const grs_font & cv_font)1952 static void show_time(grs_canvas &canvas, const grs_font &cv_font)
1953 {
1954 auto &plr = get_local_player();
1955 const unsigned secs = f2i(plr.time_level) % 60;
1956 const unsigned mins = f2i(plr.time_level) / 60;
1957
1958 if (Color_0_31_0 == -1)
1959 Color_0_31_0 = BM_XRGB(0,31,0);
1960 gr_set_fontcolor(canvas, Color_0_31_0, -1);
1961 auto &game_font = *GAME_FONT;
1962 gr_printf(canvas, game_font, FSPACX(2), (LINE_SPACING(cv_font, game_font) * 15), "%d:%02d", mins, secs);
1963 }
1964 #endif
1965
1966 #define EXTRA_SHIP_SCORE 50000 //get new ship every this many points
1967
common_add_points_to_score(const int points,int & score,const game_mode_flags Game_mode)1968 static void common_add_points_to_score(const int points, int &score, const game_mode_flags Game_mode)
1969 {
1970 if (points == 0 || cheats.enabled)
1971 return;
1972
1973 if (Newdemo_state == ND_STATE_RECORDING)
1974 newdemo_record_player_score(points);
1975
1976 const auto prev_score = score;
1977 score += points;
1978
1979 if (Game_mode & GM_MULTI)
1980 /* In single player, fall through and check whether an extra
1981 * life has been earned. In multiplayer, extra lives cannot be
1982 * earned, so return.
1983 */
1984 return;
1985
1986 const auto current_ship_score = score / EXTRA_SHIP_SCORE;
1987 const auto previous_ship_score = prev_score / EXTRA_SHIP_SCORE;
1988 if (current_ship_score != previous_ship_score)
1989 {
1990 int snd;
1991 get_local_player().lives += current_ship_score - previous_ship_score;
1992 powerup_basic_str(20, 20, 20, 0, TXT_EXTRA_LIFE);
1993 if ((snd=Powerup_info[POW_EXTRA_LIFE].hit_sound) > -1 )
1994 digi_play_sample( snd, F1_0 );
1995 }
1996 }
1997
1998 }
1999
2000 namespace dsx {
2001
add_points_to_score(player_info & player_info,const unsigned points,const game_mode_flags Game_mode)2002 void add_points_to_score(player_info &player_info, const unsigned points, const game_mode_flags Game_mode)
2003 {
2004 if ((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP))
2005 return;
2006 score_time += f1_0*2;
2007 score_display += points;
2008 if (score_time > f1_0*4) score_time = f1_0*4;
2009
2010 common_add_points_to_score(points, player_info.mission.score, Game_mode);
2011 if (Game_mode & GM_MULTI_COOP)
2012 multi_send_score();
2013 }
2014
2015 /* This is only called in single player when the player is between
2016 * levels.
2017 */
add_bonus_points_to_score(player_info & player_info,unsigned points,const game_mode_flags Game_mode)2018 void add_bonus_points_to_score(player_info &player_info, unsigned points, const game_mode_flags Game_mode)
2019 {
2020 assert(!(Game_mode & GM_MULTI));
2021 common_add_points_to_score(points, player_info.mission.score, Game_mode);
2022 }
2023
2024 }
2025
2026 namespace {
2027
2028 // Decode cockpit bitmap to deccpt and add alpha fields to weapon boxes (as it should have always been) so we later can render sub bitmaps over the window canvases
cockpit_decode_alpha(const hud_draw_context_mr hudctx,grs_bitmap * const bm)2029 static void cockpit_decode_alpha(const hud_draw_context_mr hudctx, grs_bitmap *const bm)
2030 {
2031 static const uint8_t *cur;
2032 static uint16_t cur_w, cur_h;
2033 #ifndef DXX_MAX_COCKPIT_BITMAP_SIZE
2034 /* 640 wide by 480 high should be enough for all bitmaps shipped
2035 * with shareware or commercial data.
2036 * Use a #define so that the value can be easily overridden at build
2037 * time.
2038 */
2039 #define DXX_MAX_COCKPIT_BITMAP_SIZE (640 * 480)
2040 #endif
2041
2042 const unsigned bm_h = bm->bm_h;
2043 if (unlikely(!bm_h))
2044 /* Invalid, but later code has undefined results if bm_h==0 */
2045 return;
2046 const unsigned bm_w = bm->bm_w;
2047 // check if we processed this bitmap already
2048 if (cur == bm->bm_data && cur_w == bm_w && cur_h == bm_h)
2049 {
2050 #if DXX_USE_OGL
2051 // check if textures are still valid
2052 const ogl_texture *gltexture;
2053 if ((gltexture = WinBoxOverlay[0].get()->gltexture) &&
2054 gltexture->handle &&
2055 (gltexture = WinBoxOverlay[1].get()->gltexture) &&
2056 gltexture->handle)
2057 return;
2058 #else
2059 return;
2060 #endif
2061 }
2062
2063 RAIIdmem<uint8_t[]> cockpitbuf;
2064 MALLOC(cockpitbuf, uint8_t[], DXX_MAX_COCKPIT_BITMAP_SIZE);
2065
2066 // decode the bitmap
2067 if (bm->get_flag_mask(BM_FLAG_RLE))
2068 {
2069 if (!bm_rle_expand(*bm).loop(bm_w, bm_rle_expand_range(cockpitbuf.get(), cockpitbuf.get() + DXX_MAX_COCKPIT_BITMAP_SIZE)))
2070 {
2071 /* Out of space. Return without adjusting the bitmap.
2072 * The result will look ugly, but run correctly.
2073 */
2074 con_printf(CON_URGENT, __FILE__ ":%u: BUG: RLE-encoded bitmap with size %hux%hu exceeds decode buffer size %u", __LINE__, static_cast<uint16_t>(bm_w), static_cast<uint16_t>(bm_h), DXX_MAX_COCKPIT_BITMAP_SIZE);
2075 return;
2076 }
2077 }
2078 else
2079 {
2080 const std::size_t len = bm_w * bm_h;
2081 if (len > DXX_MAX_COCKPIT_BITMAP_SIZE)
2082 {
2083 con_printf(CON_URGENT, __FILE__ ":%u: BUG: RLE-encoded bitmap with size %hux%hu exceeds decode buffer size %u", __LINE__, static_cast<uint16_t>(bm_w), static_cast<uint16_t>(bm_h), DXX_MAX_COCKPIT_BITMAP_SIZE);
2084 return;
2085 }
2086 memcpy(cockpitbuf.get(), bm->bm_data, len);
2087 }
2088
2089 // add alpha color to the pixels which are inside the window box spans
2090 auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
2091 const unsigned lower_y = ((multires_gauge_graphic.get(364, 151)));
2092 unsigned i = bm_w * lower_y;
2093 const auto fill_alpha_one_line = [&cockpitbuf](unsigned o, const d_gauge_span &s) {
2094 std::fill_n(&cockpitbuf[o + s.l], s.r - s.l + 1, TRANSPARENCY_COLOR);
2095 };
2096 range_for (auto &s,
2097 multires_gauge_graphic.is_hires()
2098 ? make_range(weapon_windows_hires)
2099 : make_range(weapon_windows_lowres)
2100 )
2101 {
2102 fill_alpha_one_line(i, s.l);
2103 fill_alpha_one_line(i, s.r);
2104 i += bm_w;
2105 }
2106 #if DXX_USE_OGL
2107 ogl_freebmtexture(*bm);
2108 /* In the OpenGL build, copy the data pointer into the bitmap for
2109 * use until the OpenGL texture is created.
2110 */
2111 gr_init_bitmap(WinBoxOverlay.decoded_full_cockpit_image, bm_mode::linear, 0, 0, bm_w, bm_h, bm_w, cockpitbuf.get());
2112 #else
2113 /* In the SDL-only build, move the data pointer into the bitmap so
2114 * that the underlying data buffer remains allocated.
2115 */
2116 gr_init_main_bitmap(WinBoxOverlay.decoded_full_cockpit_image, bm_mode::linear, 0, 0, bm_w, bm_h, bm_w, std::move(cockpitbuf));
2117 #endif
2118 gr_set_transparent(WinBoxOverlay.decoded_full_cockpit_image, 1);
2119 #if DXX_USE_OGL
2120 ogl_ubitmapm_cs(hudctx.canvas, 0, 0, opengl_bitmap_use_dst_canvas, opengl_bitmap_use_dst_canvas, WinBoxOverlay.decoded_full_cockpit_image, 255); // render one time to init the texture
2121 #endif
2122 WinBoxOverlay[0] = gr_create_sub_bitmap(WinBoxOverlay.decoded_full_cockpit_image, (PRIMARY_W_BOX_LEFT) - 2, (PRIMARY_W_BOX_TOP) - 2, (PRIMARY_W_BOX_RIGHT - PRIMARY_W_BOX_LEFT + 4), (PRIMARY_W_BOX_BOT - PRIMARY_W_BOX_TOP + 4));
2123 WinBoxOverlay[1] = gr_create_sub_bitmap(WinBoxOverlay.decoded_full_cockpit_image, (SECONDARY_W_BOX_LEFT) - 2, (SECONDARY_W_BOX_TOP) - 2, (SECONDARY_W_BOX_RIGHT - SECONDARY_W_BOX_LEFT) + 4, (SECONDARY_W_BOX_BOT - SECONDARY_W_BOX_TOP) + 4);
2124 #if DXX_USE_OGL
2125 /* The image has been copied to OpenGL as a texture. The underlying
2126 * main application memory will be freed when `cockpitbuf` goes out
2127 * of scope.
2128 * Clear bm_data to avoid leaving a dangling pointer.
2129 */
2130 WinBoxOverlay.decoded_full_cockpit_image.bm_data = nullptr;
2131 WinBoxOverlay[0]->bm_data = nullptr;
2132 WinBoxOverlay[1]->bm_data = nullptr;
2133 #endif
2134
2135 cur = bm->get_bitmap_data();
2136 cur_w = bm_w;
2137 cur_h = bm_h;
2138 }
2139 }
2140
2141 namespace dsx {
2142 namespace {
draw_wbu_overlay(const hud_draw_context_hs_mr hudctx)2143 static void draw_wbu_overlay(const hud_draw_context_hs_mr hudctx)
2144 {
2145 auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
2146 #if defined(DXX_BUILD_DESCENT_I)
2147 unsigned cockpit_idx = PlayerCfg.CockpitMode[1];
2148 #elif defined(DXX_BUILD_DESCENT_II)
2149 unsigned cockpit_idx = PlayerCfg.CockpitMode[1]+(multires_gauge_graphic.is_hires() ? (Num_cockpits / 2) : 0);
2150 #endif
2151 PIGGY_PAGE_IN(cockpit_bitmap[cockpit_idx]);
2152 grs_bitmap *bm = &GameBitmaps[cockpit_bitmap[cockpit_idx].index];
2153
2154 cockpit_decode_alpha(hudctx, bm);
2155
2156 /* The code that rendered the inset windows drew simple square
2157 * boxes, which partially overwrote the frame surrounding the inset
2158 * windows in the cockpit graphic. These calls reapply the
2159 * overwritten frame, while leaving untouched the portion that was
2160 * supposed to be overwritten.
2161 */
2162 if (WinBoxOverlay[0])
2163 hud_bitblt(hudctx, PRIMARY_W_BOX_LEFT - 2, PRIMARY_W_BOX_TOP - 2, *WinBoxOverlay[0].get());
2164 if (WinBoxOverlay[1])
2165 hud_bitblt(hudctx, SECONDARY_W_BOX_LEFT - 2, SECONDARY_W_BOX_TOP - 2, *WinBoxOverlay[1].get());
2166 }
2167 }
2168 }
2169
close_gauges()2170 void close_gauges()
2171 {
2172 WinBoxOverlay = {};
2173 }
2174
2175 namespace dsx {
init_gauges()2176 void init_gauges()
2177 {
2178 inset_window[gauge_inset_window_view::primary] = {};
2179 inset_window[gauge_inset_window_view::secondary] = {};
2180 old_laser_level = {};
2181 }
2182 }
2183
2184 namespace {
2185
draw_energy_bar(grs_canvas & canvas,const hud_draw_context_hs_mr hudctx,const int energy)2186 static void draw_energy_bar(grs_canvas &canvas, const hud_draw_context_hs_mr hudctx, const int energy)
2187 {
2188 auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
2189 const int not_energy = hudctx.xscale(multires_gauge_graphic.is_hires() ? (125 - (energy * 125) / 100) : (63 - (energy * 63) / 100));
2190 const double aplitscale = static_cast<double>(hudctx.xscale(65) / hudctx.yscale(8)) / (65 / 8); //scale amplitude of energy bar to current resolution aspect
2191
2192 // Draw left energy bar
2193 hud_gauge_bitblt(hudctx, LEFT_ENERGY_GAUGE_X, LEFT_ENERGY_GAUGE_Y, GAUGE_ENERGY_LEFT);
2194
2195 const auto color = BM_XRGB(0, 0, 0);
2196
2197 if (energy < 100)
2198 {
2199 const auto xscale_energy_gauge_x = hudctx.xscale(LEFT_ENERGY_GAUGE_X);
2200 const auto xscale_energy_gauge_w = hudctx.xscale(LEFT_ENERGY_GAUGE_W);
2201 const auto xscale_energy_gauge_h2 = hudctx.xscale(LEFT_ENERGY_GAUGE_H - 2);
2202 const auto yscale_energy_gauge_y = hudctx.yscale(LEFT_ENERGY_GAUGE_Y);
2203 const auto yscale_energy_gauge_h = hudctx.yscale(LEFT_ENERGY_GAUGE_H);
2204 for (unsigned y = 0; y < yscale_energy_gauge_h; ++y)
2205 {
2206 const auto bound = xscale_energy_gauge_w - (y * aplitscale) / 3;
2207 const auto x1 = xscale_energy_gauge_h2 - y * aplitscale;
2208 const auto x2 = std::min(x1 + not_energy, bound);
2209
2210 if (x2 > x1)
2211 {
2212 const auto ly = i2f(y + yscale_energy_gauge_y);
2213 gr_uline(canvas, i2f(x1 + xscale_energy_gauge_x), ly, i2f(x2 + xscale_energy_gauge_x), ly, color);
2214 }
2215 }
2216 }
2217
2218 // Draw right energy bar
2219 hud_gauge_bitblt(hudctx, RIGHT_ENERGY_GAUGE_X, RIGHT_ENERGY_GAUGE_Y, GAUGE_ENERGY_RIGHT);
2220
2221 if (energy < 100)
2222 {
2223 const auto xscale_energy_gauge_x = hudctx.xscale(RIGHT_ENERGY_GAUGE_X);
2224 const auto yscale_energy_gauge_y = hudctx.yscale(RIGHT_ENERGY_GAUGE_Y);
2225 const auto yscale_energy_gauge_h = hudctx.yscale(RIGHT_ENERGY_GAUGE_H);
2226 const auto xscale_right_energy = hudctx.xscale(RIGHT_ENERGY_GAUGE_W - RIGHT_ENERGY_GAUGE_H + 2);
2227 for (unsigned y = 0; y < yscale_energy_gauge_h; ++y)
2228 {
2229 const auto bound = (y * aplitscale) / 3;
2230 const auto x2 = xscale_right_energy + y * aplitscale;
2231 auto x1 = x2 - not_energy;
2232
2233 if (x1 < bound)
2234 x1 = bound;
2235
2236 if (x2 > x1)
2237 {
2238 const auto ly = i2f(y + yscale_energy_gauge_y);
2239 gr_uline(canvas, i2f(x1 + xscale_energy_gauge_x), ly, i2f(x2 + xscale_energy_gauge_x), ly, color);
2240 }
2241 }
2242 }
2243 }
2244
2245 }
2246
2247 #if defined(DXX_BUILD_DESCENT_II)
2248 namespace dsx {
2249 namespace {
draw_afterburner_bar(const hud_draw_context_hs_mr hudctx,const int afterburner)2250 static void draw_afterburner_bar(const hud_draw_context_hs_mr hudctx, const int afterburner)
2251 {
2252 struct lr
2253 {
2254 uint8_t l, r;
2255 };
2256 static const std::array<lr, AFTERBURNER_GAUGE_H_L> afterburner_bar_table = {{
2257 {3, 11},
2258 {3, 11},
2259 {3, 11},
2260 {3, 11},
2261 {3, 11},
2262 {3, 11},
2263 {2, 11},
2264 {2, 10},
2265 {2, 10},
2266 {2, 10},
2267 {2, 10},
2268 {2, 10},
2269 {2, 10},
2270 {1, 10},
2271 {1, 10},
2272 {1, 10},
2273 {1, 9},
2274 {1, 9},
2275 {1, 9},
2276 {1, 9},
2277 {0, 9},
2278 {0, 9},
2279 {0, 8},
2280 {0, 8},
2281 {0, 8},
2282 {0, 8},
2283 {1, 8},
2284 {2, 8},
2285 {3, 8},
2286 {4, 8},
2287 {5, 8},
2288 {6, 7}
2289 }};
2290 static const std::array<lr, AFTERBURNER_GAUGE_H_H> afterburner_bar_table_hires = {{
2291 {5, 20},
2292 {5, 20},
2293 {5, 19},
2294 {5, 19},
2295 {5, 19},
2296 {5, 19},
2297 {4, 19},
2298 {4, 19},
2299 {4, 19},
2300 {4, 19},
2301 {4, 19},
2302 {4, 18},
2303 {4, 18},
2304 {4, 18},
2305 {4, 18},
2306 {3, 18},
2307 {3, 18},
2308 {3, 18},
2309 {3, 18},
2310 {3, 18},
2311 {3, 18},
2312 {3, 17},
2313 {3, 17},
2314 {2, 17},
2315 {2, 17},
2316 {2, 17},
2317 {2, 17},
2318 {2, 17},
2319 {2, 17},
2320 {2, 17},
2321 {2, 17},
2322 {2, 16},
2323 {2, 16},
2324 {1, 16},
2325 {1, 16},
2326 {1, 16},
2327 {1, 16},
2328 {1, 16},
2329 {1, 16},
2330 {1, 16},
2331 {1, 16},
2332 {1, 15},
2333 {1, 15},
2334 {1, 15},
2335 {0, 15},
2336 {0, 15},
2337 {0, 15},
2338 {0, 15},
2339 {0, 15},
2340 {0, 15},
2341 {0, 14},
2342 {0, 14},
2343 {0, 14},
2344 {1, 14},
2345 {2, 14},
2346 {3, 14},
2347 {4, 14},
2348 {5, 14},
2349 {6, 13},
2350 {7, 13},
2351 {8, 13},
2352 {9, 13},
2353 {10, 13},
2354 {11, 13},
2355 {12, 13}
2356 }};
2357
2358 // Draw afterburner bar
2359 auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
2360 const auto afterburner_gauge_x = AFTERBURNER_GAUGE_X;
2361 const auto afterburner_gauge_y = AFTERBURNER_GAUGE_Y;
2362 const auto &&table = multires_gauge_graphic.is_hires()
2363 ? std::make_pair(afterburner_bar_table_hires.data(), afterburner_bar_table_hires.size())
2364 : std::make_pair(afterburner_bar_table.data(), afterburner_bar_table.size());
2365 hud_gauge_bitblt(hudctx, afterburner_gauge_x, afterburner_gauge_y, GAUGE_AFTERBURNER);
2366 const unsigned not_afterburner = fixmul(f1_0 - afterburner, table.second);
2367 if (not_afterburner > table.second)
2368 return;
2369 const uint8_t color = BM_XRGB(0, 0, 0);
2370 const int base_top = hudctx.yscale(afterburner_gauge_y - 1);
2371 const int base_bottom = hudctx.yscale(afterburner_gauge_y);
2372 int y = 0;
2373 range_for (auto &ab, unchecked_partial_range(table.first, not_afterburner))
2374 {
2375 const int left = hudctx.xscale(afterburner_gauge_x + ab.l);
2376 const int right = hudctx.xscale(afterburner_gauge_x + ab.r + 1);
2377 for (int i = hudctx.yscale(y), j = hudctx.yscale(++y); i < j; ++i)
2378 {
2379 gr_rect(hudctx.canvas, left, base_top + i, right, base_bottom + i, color);
2380 }
2381 }
2382 }
2383
2384 }
2385 }
2386 #endif
2387
2388 namespace {
2389
draw_shield_bar(const hud_draw_context_hs_mr hudctx,const int shield)2390 static void draw_shield_bar(const hud_draw_context_hs_mr hudctx, const int shield)
2391 {
2392 int bm_num = shield>=100?9:(shield / 10);
2393 auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
2394 hud_gauge_bitblt(hudctx, SHIELD_GAUGE_X, SHIELD_GAUGE_Y, GAUGE_SHIELDS + 9 - bm_num);
2395 }
2396
show_cockpit_cloak_invul_timer(grs_canvas & canvas,const fix64 effect_end,const int y)2397 static void show_cockpit_cloak_invul_timer(grs_canvas &canvas, const fix64 effect_end, const int y)
2398 {
2399 char countdown[8];
2400 snprintf(countdown, sizeof(countdown), "%lu", static_cast<unsigned long>(effect_end / F1_0));
2401 gr_set_fontcolor(canvas, BM_XRGB(31, 31, 31), -1);
2402 const auto &&[ow, oh] = gr_get_string_size(*canvas.cv_font, countdown);
2403 const int x = grd_curscreen->get_screen_width() / (PlayerCfg.CockpitMode[1] == CM_STATUS_BAR
2404 ? 2.266
2405 : 1.951
2406 );
2407 gr_string(canvas, *canvas.cv_font, x - (ow / 2), y, countdown, ow, oh);
2408 }
2409
2410 }
2411
2412 #define CLOAK_FADE_WAIT_TIME 0x400
2413
2414 namespace dsx {
2415
2416 namespace {
2417
draw_player_ship(const hud_draw_context_hs_mr hudctx,const player_info & player_info,const int cloak_state,const int x,const int y)2418 static void draw_player_ship(const hud_draw_context_hs_mr hudctx, const player_info &player_info, const int cloak_state, const int x, const int y)
2419 {
2420 static fix cloak_fade_timer=0;
2421 static int8_t cloak_fade_value = GR_FADE_LEVELS - 1;
2422
2423 if (cloak_state)
2424 {
2425 static int step = 0;
2426 const auto cloak_time = player_info.cloak_time;
2427
2428 if (GameTime64 - cloak_time < F1_0)
2429 {
2430 step = -2;
2431 }
2432 else if (cloak_time + CLOAK_TIME_MAX - GameTime64 <= F1_0*3)
2433 {
2434 if (cloak_fade_value >= static_cast<signed>(GR_FADE_LEVELS-1))
2435 {
2436 step = -2;
2437 }
2438 else if (cloak_fade_value <= 0)
2439 {
2440 step = 2;
2441 }
2442 }
2443 else
2444 {
2445 step = 0;
2446 cloak_fade_value = 0;
2447 }
2448
2449 cloak_fade_timer -= FrameTime;
2450
2451 while (cloak_fade_timer < 0)
2452 {
2453 cloak_fade_timer += CLOAK_FADE_WAIT_TIME;
2454 cloak_fade_value += step;
2455 }
2456
2457 if (cloak_fade_value > static_cast<signed>(GR_FADE_LEVELS-1))
2458 cloak_fade_value = (GR_FADE_LEVELS-1);
2459 if (cloak_fade_value <= 0)
2460 cloak_fade_value = 0;
2461 }
2462 else
2463 {
2464 cloak_fade_timer = 0;
2465 cloak_fade_value = GR_FADE_LEVELS-1;
2466 }
2467
2468 const auto color = get_player_or_team_color(Player_num);
2469 #if defined(DXX_BUILD_DESCENT_II)
2470 auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
2471 #endif
2472 PAGE_IN_GAUGE(GAUGE_SHIPS+color, multires_gauge_graphic);
2473 auto &bm = GameBitmaps[GET_GAUGE_INDEX(GAUGE_SHIPS+color)];
2474 hud_bitblt(hudctx, x, y, bm);
2475 auto &canvas = hudctx.canvas;
2476 gr_settransblend(canvas, gr_fade_level{static_cast<uint8_t>(cloak_fade_value)}, gr_blend::normal);
2477 gr_rect(canvas, hudctx.xscale(x - 3), hudctx.yscale(y - 3), hudctx.xscale(x + bm.bm_w + 3), hudctx.yscale(y + bm.bm_h + 3), 0);
2478 gr_settransblend(canvas, GR_FADE_OFF, gr_blend::normal);
2479 // Show Cloak Timer if enabled
2480 if (cloak_fade_value < GR_FADE_LEVELS/2 && show_cloak_invul_timer())
2481 show_cockpit_cloak_invul_timer(canvas, player_info.cloak_time + CLOAK_TIME_MAX - GameTime64, hudctx.yscale(y + (bm.bm_h / 2)));
2482 }
2483
2484 }
2485
2486 }
2487
2488 #define INV_FRAME_TIME (f1_0/10) //how long for each frame
2489
2490 namespace dcx {
2491
2492 namespace {
2493
get_gauge_width_string(const unsigned v)2494 static const char *get_gauge_width_string(const unsigned v)
2495 {
2496 if (v > 199)
2497 return "200";
2498 return &"100"[(v > 99)
2499 ? 0
2500 : (v > 9) ? 1 : 2
2501 ];
2502 }
2503
2504 }
2505
2506 }
2507
2508 namespace {
2509
draw_numerical_display(const draw_numerical_display_draw_context hudctx,const int shield,const int energy)2510 static void draw_numerical_display(const draw_numerical_display_draw_context hudctx, const int shield, const int energy)
2511 {
2512 auto &canvas = hudctx.canvas;
2513 #if !DXX_USE_OGL
2514 auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
2515 hud_gauge_bitblt(hudctx, NUMERICAL_GAUGE_X, NUMERICAL_GAUGE_Y, GAUGE_NUMERICAL);
2516 #endif
2517 // cockpit is not 100% geometric so we need to divide shield and energy X position by 1.951 which should be most accurate
2518 // gr_get_string_size is used so we can get the numbers finally in the correct position with sw and ew
2519 const int xb = grd_curscreen->get_screen_width() / 1.951;
2520 auto &game_font = *GAME_FONT;
2521 const auto a = [&canvas, &game_font, xb](int v, int y) {
2522 const auto w = gr_get_string_size(game_font, get_gauge_width_string(v)).width;
2523 gr_printf(canvas, game_font, xb - (w / 2), y, "%d", v);
2524 };
2525 gr_set_fontcolor(canvas, BM_XRGB(14, 14, 23), -1);
2526 const auto screen_height = grd_curscreen->get_screen_height();
2527 a(shield, screen_height / 1.365);
2528
2529 gr_set_fontcolor(canvas, BM_XRGB(25, 18, 6), -1);
2530 a(energy, screen_height / 1.5);
2531 }
2532
2533 }
2534
draw_all_cockpit_keys()2535 void draw_keys_state::draw_all_cockpit_keys()
2536 {
2537 auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
2538 draw_one_key(GAUGE_BLUE_KEY_X, GAUGE_BLUE_KEY_Y, GAUGE_BLUE_KEY, PLAYER_FLAGS_BLUE_KEY);
2539 draw_one_key(GAUGE_GOLD_KEY_X, GAUGE_GOLD_KEY_Y, GAUGE_GOLD_KEY, PLAYER_FLAGS_GOLD_KEY);
2540 draw_one_key(GAUGE_RED_KEY_X, GAUGE_RED_KEY_Y, GAUGE_RED_KEY, PLAYER_FLAGS_RED_KEY);
2541 }
2542
2543 namespace dsx {
2544
2545 namespace {
2546
draw_weapon_info_sub(const hud_draw_context_hs_mr hudctx,const player_info & player_info,const int info_index,const gauge_box * const box,const int pic_x,const int pic_y,const char * const name,const int text_x,const int text_y)2547 static void draw_weapon_info_sub(const hud_draw_context_hs_mr hudctx, const player_info &player_info, const int info_index, const gauge_box *const box, const int pic_x, const int pic_y, const char *const name, const int text_x, const int text_y)
2548 {
2549 //clear the window
2550 const uint8_t color = BM_XRGB(0, 0, 0);
2551 {
2552 #if defined(DXX_BUILD_DESCENT_I)
2553 constexpr unsigned bottom_bias = 1;
2554 #elif defined(DXX_BUILD_DESCENT_II)
2555 constexpr unsigned bottom_bias = 0;
2556 #endif
2557 gr_rect(hudctx.canvas, hudctx.xscale(box->left), hudctx.yscale(box->top), hudctx.xscale(box->right), hudctx.yscale(box->bot + bottom_bias), color);
2558 }
2559 const auto &picture =
2560 #if defined(DXX_BUILD_DESCENT_II)
2561 // !SHAREWARE
2562 (Piggy_hamfile_version >= 3 && hudctx.multires_gauge_graphic.is_hires()) ?
2563 Weapon_info[info_index].hires_picture :
2564 #endif
2565 Weapon_info[info_index].picture;
2566 PIGGY_PAGE_IN(picture);
2567 auto &bm = GameBitmaps[picture.index];
2568
2569 hud_bitblt(hudctx, pic_x, pic_y, bm);
2570
2571 if (PlayerCfg.HudMode == HudType::Standard)
2572 {
2573 auto &canvas = hudctx.canvas;
2574 gr_set_fontcolor(canvas, BM_XRGB(0, 20, 0), -1);
2575
2576 gr_string(canvas, *canvas.cv_font, text_x, text_y, name);
2577
2578 // For laser, show level and quadness
2579 #if defined(DXX_BUILD_DESCENT_I)
2580 if (info_index == primary_weapon_index_t::LASER_INDEX)
2581 #elif defined(DXX_BUILD_DESCENT_II)
2582 if (info_index == weapon_id_type::LASER_ID || info_index == weapon_id_type::SUPER_LASER_ID)
2583 #endif
2584 {
2585 const auto &&line_spacing = LINE_SPACING(*canvas.cv_font, *GAME_FONT);
2586 gr_printf(canvas, *canvas.cv_font, text_x, text_y + line_spacing, "%s: %u", TXT_LVL, static_cast<unsigned>(player_info.laser_level) + 1);
2587 if (player_info.powerup_flags & PLAYER_FLAGS_QUAD_LASERS)
2588 gr_string(canvas, *canvas.cv_font, text_x, text_y + (line_spacing * 2), TXT_QUAD);
2589 }
2590 }
2591 }
2592
draw_primary_weapon_info(const hud_draw_context_hs_mr hudctx,const player_info & player_info,const primary_weapon_index_t weapon_num,const laser_level level)2593 static void draw_primary_weapon_info(const hud_draw_context_hs_mr hudctx, const player_info &player_info, const primary_weapon_index_t weapon_num, const laser_level level)
2594 {
2595 #if defined(DXX_BUILD_DESCENT_I)
2596 (void)level;
2597 #endif
2598 int x,y;
2599
2600 {
2601 const auto weapon_id = Primary_weapon_to_weapon_info[weapon_num];
2602 const auto info_index =
2603 #if defined(DXX_BUILD_DESCENT_II)
2604 (weapon_id == weapon_id_type::LASER_ID && level > MAX_LASER_LEVEL)
2605 ? weapon_id_type::SUPER_LASER_ID
2606 :
2607 #endif
2608 weapon_id;
2609
2610 const gauge_box *box;
2611 int pic_x, pic_y, text_x, text_y;
2612 auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
2613 auto &resbox = gauge_boxes[multires_gauge_graphic.hiresmode];
2614 auto &weaponbox = resbox[gauge_inset_window_view::primary];
2615 if (PlayerCfg.CockpitMode[1] == CM_STATUS_BAR)
2616 {
2617 box = &weaponbox[gauge_hud_type::statusbar];
2618 pic_x = SB_PRIMARY_W_PIC_X;
2619 pic_y = SB_PRIMARY_W_PIC_Y;
2620 text_x = SB_PRIMARY_W_TEXT_X;
2621 text_y = SB_PRIMARY_W_TEXT_Y;
2622 x=SB_PRIMARY_AMMO_X;
2623 y=SB_PRIMARY_AMMO_Y;
2624 }
2625 else
2626 {
2627 box = &weaponbox[gauge_hud_type::cockpit];
2628 pic_x = PRIMARY_W_PIC_X;
2629 pic_y = PRIMARY_W_PIC_Y;
2630 text_x = PRIMARY_W_TEXT_X;
2631 text_y = PRIMARY_W_TEXT_Y;
2632 x=PRIMARY_AMMO_X;
2633 y=PRIMARY_AMMO_Y;
2634 }
2635 draw_weapon_info_sub(hudctx, player_info, info_index, box, pic_x, pic_y, PRIMARY_WEAPON_NAMES_SHORT(weapon_num), hudctx.xscale(text_x), hudctx.yscale(text_y));
2636 if (PlayerCfg.HudMode != HudType::Standard)
2637 {
2638 #if defined(DXX_BUILD_DESCENT_II)
2639 if (inset_window[gauge_inset_window_view::primary].user == weapon_box_user::weapon)
2640 #endif
2641 hud_show_primary_weapons_mode(hudctx.canvas, player_info, 1, hudctx.xscale(x), hudctx.yscale(y));
2642 }
2643 }
2644 }
2645
draw_secondary_weapon_info(const hud_draw_context_hs_mr hudctx,const player_info & player_info,const secondary_weapon_index_t weapon_num)2646 static void draw_secondary_weapon_info(const hud_draw_context_hs_mr hudctx, const player_info &player_info, const secondary_weapon_index_t weapon_num)
2647 {
2648 int x,y;
2649 int info_index;
2650
2651 {
2652 info_index = Secondary_weapon_to_weapon_info[weapon_num];
2653 const gauge_box *box;
2654 int pic_x, pic_y, text_x, text_y;
2655 auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
2656 auto &resbox = gauge_boxes[multires_gauge_graphic.hiresmode];
2657 auto &weaponbox = resbox[gauge_inset_window_view::secondary];
2658 if (PlayerCfg.CockpitMode[1] == CM_STATUS_BAR)
2659 {
2660 box = &weaponbox[gauge_hud_type::statusbar];
2661 pic_x = SB_SECONDARY_W_PIC_X;
2662 pic_y = SB_SECONDARY_W_PIC_Y;
2663 text_x = SB_SECONDARY_W_TEXT_X;
2664 text_y = SB_SECONDARY_W_TEXT_Y;
2665 x=SB_SECONDARY_AMMO_X;
2666 y=SB_SECONDARY_AMMO_Y;
2667 }
2668 else
2669 {
2670 box = &weaponbox[gauge_hud_type::cockpit];
2671 pic_x = SECONDARY_W_PIC_X;
2672 pic_y = SECONDARY_W_PIC_Y;
2673 text_x = SECONDARY_W_TEXT_X;
2674 text_y = SECONDARY_W_TEXT_Y;
2675 x=SECONDARY_AMMO_X;
2676 y=SECONDARY_AMMO_Y;
2677 }
2678 draw_weapon_info_sub(hudctx, player_info, info_index, box, pic_x, pic_y, SECONDARY_WEAPON_NAMES_SHORT(weapon_num), hudctx.xscale(text_x), hudctx.yscale(text_y));
2679 if (PlayerCfg.HudMode != HudType::Standard)
2680 {
2681 #if defined(DXX_BUILD_DESCENT_II)
2682 if (inset_window[gauge_inset_window_view::secondary].user == weapon_box_user::weapon)
2683 #endif
2684 hud_show_secondary_weapons_mode(hudctx.canvas, player_info, 1, hudctx.xscale(x), hudctx.yscale(y));
2685 }
2686 }
2687 }
2688
draw_weapon_info(const hud_draw_context_hs_mr hudctx,const player_info & player_info,const weapon_index weapon_num,const laser_level laser_level,const gauge_inset_window_view wt)2689 static void draw_weapon_info(const hud_draw_context_hs_mr hudctx, const player_info &player_info, const weapon_index weapon_num, const laser_level laser_level, const gauge_inset_window_view wt)
2690 {
2691 if (wt == gauge_inset_window_view::primary)
2692 draw_primary_weapon_info(hudctx, player_info, weapon_num.primary, laser_level);
2693 else
2694 draw_secondary_weapon_info(hudctx, player_info, weapon_num.secondary);
2695 }
2696
2697 }
2698
2699 }
2700
2701 namespace {
2702
draw_ammo_info(grs_canvas & canvas,const unsigned x,const unsigned y,const unsigned ammo_count)2703 static void draw_ammo_info(grs_canvas &canvas, const unsigned x, const unsigned y, const unsigned ammo_count)
2704 {
2705 if (PlayerCfg.HudMode == HudType::Standard)
2706 {
2707 gr_set_fontcolor(canvas, BM_XRGB(20, 0, 0), -1);
2708 gr_printf(canvas, *canvas.cv_font, x, y, "%03u", ammo_count);
2709 }
2710 }
2711
draw_secondary_ammo_info(const hud_draw_context_hs_mr hudctx,const unsigned ammo_count)2712 static void draw_secondary_ammo_info(const hud_draw_context_hs_mr hudctx, const unsigned ammo_count)
2713 {
2714 int x, y;
2715 auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
2716 if (PlayerCfg.CockpitMode[1] == CM_STATUS_BAR)
2717 x = SB_SECONDARY_AMMO_X, y = SB_SECONDARY_AMMO_Y;
2718 else
2719 x = SECONDARY_AMMO_X, y = SECONDARY_AMMO_Y;
2720 draw_ammo_info(hudctx.canvas, hudctx.xscale(x), hudctx.yscale(y), ammo_count);
2721 }
2722
draw_weapon_box(const hud_draw_context_hs_mr hudctx,const player_info & player_info,const weapon_index weapon_num,const gauge_inset_window_view wt)2723 static void draw_weapon_box(const hud_draw_context_hs_mr hudctx, const player_info &player_info, const weapon_index weapon_num, const gauge_inset_window_view wt)
2724 {
2725 auto &canvas = hudctx.canvas;
2726 gr_set_curfont(canvas, *GAME_FONT);
2727
2728 const auto laser_level_changed = (wt == gauge_inset_window_view::primary && weapon_num.primary == primary_weapon_index_t::LASER_INDEX && (player_info.laser_level != old_laser_level));
2729
2730 auto &inset = inset_window[wt];
2731 if ((weapon_num != inset.old_weapon || laser_level_changed) && inset.box_state == weapon_box_state::set && inset.old_weapon != weapon_index{} && PlayerCfg.HudMode == HudType::Standard)
2732 {
2733 inset.box_state = weapon_box_state::fading_out;
2734 inset.fade_value = i2f(GR_FADE_LEVELS - 1);
2735 }
2736
2737 const local_multires_gauge_graphic multires_gauge_graphic{};
2738 if (inset.old_weapon == weapon_index{})
2739 {
2740 draw_weapon_info(hudctx, player_info, weapon_num, player_info.laser_level, wt);
2741 inset.old_weapon = weapon_num;
2742 inset.box_state = weapon_box_state::set;
2743 }
2744
2745 if (inset.box_state == weapon_box_state::fading_out)
2746 {
2747 draw_weapon_info(hudctx, player_info, inset.old_weapon, old_laser_level, wt);
2748 inset.fade_value -= FrameTime * FADE_SCALE;
2749 if (inset.fade_value <= 0)
2750 {
2751 inset.fade_value = 0;
2752 inset.box_state = weapon_box_state::fading_in;
2753 inset.old_weapon = weapon_num;
2754 old_laser_level = player_info.laser_level;
2755 }
2756 }
2757 else if (inset.box_state == weapon_box_state::fading_in)
2758 {
2759 if (weapon_num != inset.old_weapon) {
2760 inset.box_state = weapon_box_state::fading_out;
2761 }
2762 else {
2763 draw_weapon_info(hudctx, player_info, weapon_num, player_info.laser_level, wt);
2764 inset.fade_value += FrameTime * FADE_SCALE;
2765 if (inset.fade_value >= i2f(GR_FADE_LEVELS - 1))
2766 {
2767 inset.old_weapon = {};
2768 inset.box_state = weapon_box_state::set;
2769 }
2770 }
2771 } else
2772 {
2773 draw_weapon_info(hudctx, player_info, weapon_num, player_info.laser_level, wt);
2774 inset.old_weapon = weapon_num;
2775 old_laser_level = player_info.laser_level;
2776 }
2777
2778 if (inset.box_state != weapon_box_state::set) //fade gauge
2779 {
2780 int fade_value = f2i(inset_window[wt].fade_value);
2781
2782 gr_settransblend(canvas, static_cast<gr_fade_level>(fade_value), gr_blend::normal);
2783 auto &resbox = gauge_boxes[multires_gauge_graphic.hiresmode];
2784 auto &weaponbox = resbox[wt];
2785 auto &g = weaponbox[(PlayerCfg.CockpitMode[1] == CM_STATUS_BAR) ? gauge_hud_type::statusbar : gauge_hud_type::cockpit];
2786 auto &canvas = hudctx.canvas;
2787 gr_rect(canvas, hudctx.xscale(g.left), hudctx.yscale(g.top), hudctx.xscale(g.right), hudctx.yscale(g.bot), 0);
2788
2789 gr_settransblend(canvas, GR_FADE_OFF, gr_blend::normal);
2790 }
2791 }
2792
2793 }
2794
2795 namespace dsx {
2796 namespace {
2797 #if defined(DXX_BUILD_DESCENT_II)
draw_static(const d_vclip_array & Vclip,const hud_draw_context_hs_mr hudctx,const gauge_inset_window_view win)2798 static void draw_static(const d_vclip_array &Vclip, const hud_draw_context_hs_mr hudctx, const gauge_inset_window_view win)
2799 {
2800 const vclip *const vc = &Vclip[VCLIP_MONITOR_STATIC];
2801 int framenum;
2802 auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
2803 #if !DXX_USE_OGL
2804 int x,y;
2805 #endif
2806
2807 auto &time_static_played = inset_window[win].time_static_played;
2808 time_static_played += FrameTime;
2809 if (time_static_played >= vc->play_time) {
2810 inset_window[win].user = weapon_box_user::weapon;
2811 return;
2812 }
2813
2814 framenum = time_static_played * vc->num_frames / vc->play_time;
2815
2816 PIGGY_PAGE_IN(vc->frames[framenum]);
2817
2818 auto &bmp = GameBitmaps[vc->frames[framenum].index];
2819 auto &resbox = gauge_boxes[multires_gauge_graphic.hiresmode];
2820 auto &weaponbox = resbox[win];
2821 auto &box = weaponbox[(PlayerCfg.CockpitMode[1] == CM_STATUS_BAR) ? gauge_hud_type::statusbar : gauge_hud_type::cockpit];
2822 #if !DXX_USE_OGL
2823 for (x = box.left; x < box.right; x += bmp.bm_w)
2824 for (y = box.top; y < box.bot; y += bmp.bm_h)
2825 gr_bitmap(hudctx.canvas, x, y, bmp);
2826 #else
2827 if (multires_gauge_graphic.is_hires())
2828 {
2829 const auto scaled_left = hudctx.xscale(box.left);
2830 const auto scaled_top = hudctx.yscale(box.top);
2831 const auto scaled_bottom = hudctx.yscale(box.bot - bmp.bm_h);
2832 hud_bitblt_scaled_xy(hudctx, scaled_left, scaled_top, bmp);
2833 hud_bitblt_scaled_xy(hudctx, scaled_left, scaled_bottom, bmp);
2834 const auto scaled_right = hudctx.xscale(box.right - bmp.bm_w);
2835 hud_bitblt_scaled_xy(hudctx, scaled_right, scaled_top, bmp);
2836 hud_bitblt_scaled_xy(hudctx, scaled_right, scaled_bottom, bmp);
2837 }
2838 #endif
2839 }
2840 #endif
2841
draw_weapon_box0(const hud_draw_context_hs_mr hudctx,const player_info & player_info)2842 static void draw_weapon_box0(const hud_draw_context_hs_mr hudctx, const player_info &player_info)
2843 {
2844 #if defined(DXX_BUILD_DESCENT_II)
2845 const auto user = inset_window[gauge_inset_window_view::primary].user;
2846 if (user == weapon_box_user::weapon)
2847 #endif
2848 {
2849 const auto Primary_weapon = player_info.Primary_weapon;
2850 draw_weapon_box(hudctx, player_info, Primary_weapon.get_active(), gauge_inset_window_view::primary);
2851
2852 if (inset_window[gauge_inset_window_view::primary].box_state == weapon_box_state::set)
2853 {
2854 unsigned nd_ammo;
2855 unsigned ammo_count;
2856 if (weapon_index_uses_vulcan_ammo(Primary_weapon))
2857 {
2858 nd_ammo = player_info.vulcan_ammo;
2859 ammo_count = vulcan_ammo_scale(nd_ammo);
2860 }
2861 #if defined(DXX_BUILD_DESCENT_II)
2862 else if (Primary_weapon == primary_weapon_index_t::OMEGA_INDEX)
2863 {
2864 auto &Omega_charge = player_info.Omega_charge;
2865 nd_ammo = Omega_charge;
2866 ammo_count = Omega_charge * 100/MAX_OMEGA_CHARGE;
2867 }
2868 #endif
2869 else
2870 return;
2871 if (Newdemo_state == ND_STATE_RECORDING)
2872 newdemo_record_primary_ammo(nd_ammo);
2873 draw_primary_ammo_info(hudctx, ammo_count);
2874 }
2875 }
2876 #if defined(DXX_BUILD_DESCENT_II)
2877 else if (user == weapon_box_user::post_missile_static)
2878 draw_static(Vclip, hudctx, gauge_inset_window_view::primary);
2879 #endif
2880 }
2881
draw_weapon_box1(const hud_draw_context_hs_mr hudctx,const player_info & player_info)2882 static void draw_weapon_box1(const hud_draw_context_hs_mr hudctx, const player_info &player_info)
2883 {
2884 #if defined(DXX_BUILD_DESCENT_II)
2885 if (inset_window[gauge_inset_window_view::secondary].user == weapon_box_user::weapon)
2886 #endif
2887 {
2888 auto &Secondary_weapon = player_info.Secondary_weapon;
2889 draw_weapon_box(hudctx, player_info, Secondary_weapon.get_active(), gauge_inset_window_view::secondary);
2890 if (inset_window[gauge_inset_window_view::secondary].box_state == weapon_box_state::set)
2891 {
2892 const auto ammo = player_info.secondary_ammo[Secondary_weapon];
2893 if (Newdemo_state == ND_STATE_RECORDING)
2894 newdemo_record_secondary_ammo(ammo);
2895 draw_secondary_ammo_info(hudctx, ammo);
2896 }
2897 }
2898 #if defined(DXX_BUILD_DESCENT_II)
2899 else if (inset_window[gauge_inset_window_view::secondary].user == weapon_box_user::post_missile_static)
2900 draw_static(Vclip, hudctx, gauge_inset_window_view::secondary);
2901 #endif
2902 }
2903
draw_weapon_boxes(const hud_draw_context_hs_mr hudctx,const player_info & player_info)2904 static void draw_weapon_boxes(const hud_draw_context_hs_mr hudctx, const player_info &player_info)
2905 {
2906 draw_weapon_box0(hudctx, player_info);
2907 draw_weapon_box1(hudctx, player_info);
2908 }
2909
sb_draw_energy_bar(const hud_draw_context_hs_mr hudctx,const unsigned energy)2910 static void sb_draw_energy_bar(const hud_draw_context_hs_mr hudctx, const unsigned energy)
2911 {
2912
2913 auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
2914 hud_gauge_bitblt(hudctx, SB_ENERGY_GAUGE_X, SB_ENERGY_GAUGE_Y, SB_GAUGE_ENERGY);
2915
2916 auto &canvas = hudctx.canvas;
2917 if (energy <= 100)
2918 {
2919 const auto color = 0;
2920 const int erase_x0 = i2f(hudctx.xscale(SB_ENERGY_GAUGE_X));
2921 const int erase_x1 = i2f(hudctx.xscale(SB_ENERGY_GAUGE_X + SB_ENERGY_GAUGE_W));
2922 const int erase_y_base = hudctx.yscale(SB_ENERGY_GAUGE_Y);
2923 for (int i = hudctx.yscale((100 - energy) * SB_ENERGY_GAUGE_H / 100); i-- > 0;)
2924 {
2925 const int erase_y = i2f(erase_y_base + i);
2926 gr_uline(canvas, erase_x0, erase_y, erase_x1, erase_y, color);
2927 }
2928 }
2929
2930 //draw numbers
2931 gr_set_fontcolor(canvas, BM_XRGB(25, 18, 6), -1);
2932 const auto ew = gr_get_string_size(*canvas.cv_font, get_gauge_width_string(energy)).width;
2933 #if defined(DXX_BUILD_DESCENT_I)
2934 unsigned y = SB_ENERGY_NUM_Y;
2935 #elif defined(DXX_BUILD_DESCENT_II)
2936 unsigned y = SB_ENERGY_GAUGE_Y + SB_ENERGY_GAUGE_H - GAME_FONT->ft_h - (GAME_FONT->ft_h / 4);
2937 #endif
2938 gr_printf(canvas, *canvas.cv_font, (grd_curscreen->get_screen_width() / 3) - (ew / 2), hudctx.yscale(y), "%d", energy);
2939 }
2940
2941 #if defined(DXX_BUILD_DESCENT_II)
sb_draw_afterburner(const hud_draw_context_hs_mr hudctx,const player_info & player_info)2942 static void sb_draw_afterburner(const hud_draw_context_hs_mr hudctx, const player_info &player_info)
2943 {
2944 auto &ab_str = "AB";
2945
2946 auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
2947 hud_gauge_bitblt(hudctx, SB_AFTERBURNER_GAUGE_X, SB_AFTERBURNER_GAUGE_Y, SB_GAUGE_AFTERBURNER);
2948
2949 const auto color = 0;
2950 const int erase_x0 = i2f(hudctx.xscale(SB_AFTERBURNER_GAUGE_X));
2951 const int erase_x1 = i2f(hudctx.xscale(SB_AFTERBURNER_GAUGE_X + (SB_AFTERBURNER_GAUGE_W)));
2952 const int erase_y_base = hudctx.yscale(SB_AFTERBURNER_GAUGE_Y);
2953 for (int i = hudctx.yscale(fixmul((f1_0 - Afterburner_charge), SB_AFTERBURNER_GAUGE_H)); i-- > 0;)
2954 {
2955 const int erase_y = i2f(erase_y_base + i);
2956 gr_uline(hudctx.canvas, erase_x0, erase_y, erase_x1, erase_y, color);
2957 }
2958
2959 //draw legend
2960 unsigned r, g, b;
2961 if (player_info.powerup_flags & PLAYER_FLAGS_AFTERBURNER)
2962 r = 90, g = b = 0;
2963 else
2964 r = g = b = 24;
2965 auto &canvas = hudctx.canvas;
2966 gr_set_fontcolor(canvas, gr_find_closest_color(r, g, b), -1);
2967
2968 const auto &&[w, h] = gr_get_string_size(*canvas.cv_font, ab_str);
2969 gr_string(canvas, *canvas.cv_font, hudctx.xscale(SB_AFTERBURNER_GAUGE_X + (SB_AFTERBURNER_GAUGE_W + 1) / 2) - (w / 2), hudctx.yscale(SB_AFTERBURNER_GAUGE_Y + (SB_AFTERBURNER_GAUGE_H - GAME_FONT->ft_h - (GAME_FONT->ft_h / 4))), ab_str, w, h);
2970 }
2971 #endif
2972
2973 }
2974 }
2975
2976 namespace {
2977
sb_draw_shield_num(const hud_draw_context_hs_mr hudctx,const unsigned shield)2978 static void sb_draw_shield_num(const hud_draw_context_hs_mr hudctx, const unsigned shield)
2979 {
2980 //draw numbers
2981 auto &canvas = hudctx.canvas;
2982 gr_set_fontcolor(canvas, BM_XRGB(14, 14, 23), -1);
2983
2984 auto &game_font = *GAME_FONT;
2985 const auto sw = gr_get_string_size(game_font, get_gauge_width_string(shield)).width;
2986 auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
2987 gr_printf(canvas, game_font, (grd_curscreen->get_screen_width() / 2.266) - (sw / 2), hudctx.yscale(SB_SHIELD_NUM_Y), "%d", shield);
2988 }
2989
sb_draw_shield_bar(const hud_draw_context_hs_mr hudctx,const int shield)2990 static void sb_draw_shield_bar(const hud_draw_context_hs_mr hudctx, const int shield)
2991 {
2992 int bm_num = shield>=100?9:(shield / 10);
2993 auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
2994 hud_gauge_bitblt(hudctx, SB_SHIELD_GAUGE_X, SB_SHIELD_GAUGE_Y, GAUGE_SHIELDS+9-bm_num);
2995 }
2996
2997 }
2998
2999 namespace dsx {
3000
3001 namespace {
3002
draw_all_statusbar_keys()3003 void draw_keys_state::draw_all_statusbar_keys()
3004 {
3005 auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
3006 draw_one_key(SB_GAUGE_KEYS_X, SB_GAUGE_BLUE_KEY_Y, SB_GAUGE_BLUE_KEY, PLAYER_FLAGS_BLUE_KEY);
3007 draw_one_key(SB_GAUGE_KEYS_X, SB_GAUGE_GOLD_KEY_Y, SB_GAUGE_GOLD_KEY, PLAYER_FLAGS_GOLD_KEY);
3008 draw_one_key(SB_GAUGE_KEYS_X, SB_GAUGE_RED_KEY_Y, SB_GAUGE_RED_KEY, PLAYER_FLAGS_RED_KEY);
3009 }
3010
3011 // Draws invulnerable ship, or maybe the flashing ship, depending on invulnerability time left.
draw_invulnerable_ship(const hud_draw_context_hs_mr hudctx,const object & plrobj)3012 static void draw_invulnerable_ship(const hud_draw_context_hs_mr hudctx, const object &plrobj)
3013 {
3014 auto &player_info = plrobj.ctype.player_info;
3015 const auto cmmode = PlayerCfg.CockpitMode[1];
3016 const auto t = player_info.invulnerable_time;
3017 if (t + INVULNERABLE_TIME_MAX - GameTime64 > F1_0*4 || GameTime64 & 0x8000)
3018 {
3019 static fix time;
3020 auto ltime = time + FrameTime;
3021 const auto old_invulnerable_frame = invulnerable_frame;
3022 while (ltime > INV_FRAME_TIME)
3023 {
3024 ltime -= INV_FRAME_TIME;
3025 if (++invulnerable_frame == N_INVULNERABLE_FRAMES)
3026 invulnerable_frame=0;
3027 }
3028 time = ltime;
3029 unsigned x, y;
3030 auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
3031 if (cmmode == CM_STATUS_BAR)
3032 {
3033 x = SB_SHIELD_GAUGE_X;
3034 y = SB_SHIELD_GAUGE_Y;
3035 }
3036 else
3037 {
3038 x = SHIELD_GAUGE_X;
3039 y = SHIELD_GAUGE_Y;
3040 }
3041 hud_gauge_bitblt(hudctx, x, y, GAUGE_INVULNERABLE + old_invulnerable_frame);
3042
3043 // Show Invulnerability Timer if enabled
3044 if (show_cloak_invul_timer())
3045 show_cockpit_cloak_invul_timer(hudctx.canvas, t + INVULNERABLE_TIME_MAX - GameTime64, hudctx.yscale(y));
3046 }
3047 else
3048 {
3049 const auto shields_ir = f2ir(plrobj.shields);
3050 if (cmmode == CM_STATUS_BAR)
3051 sb_draw_shield_bar(hudctx, shields_ir);
3052 else
3053 draw_shield_bar(hudctx, shields_ir);
3054 }
3055 }
3056
3057 }
3058
3059 }
3060
3061 const rgb_array_t player_rgb_normal{{
3062 {15,15,23},
3063 {27,0,0},
3064 {0,23,0},
3065 {30,11,31},
3066 {31,16,0},
3067 {24,17,6},
3068 {14,21,12},
3069 {29,29,0},
3070 }};
3071
3072 namespace {
3073
3074 struct xy {
3075 sbyte x, y;
3076 };
3077
3078 //offsets for reticle parts: high-big high-sml low-big low-sml
3079 const std::array<xy, 4> cross_offsets{{
3080 {-8,-5},
3081 {-4,-2},
3082 {-4,-2},
3083 {-2,-1}
3084 }},
3085 primary_offsets{{
3086 {-30,14},
3087 {-16,6},
3088 {-15,6},
3089 {-8, 2}
3090 }},
3091 secondary_offsets{{
3092 {-24,2},
3093 {-12,0},
3094 {-12,1},
3095 {-6,-2}
3096 }};
3097
3098 }
3099
3100 //draw the reticle
3101 namespace dsx {
show_reticle(grs_canvas & canvas,const player_info & player_info,int reticle_type,int secondary_display)3102 void show_reticle(grs_canvas &canvas, const player_info &player_info, int reticle_type, int secondary_display)
3103 {
3104 int x,y,size;
3105 int laser_ready,missile_ready;
3106 int cross_bm_num,primary_bm_num,secondary_bm_num;
3107 int gauge_index;
3108
3109 #if defined(DXX_BUILD_DESCENT_II)
3110 if (Newdemo_state==ND_STATE_PLAYBACK && Viewer->type != OBJ_PLAYER)
3111 return;
3112 #endif
3113
3114 x = canvas.cv_bitmap.bm_w/2;
3115 y = canvas.cv_bitmap.bm_h/2;
3116 size = (canvas.cv_bitmap.bm_h / (32-(PlayerCfg.ReticleSize*4)));
3117
3118 laser_ready = allowed_to_fire_laser(player_info);
3119
3120 missile_ready = allowed_to_fire_missile(player_info);
3121 auto &Primary_weapon = player_info.Primary_weapon;
3122 primary_bm_num = (laser_ready && player_has_primary_weapon(player_info, Primary_weapon).has_all());
3123 auto &Secondary_weapon = player_info.Secondary_weapon;
3124 secondary_bm_num = (missile_ready && player_has_secondary_weapon(player_info, Secondary_weapon).has_all());
3125
3126 if (primary_bm_num && Primary_weapon == primary_weapon_index_t::LASER_INDEX && (player_info.powerup_flags & PLAYER_FLAGS_QUAD_LASERS))
3127 primary_bm_num++;
3128
3129 if (Secondary_weapon_to_gun_num[Secondary_weapon]==7)
3130 secondary_bm_num += 3; //now value is 0,1 or 3,4
3131 else if (secondary_bm_num && !(player_info.missile_gun & 1))
3132 secondary_bm_num++;
3133
3134 cross_bm_num = ((primary_bm_num > 0) || (secondary_bm_num > 0));
3135
3136 Assert(primary_bm_num <= 2);
3137 Assert(secondary_bm_num <= 4);
3138 Assert(cross_bm_num <= 1);
3139
3140 const auto color = BM_XRGB(PlayerCfg.ReticleRGBA[0],PlayerCfg.ReticleRGBA[1],PlayerCfg.ReticleRGBA[2]);
3141 gr_settransblend(canvas, static_cast<gr_fade_level>(PlayerCfg.ReticleRGBA[3]), gr_blend::normal);
3142
3143 [&]{
3144 int x0, x1, y0, y1;
3145 switch (reticle_type)
3146 {
3147 case RET_TYPE_CLASSIC:
3148 {
3149 const local_multires_gauge_graphic multires_gauge_graphic{};
3150 const hud_draw_context_hs_mr hudctx(canvas, grd_curscreen->get_screen_width(), grd_curscreen->get_screen_height(), multires_gauge_graphic);
3151 const auto use_hires_reticle = multires_gauge_graphic.is_hires();
3152 const unsigned ofs = (use_hires_reticle ? 0 : 2);
3153 gauge_index = RETICLE_CROSS + cross_bm_num;
3154 PAGE_IN_GAUGE(gauge_index, multires_gauge_graphic);
3155 auto &cross = GameBitmaps[GET_GAUGE_INDEX(gauge_index)];
3156 const auto &&hud_scale_ar = HUD_SCALE_AR(hudctx.xscale, hudctx.yscale);
3157 hud_bitblt_free(canvas, x + hud_scale_ar(cross_offsets[ofs].x), y + hud_scale_ar(cross_offsets[ofs].y), hud_scale_ar(cross.bm_w), hud_scale_ar(cross.bm_h), cross);
3158
3159 gauge_index = RETICLE_PRIMARY + primary_bm_num;
3160 PAGE_IN_GAUGE(gauge_index, multires_gauge_graphic);
3161 auto &primary = GameBitmaps[GET_GAUGE_INDEX(gauge_index)];
3162 hud_bitblt_free(canvas, x + hud_scale_ar(primary_offsets[ofs].x), y + hud_scale_ar(primary_offsets[ofs].y), hud_scale_ar(primary.bm_w), hud_scale_ar(primary.bm_h), primary);
3163
3164 gauge_index = RETICLE_SECONDARY + secondary_bm_num;
3165 PAGE_IN_GAUGE(gauge_index, multires_gauge_graphic);
3166 auto &secondary = GameBitmaps[GET_GAUGE_INDEX(gauge_index)];
3167 hud_bitblt_free(canvas, x + hud_scale_ar(secondary_offsets[ofs].x), y + hud_scale_ar(secondary_offsets[ofs].y), hud_scale_ar(secondary.bm_w), hud_scale_ar(secondary.bm_h), secondary);
3168 return;
3169 }
3170 case RET_TYPE_CLASSIC_REBOOT:
3171 #if DXX_USE_OGL
3172 ogl_draw_vertex_reticle(canvas, cross_bm_num,primary_bm_num,secondary_bm_num,BM_XRGB(PlayerCfg.ReticleRGBA[0],PlayerCfg.ReticleRGBA[1],PlayerCfg.ReticleRGBA[2]),PlayerCfg.ReticleRGBA[3],PlayerCfg.ReticleSize);
3173 #endif
3174 return;
3175 case RET_TYPE_X:
3176 {
3177 gr_uline(canvas, i2f(x-(size/2)), i2f(y-(size/2)), i2f(x-(size/5)), i2f(y-(size/5)), color); // top-left
3178 gr_uline(canvas, i2f(x+(size/2)), i2f(y-(size/2)), i2f(x+(size/5)), i2f(y-(size/5)), color); // top-right
3179 gr_uline(canvas, i2f(x-(size/2)), i2f(y+(size/2)), i2f(x-(size/5)), i2f(y+(size/5)), color); // bottom-left
3180 gr_uline(canvas, i2f(x+(size/2)), i2f(y+(size/2)), i2f(x+(size/5)), i2f(y+(size/5)), color); // bottom-right
3181 if (secondary_display && secondary_bm_num == 1)
3182 x0 = i2f(x-(size/2)-(size/5)), y0 = i2f(y-(size/2)), x1 = i2f(x-(size/5)-(size/5)), y1 = i2f(y-(size/5));
3183 else if (secondary_display && secondary_bm_num == 2)
3184 x0 = i2f(x+(size/2)+(size/5)), y0 = i2f(y-(size/2)), x1 = i2f(x+(size/5)+(size/5)), y1 = i2f(y-(size/5));
3185 else if (secondary_display && secondary_bm_num == 4)
3186 x0 = i2f(x+(size/2)), y0 = i2f(y+(size/2)+(size/5)), x1 = i2f(x+(size/5)), y1 = i2f(y+(size/5)+(size/5));
3187 else
3188 return;
3189 }
3190 break;
3191 case RET_TYPE_DOT:
3192 {
3193 gr_disk(canvas, i2f(x), i2f(y), i2f(size/5), color);
3194 if (secondary_display && secondary_bm_num == 1)
3195 x0 = i2f(x-(size/2)-(size/5)), y0 = i2f(y-(size/2)), x1 = i2f(x-(size/5)-(size/5)), y1 = i2f(y-(size/5));
3196 else if (secondary_display && secondary_bm_num == 2)
3197 x0 = i2f(x+(size/2)+(size/5)), y0 = i2f(y-(size/2)), x1 = i2f(x+(size/5)+(size/5)), y1 = i2f(y-(size/5));
3198 else if (secondary_display && secondary_bm_num == 4)
3199 x0 = i2f(x), y0 = i2f(y+(size/2)+(size/5)), x1 = i2f(x), y1 = i2f(y+(size/5)+(size/5));
3200 else
3201 return;
3202 }
3203 break;
3204 case RET_TYPE_CIRCLE:
3205 {
3206 gr_ucircle(canvas, i2f(x), i2f(y), i2f(size/4), color);
3207 if (secondary_display && secondary_bm_num == 1)
3208 x0 = i2f(x-(size/2)-(size/5)), y0 = i2f(y-(size/2)), x1 = i2f(x-(size/5)-(size/5)), y1 = i2f(y-(size/5));
3209 else if (secondary_display && secondary_bm_num == 2)
3210 x0 = i2f(x+(size/2)+(size/5)), y0 = i2f(y-(size/2)), x1 = i2f(x+(size/5)+(size/5)), y1 = i2f(y-(size/5));
3211 else if (secondary_display && secondary_bm_num == 4)
3212 x0 = i2f(x), y0 = i2f(y+(size/2)+(size/5)), x1 = i2f(x), y1 = i2f(y+(size/5)+(size/5));
3213 else
3214 return;
3215 }
3216 break;
3217 case RET_TYPE_CROSS_V1:
3218 {
3219 gr_uline(canvas, i2f(x),i2f(y-(size/2)),i2f(x),i2f(y+(size/2)+1), color); // horiz
3220 gr_uline(canvas, i2f(x-(size/2)),i2f(y),i2f(x+(size/2)+1),i2f(y), color); // vert
3221 if (secondary_display && secondary_bm_num == 1)
3222 x0 = i2f(x-(size/2)), y0 = i2f(y-(size/2)), x1 = i2f(x-(size/5)), y1 = i2f(y-(size/5));
3223 else if (secondary_display && secondary_bm_num == 2)
3224 x0 = i2f(x+(size/2)), y0 = i2f(y-(size/2)), x1 = i2f(x+(size/5)), y1 = i2f(y-(size/5));
3225 else if (secondary_display && secondary_bm_num == 4)
3226 x0 = i2f(x-(size/2)), y0 = i2f(y+(size/2)), x1 = i2f(x-(size/5)), y1 = i2f(y+(size/5));
3227 else
3228 return;
3229 }
3230 break;
3231 case RET_TYPE_CROSS_V2:
3232 {
3233 gr_uline(canvas, i2f(x), i2f(y-(size/2)), i2f(x), i2f(y-(size/6)), color); // vert-top
3234 gr_uline(canvas, i2f(x), i2f(y+(size/2)), i2f(x), i2f(y+(size/6)), color); // vert-bottom
3235 gr_uline(canvas, i2f(x-(size/2)), i2f(y), i2f(x-(size/6)), i2f(y), color); // horiz-left
3236 gr_uline(canvas, i2f(x+(size/2)), i2f(y), i2f(x+(size/6)), i2f(y), color); // horiz-right
3237 if (secondary_display && secondary_bm_num == 1)
3238 x0 = i2f(x-(size/2)), y0 = i2f(y-(size/2)), x1 = i2f(x-(size/5)), y1 = i2f(y-(size/5));
3239 else if (secondary_display && secondary_bm_num == 2)
3240 x0 = i2f(x+(size/2)), y0 = i2f(y-(size/2)), x1 = i2f(x+(size/5)), y1 = i2f(y-(size/5));
3241 else if (secondary_display && secondary_bm_num == 4)
3242 x0 = i2f(x-(size/2)), y0 = i2f(y+(size/2)), x1 = i2f(x-(size/5)), y1 = i2f(y+(size/5));
3243 else
3244 return;
3245 }
3246 break;
3247 case RET_TYPE_ANGLE:
3248 {
3249 gr_uline(canvas, i2f(x),i2f(y),i2f(x),i2f(y+(size/2)), color); // vert
3250 gr_uline(canvas, i2f(x),i2f(y),i2f(x+(size/2)),i2f(y), color); // horiz
3251 if (secondary_display && secondary_bm_num == 1)
3252 x0 = i2f(x-(size/2)), y0 = i2f(y-(size/2)), x1 = i2f(x-(size/5)), y1 = i2f(y-(size/5));
3253 else if (secondary_display && secondary_bm_num == 2)
3254 x0 = i2f(x+(size/2)), y0 = i2f(y-(size/2)), x1 = i2f(x+(size/5)), y1 = i2f(y-(size/5));
3255 else if (secondary_display && secondary_bm_num == 4)
3256 x0 = i2f(x-(size/2)), y0 = i2f(y+(size/2)), x1 = i2f(x-(size/5)), y1 = i2f(y+(size/5));
3257 else
3258 return;
3259 }
3260 break;
3261 case RET_TYPE_NONE:
3262 default:
3263 return;
3264 }
3265 gr_uline(canvas, x0, y0, x1, y1, color);
3266 }();
3267 gr_settransblend(canvas, GR_FADE_OFF, gr_blend::normal);
3268 }
3269 }
3270
3271 namespace dcx {
3272
show_mousefs_indicator(grs_canvas & canvas,int mx,int my,int mz,int x,int y,int size)3273 void show_mousefs_indicator(grs_canvas &canvas, int mx, int my, int mz, int x, int y, int size)
3274 {
3275 int axscale = (MOUSEFS_DELTA_RANGE*2)/size, xaxpos = x+(mx/axscale), yaxpos = y+(my/axscale), zaxpos = y+(mz/axscale);
3276
3277 gr_settransblend(canvas, static_cast<gr_fade_level>(PlayerCfg.ReticleRGBA[3]), gr_blend::normal);
3278 auto &rgba = PlayerCfg.ReticleRGBA;
3279 const auto color = BM_XRGB(rgba[0], rgba[1], rgba[2]);
3280 gr_uline(canvas, i2f(xaxpos), i2f(y-(size/2)), i2f(xaxpos), i2f(y-(size/4)), color);
3281 gr_uline(canvas, i2f(xaxpos), i2f(y+(size/2)), i2f(xaxpos), i2f(y+(size/4)), color);
3282 gr_uline(canvas, i2f(x-(size/2)), i2f(yaxpos), i2f(x-(size/4)), i2f(yaxpos), color);
3283 gr_uline(canvas, i2f(x+(size/2)), i2f(yaxpos), i2f(x+(size/4)), i2f(yaxpos), color);
3284 const local_multires_gauge_graphic multires_gauge_graphic{};
3285 auto &&hud_scale_ar = HUD_SCALE_AR(grd_curscreen->get_screen_width(), grd_curscreen->get_screen_height(), multires_gauge_graphic);
3286 auto &&hud_scale_ar2 = hud_scale_ar(2);
3287 gr_uline(canvas, i2f(x + (size / 2) + hud_scale_ar2), i2f(y), i2f(x + (size / 2) + hud_scale_ar2), i2f(zaxpos), color);
3288 gr_settransblend(canvas, GR_FADE_OFF, gr_blend::normal);
3289 }
3290
3291 }
3292
3293 namespace dsx {
3294 namespace {
3295
hud_show_kill_list(fvcobjptr & vcobjptr,grs_canvas & canvas,const game_mode_flags Game_mode)3296 static void hud_show_kill_list(fvcobjptr &vcobjptr, grs_canvas &canvas, const game_mode_flags Game_mode)
3297 {
3298 playernum_t n_players;
3299 playernum_array_t player_list;
3300 int n_left,i,x0,x1,x2,y,save_y;
3301
3302 if (Show_kill_list_timer > 0)
3303 {
3304 Show_kill_list_timer -= FrameTime;
3305 if (Show_kill_list_timer < 0)
3306 Show_kill_list = show_kill_list_mode::None;
3307 }
3308
3309 n_players = multi_get_kill_list(player_list);
3310
3311 if (Show_kill_list == show_kill_list_mode::team_kills)
3312 n_players = 2;
3313
3314 if (n_players <= 4)
3315 n_left = n_players;
3316 else
3317 n_left = (n_players+1)/2;
3318
3319 const auto &&fspacx = FSPACX();
3320 const auto &&fspacx43 = fspacx(43);
3321
3322 x1 = fspacx43;
3323
3324 const auto is_multiplayer_cooperative = Game_mode & GM_MULTI_COOP;
3325 if (is_multiplayer_cooperative)
3326 x1 = fspacx(31);
3327
3328 auto &game_font = *GAME_FONT;
3329 const auto &&line_spacing = LINE_SPACING(game_font, game_font);
3330 save_y = y = canvas.cv_bitmap.bm_h - n_left * line_spacing;
3331
3332 if (PlayerCfg.CockpitMode[1] == CM_FULL_COCKPIT) {
3333 save_y = y -= fspacx(6);
3334 if (is_multiplayer_cooperative)
3335 x1 = fspacx(33);
3336 }
3337
3338 const auto bm_w = canvas.cv_bitmap.bm_w;
3339 const auto &&bmw_x0_cockpit = bm_w - fspacx(PlayerCfg.CockpitMode[1] == CM_FULL_COCKPIT ? 53 : 60);
3340 // Right edge of name, change this for width problems
3341 const auto &&bmw_x1_multi = bm_w - fspacx(is_multiplayer_cooperative ? 27 : 15);
3342 const auto &&fspacx1 = fspacx(1);
3343 const auto &&fspacx2 = fspacx(2);
3344 const auto &&fspacx18 = fspacx(18);
3345 const auto &&fspacx35 = fspacx(35);
3346 const auto &&fspacx64 = fspacx(64);
3347 x0 = fspacx1;
3348 for (i=0;i<n_players;i++) {
3349 playernum_t player_num;
3350 callsign_t name;
3351
3352 if (i>=n_left) {
3353 x0 = bmw_x0_cockpit;
3354 x1 = bmw_x1_multi;
3355 if (PlayerCfg.MultiPingHud)
3356 {
3357 x0 -= fspacx35;
3358 x1 -= fspacx35;
3359 }
3360 if (i==n_left)
3361 y = save_y;
3362
3363 if (Netgame.KillGoal || Netgame.PlayTimeAllowed.count())
3364 x1 -= fspacx18;
3365 }
3366 else if (Netgame.KillGoal || Netgame.PlayTimeAllowed.count())
3367 {
3368 x1 = fspacx43;
3369 x1 -= fspacx18;
3370 }
3371
3372 if (Show_kill_list == show_kill_list_mode::team_kills)
3373 player_num = i;
3374 else
3375 player_num = player_list[i];
3376 auto &p = *vcplayerptr(player_num);
3377
3378 color_t fontcolor;
3379 rgb color;
3380 if (Show_kill_list == show_kill_list_mode::_1 || Show_kill_list == show_kill_list_mode::efficiency)
3381 {
3382 if (vcplayerptr(player_num)->connected != CONNECT_PLAYING)
3383 color.r = color.g = color.b = 12;
3384 else {
3385 color = player_rgb[get_player_or_team_color(player_num)];
3386 }
3387 }
3388 else
3389 {
3390 color = player_rgb_normal[player_num];
3391 }
3392 fontcolor = BM_XRGB(color.r, color.g, color.b);
3393 gr_set_fontcolor(canvas, fontcolor, -1);
3394
3395 if (Show_kill_list == show_kill_list_mode::team_kills)
3396 name = Netgame.team_name[i];
3397 else if ((Game_mode & GM_BOUNTY) && player_num == Bounty_target && (GameTime64 & 0x10000))
3398 {
3399 name = "[TARGET]";
3400 }
3401 else
3402 name = vcplayerptr(player_num)->callsign; // Note link to above if!!
3403 auto [sw, sh] = gr_get_string_size(game_font, static_cast<const char *>(name));
3404 {
3405 const auto b = x1 - x0 - fspacx2;
3406 if (sw > b)
3407 for (char *e = &name.buffer()[strlen(name)];;)
3408 {
3409 *--e = 0;
3410 sw = gr_get_string_size(game_font, name).width;
3411 if (!(sw > b))
3412 break;
3413 }
3414 }
3415 gr_string(canvas, game_font, x0, y, name, sw, sh);
3416
3417 auto &player_info = vcobjptr(p.objnum)->ctype.player_info;
3418 if (Show_kill_list == show_kill_list_mode::efficiency)
3419 {
3420 const int eff = (player_info.net_killed_total + player_info.net_kills_total <= 0)
3421 ? 0
3422 : static_cast<int>(
3423 static_cast<float>(player_info.net_kills_total) / (
3424 static_cast<float>(player_info.net_killed_total) + static_cast<float>(player_info.net_kills_total)
3425 ) * 100.0
3426 );
3427 gr_printf(canvas, game_font, x1, y, "%i%%", eff <= 0 ? 0 : eff);
3428 }
3429 else if (Show_kill_list == show_kill_list_mode::team_kills)
3430 gr_printf(canvas, game_font, x1, y, "%3d", team_kills[i]);
3431 else if (is_multiplayer_cooperative)
3432 gr_printf(canvas, game_font, x1, y, "%-6d", player_info.mission.score);
3433 else if (Netgame.KillGoal || Netgame.PlayTimeAllowed.count())
3434 gr_printf(canvas, game_font, x1, y, "%3d(%d)", player_info.net_kills_total, player_info.KillGoalCount);
3435 else
3436 gr_printf(canvas, game_font, x1, y, "%3d", player_info.net_kills_total);
3437
3438 if (PlayerCfg.MultiPingHud && Show_kill_list != show_kill_list_mode::team_kills)
3439 {
3440 if (is_multiplayer_cooperative)
3441 x2 = SWIDTH - (fspacx64/2);
3442 else
3443 x2 = x0 + fspacx64;
3444 gr_printf(canvas, game_font, x2, y, "%4dms", Netgame.players[player_num].ping);
3445 }
3446
3447 y += line_spacing;
3448 }
3449 }
3450
3451 //returns true if viewer can see object
see_object(fvcobjptridx & vcobjptridx,const vcobjptridx_t objnum)3452 static int see_object(fvcobjptridx &vcobjptridx, const vcobjptridx_t objnum)
3453 {
3454 fvi_query fq;
3455 int hit_type;
3456 fvi_info hit_data;
3457
3458 //see if we can see this player
3459
3460 fq.p0 = &Viewer->pos;
3461 fq.p1 = &objnum->pos;
3462 fq.rad = 0;
3463 fq.thisobjnum = vcobjptridx(Viewer);
3464 fq.flags = FQ_TRANSWALL | FQ_CHECK_OBJS | FQ_GET_SEGLIST;
3465 fq.startseg = Viewer->segnum;
3466 fq.ignore_obj_list.first = nullptr;
3467
3468 hit_type = find_vector_intersection(fq, hit_data);
3469
3470 return (hit_type == HIT_OBJECT && hit_data.hit_object == objnum);
3471 }
3472
3473 }
3474
3475 //show names of teammates & players carrying flags
3476
show_HUD_names(grs_canvas & canvas,const game_mode_flags Game_mode)3477 void show_HUD_names(grs_canvas &canvas, const game_mode_flags Game_mode)
3478 {
3479 auto &Objects = LevelUniqueObjectState.Objects;
3480 auto &vcobjptr = Objects.vcptr;
3481 auto &vcobjptridx = Objects.vcptridx;
3482 for (playernum_t pnum = 0;pnum < N_players; ++pnum)
3483 {
3484 if (pnum == Player_num || vcplayerptr(pnum)->connected != CONNECT_PLAYING)
3485 continue;
3486 // ridiculusly complex to check if we want to show something... but this is readable at least.
3487
3488 objnum_t objnum;
3489 if (Newdemo_state == ND_STATE_PLAYBACK) {
3490 //if this is a demo, the objnum in the player struct is wrong, so we search the object list for the objnum
3491 for (objnum=0;objnum<=Highest_object_index;objnum++)
3492 {
3493 auto &objp = *vcobjptr(objnum);
3494 if (objp.type == OBJ_PLAYER && get_player_id(objp) == pnum)
3495 break;
3496 }
3497 if (objnum > Highest_object_index) //not in list, thus not visible
3498 continue; //..so don't show name
3499 }
3500 else
3501 objnum = vcplayerptr(pnum)->objnum;
3502
3503 const auto &&objp = vcobjptridx(objnum);
3504 const auto &pl_flags = objp->ctype.player_info.powerup_flags;
3505 const auto is_friend = (Game_mode & GM_MULTI_COOP || (Game_mode & GM_TEAM && get_team(pnum) == get_team(Player_num)));
3506 const auto show_friend_name = Show_reticle_name;
3507 const auto is_cloaked = pl_flags & PLAYER_FLAGS_CLOAKED;
3508 const auto show_enemy_name = Show_reticle_name && Netgame.ShowEnemyNames && !is_cloaked;
3509 const auto show_name = is_friend ? show_friend_name : show_enemy_name;
3510 const auto show_typing = is_friend || !is_cloaked;
3511 const auto is_bounty_target = (Game_mode & GM_BOUNTY) && pnum == Bounty_target;
3512 const auto show_indi = (is_friend || !is_cloaked) &&
3513 #if defined(DXX_BUILD_DESCENT_I)
3514 is_bounty_target;
3515 #elif defined(DXX_BUILD_DESCENT_II)
3516 (is_bounty_target || ((game_mode_capture_flag() || game_mode_hoard()) && (pl_flags & PLAYER_FLAGS_FLAG)));
3517 #endif
3518
3519 if ((show_name || show_typing || show_indi) && see_object(vcobjptridx, objp))
3520 {
3521 auto player_point = g3_rotate_point(objp->pos);
3522 if (player_point.p3_codes == 0) //on screen
3523 {
3524 g3_project_point(player_point);
3525 if (!(player_point.p3_flags & PF_OVERFLOW))
3526 {
3527 fix x,y,dx,dy;
3528 char s[CALLSIGN_LEN+10];
3529 int x1, y1;
3530
3531 x = player_point.p3_sx;
3532 y = player_point.p3_sy;
3533 dy = -fixmuldiv(fixmul(objp->size, Matrix_scale.y), i2f(canvas.cv_bitmap.bm_h) / 2, player_point.p3_z);
3534 dx = fixmul(dy,grd_curscreen->sc_aspect);
3535 /* Set the text to show */
3536 const char *name = NULL;
3537 if(is_bounty_target)
3538 name = "Target";
3539 else if (show_name)
3540 name = static_cast<const char *>(vcplayerptr(pnum)->callsign);
3541 const char *trailer = NULL;
3542 if (show_typing)
3543 {
3544 if (multi_sending_message[pnum] == msgsend_state::typing)
3545 trailer = "Typing";
3546 else if (multi_sending_message[pnum] == msgsend_state::automap)
3547 trailer = "Map";
3548 }
3549 int written = snprintf(s, sizeof(s), "%s%s%s", name ? name : "", name && trailer ? ", " : "", trailer ? trailer : "");
3550 if (written)
3551 {
3552 const auto &&[w, h] = gr_get_string_size(*canvas.cv_font, s);
3553 const auto color = get_player_or_team_color(pnum);
3554 gr_set_fontcolor(canvas, BM_XRGB(player_rgb[color].r, player_rgb[color].g, player_rgb[color].b), -1);
3555 x1 = f2i(x)-w/2;
3556 y1 = f2i(y-dy)+FSPACY(1);
3557 gr_string(canvas, *canvas.cv_font, x1, y1, s, w, h);
3558 }
3559
3560 /* Draw box on HUD */
3561 if (show_indi)
3562 {
3563 fix w,h;
3564
3565 w = dx/4;
3566 h = dy/4;
3567
3568 struct {
3569 int r, g, b;
3570 } c{};
3571 #if defined(DXX_BUILD_DESCENT_II)
3572 if (game_mode_capture_flag())
3573 ((get_team(pnum) == TEAM_BLUE) ? c.r : c.b) = 31;
3574 else if (game_mode_hoard())
3575 {
3576 ((Game_mode & GM_TEAM)
3577 ? ((get_team(pnum) == TEAM_RED) ? c.r : c.b)
3578 : c.g
3579 ) = 31;
3580 }
3581 else
3582 #endif
3583 {
3584 auto &color = player_rgb[get_player_color(pnum)];
3585 c = {color.r, color.g, color.b};
3586 }
3587 const uint8_t color = BM_XRGB(c.r, c.g, c.b);
3588
3589 gr_line(canvas, x + dx - w, y - dy, x + dx, y - dy, color);
3590 gr_line(canvas, x + dx, y - dy, x + dx, y - dy + h, color);
3591 gr_line(canvas, x - dx, y - dy, x - dx + w, y - dy, color);
3592 gr_line(canvas, x - dx, y - dy, x - dx, y - dy + h, color);
3593 gr_line(canvas, x + dx - w, y + dy, x + dx, y + dy, color);
3594 gr_line(canvas, x + dx, y + dy, x + dx, y + dy - h, color);
3595 gr_line(canvas, x - dx, y + dy, x - dx + w, y + dy, color);
3596 gr_line(canvas, x - dx, y + dy, x - dx, y + dy - h, color);
3597 }
3598 }
3599 }
3600 }
3601 }
3602 }
3603
3604 //draw all the things on the HUD
draw_hud(grs_canvas & canvas,const object & plrobj,const control_info & Controls,const game_mode_flags Game_mode)3605 void draw_hud(grs_canvas &canvas, const object &plrobj, const control_info &Controls, const game_mode_flags Game_mode)
3606 {
3607 auto &Objects = LevelUniqueObjectState.Objects;
3608 auto &vcobjptr = Objects.vcptr;
3609 auto &player_info = plrobj.ctype.player_info;
3610 if (Newdemo_state == ND_STATE_RECORDING)
3611 {
3612 int ammo;
3613 auto &Primary_weapon = player_info.Primary_weapon;
3614 if ((Primary_weapon == primary_weapon_index_t::VULCAN_INDEX && (ammo = player_info.vulcan_ammo, true))
3615 #if defined(DXX_BUILD_DESCENT_II)
3616 ||
3617 (Primary_weapon == primary_weapon_index_t::OMEGA_INDEX && (ammo = player_info.Omega_charge, true))
3618 #endif
3619 )
3620 newdemo_record_primary_ammo(ammo);
3621 newdemo_record_secondary_ammo(player_info.secondary_ammo[player_info.Secondary_weapon]);
3622 }
3623 if (PlayerCfg.HudMode == HudType::Hidden) // no hud, "immersion mode"
3624 return;
3625
3626 // Cruise speed
3627 if (Viewer->type == OBJ_PLAYER && get_player_id(vcobjptr(Viewer)) == Player_num && PlayerCfg.CockpitMode[1] != CM_REAR_VIEW)
3628 {
3629 int x = FSPACX(1);
3630 int y = canvas.cv_bitmap.bm_h;
3631
3632 gr_set_fontcolor(canvas, BM_XRGB(0, 31, 0), -1);
3633 if (Cruise_speed > 0) {
3634 auto &game_font = *GAME_FONT;
3635 const auto &&line_spacing = LINE_SPACING(game_font, game_font);
3636 if (PlayerCfg.CockpitMode[1]==CM_FULL_SCREEN) {
3637 y -= (Game_mode & GM_MULTI)
3638 ? line_spacing * 10
3639 : line_spacing * 6;
3640 } else if (PlayerCfg.CockpitMode[1] == CM_STATUS_BAR) {
3641 y -= (Game_mode & GM_MULTI)
3642 ? line_spacing * 6
3643 : line_spacing * 1;
3644 } else {
3645 y -= (Game_mode & GM_MULTI)
3646 ? line_spacing * 7
3647 : line_spacing * 2;
3648 }
3649
3650 gr_printf(canvas, game_font, x, y, "%s %2d%%", TXT_CRUISE, f2i(Cruise_speed) );
3651 }
3652 }
3653
3654 // Show score so long as not in rearview
3655 if ( !Rear_view && PlayerCfg.CockpitMode[1]!=CM_REAR_VIEW && PlayerCfg.CockpitMode[1]!=CM_STATUS_BAR) {
3656 hud_show_score(canvas, player_info, Game_mode);
3657 if (score_time)
3658 hud_show_score_added(canvas, Game_mode);
3659 }
3660
3661 if ( !Rear_view && PlayerCfg.CockpitMode[1]!=CM_REAR_VIEW)
3662 hud_show_timer_count(canvas, Game_mode);
3663
3664 // Show other stuff if not in rearview or letterbox.
3665 if (!Rear_view && PlayerCfg.CockpitMode[1]!=CM_REAR_VIEW)
3666 {
3667 show_HUD_names(canvas, Game_mode);
3668
3669 if (PlayerCfg.CockpitMode[1]==CM_STATUS_BAR || PlayerCfg.CockpitMode[1]==CM_FULL_SCREEN)
3670 hud_show_homing_warning(canvas, player_info.homing_object_dist);
3671
3672 const local_multires_gauge_graphic multires_gauge_graphic = {};
3673 const hud_draw_context_hs_mr hudctx(canvas, grd_curscreen->get_screen_width(), grd_curscreen->get_screen_height(), multires_gauge_graphic);
3674 if (PlayerCfg.CockpitMode[1]==CM_FULL_SCREEN) {
3675
3676 auto &game_font = *GAME_FONT;
3677 const auto &&line_spacing = LINE_SPACING(game_font, game_font);
3678 const unsigned base_y = canvas.cv_bitmap.bm_h - ((Game_mode & GM_MULTI) ? (line_spacing * (5 + (N_players > 3))) : line_spacing);
3679 unsigned current_y = base_y;
3680 hud_show_energy(canvas, player_info, game_font, current_y);
3681 current_y -= line_spacing;
3682 hud_show_shield(canvas, plrobj, game_font, current_y);
3683 current_y -= line_spacing;
3684 #if defined(DXX_BUILD_DESCENT_II)
3685 hud_show_afterburner(canvas, player_info, game_font, current_y);
3686 current_y -= line_spacing;
3687 #endif
3688 hud_show_weapons(canvas, plrobj, game_font, Game_mode & GM_MULTI);
3689 #if defined(DXX_BUILD_DESCENT_I)
3690 if (!PCSharePig)
3691 #endif
3692 {
3693 if (!((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP)))
3694 /* In non-cooperative multiplayer games, everyone always has all
3695 * keys. Hide them to reduce visual clutter.
3696 */
3697 hud_show_keys(hudctx, HUD_SCALE_AR(hudctx.xscale, hudctx.yscale), player_info);
3698 }
3699 hud_show_cloak_invuln(canvas, player_info.powerup_flags, player_info.cloak_time, player_info.invulnerable_time, current_y);
3700
3701 if (Newdemo_state==ND_STATE_RECORDING)
3702 newdemo_record_player_flags(player_info.powerup_flags.get_player_flags());
3703 }
3704
3705 #ifndef RELEASE
3706 if (!(Game_mode&GM_MULTI && Show_kill_list != show_kill_list_mode::None))
3707 show_time(canvas, *canvas.cv_font);
3708 #endif
3709
3710 #if defined(DXX_BUILD_DESCENT_II)
3711 if (PlayerCfg.CockpitMode[1] != CM_LETTERBOX && PlayerCfg.CockpitMode[1] != CM_REAR_VIEW)
3712 {
3713 hud_show_flag(canvas, player_info, multires_gauge_graphic);
3714 hud_show_orbs(canvas, player_info, multires_gauge_graphic);
3715 }
3716 #endif
3717 HUD_render_message_frame(canvas);
3718
3719 if (PlayerCfg.CockpitMode[1]!=CM_STATUS_BAR)
3720 hud_show_lives(hudctx, HUD_SCALE_AR(grd_curscreen->get_screen_width(), grd_curscreen->get_screen_height(), multires_gauge_graphic), player_info, Game_mode & GM_MULTI);
3721 if (Game_mode&GM_MULTI && Show_kill_list != show_kill_list_mode::None)
3722 hud_show_kill_list(vcobjptr, canvas, Game_mode);
3723 if (PlayerCfg.CockpitMode[1] != CM_LETTERBOX)
3724 show_reticle(canvas, player_info, PlayerCfg.ReticleType, 1);
3725 if (PlayerCfg.CockpitMode[1] != CM_LETTERBOX && Newdemo_state != ND_STATE_PLAYBACK && PlayerCfg.MouseFlightSim && PlayerCfg.MouseFSIndicator)
3726 {
3727 const auto gwidth = canvas.cv_bitmap.bm_w;
3728 const auto gheight = canvas.cv_bitmap.bm_h;
3729 auto &raw_mouse_axis = Controls.raw_mouse_axis;
3730 show_mousefs_indicator(canvas, raw_mouse_axis[0], raw_mouse_axis[1], raw_mouse_axis[2], gwidth / 2, gheight / 2, gheight / 4);
3731 }
3732 }
3733
3734 if (Rear_view && PlayerCfg.CockpitMode[1]!=CM_REAR_VIEW) {
3735 HUD_render_message_frame(canvas);
3736 gr_set_fontcolor(canvas, BM_XRGB(0, 31, 0), -1);
3737 auto &game_font = *GAME_FONT;
3738 gr_string(canvas, game_font, 0x8000, canvas.cv_bitmap.bm_h - LINE_SPACING(game_font, game_font), TXT_REAR_VIEW);
3739 }
3740 }
3741
3742 //print out some player statistics
render_gauges(grs_canvas & canvas,const game_mode_flags Game_mode)3743 void render_gauges(grs_canvas &canvas, const game_mode_flags Game_mode)
3744 {
3745 auto &Objects = LevelUniqueObjectState.Objects;
3746 auto &vmobjptr = Objects.vmptr;
3747 auto &plrobj = get_local_plrobj();
3748 auto &player_info = plrobj.ctype.player_info;
3749 const auto energy = f2ir(player_info.energy);
3750 auto &pl_flags = player_info.powerup_flags;
3751 const auto cloak = (pl_flags & PLAYER_FLAGS_CLOAKED);
3752
3753 Assert(PlayerCfg.CockpitMode[1]==CM_FULL_COCKPIT || PlayerCfg.CockpitMode[1]==CM_STATUS_BAR);
3754
3755 auto shields = f2ir(plrobj.shields);
3756 if (shields < 0 ) shields = 0;
3757
3758 gr_set_curfont(canvas, *GAME_FONT);
3759
3760 if (Newdemo_state == ND_STATE_RECORDING)
3761 {
3762 const auto homing_object_dist = player_info.homing_object_dist;
3763 if (homing_object_dist >= 0)
3764 newdemo_record_homing_distance(homing_object_dist);
3765 }
3766
3767 const local_multires_gauge_graphic multires_gauge_graphic{};
3768 const hud_draw_context_hs_mr hudctx(canvas, grd_curscreen->get_screen_width(), grd_curscreen->get_screen_height(), multires_gauge_graphic);
3769 draw_weapon_boxes(hudctx, player_info);
3770 if (PlayerCfg.CockpitMode[1] == CM_FULL_COCKPIT) {
3771 if (Newdemo_state == ND_STATE_RECORDING)
3772 newdemo_record_player_energy(energy);
3773 draw_energy_bar(*grd_curcanv, hudctx, energy);
3774 gr_set_default_canvas();
3775 #if defined(DXX_BUILD_DESCENT_I)
3776 if (PlayerCfg.HudMode == HudType::Standard)
3777 #elif defined(DXX_BUILD_DESCENT_II)
3778 if (Newdemo_state==ND_STATE_RECORDING )
3779 newdemo_record_player_afterburner(Afterburner_charge);
3780 draw_afterburner_bar(hudctx, Afterburner_charge);
3781 #endif
3782 show_bomb_count(hudctx.canvas, player_info, hudctx.xscale(BOMB_COUNT_X), hudctx.yscale(BOMB_COUNT_Y), gr_find_closest_color(0, 0, 0), 0, 0);
3783 draw_player_ship(hudctx, player_info, cloak, SHIP_GAUGE_X, SHIP_GAUGE_Y);
3784
3785 if (player_info.powerup_flags & PLAYER_FLAGS_INVULNERABLE)
3786 draw_invulnerable_ship(hudctx, plrobj);
3787 else
3788 draw_shield_bar(hudctx, shields);
3789 draw_numerical_display(hudctx, shields, energy);
3790
3791 if (Newdemo_state==ND_STATE_RECORDING)
3792 {
3793 newdemo_record_player_shields(shields);
3794 newdemo_record_player_flags(player_info.powerup_flags.get_player_flags());
3795 }
3796 draw_keys_state(hudctx, player_info.powerup_flags).draw_all_cockpit_keys();
3797
3798 show_homing_warning(hudctx, player_info.homing_object_dist);
3799 draw_wbu_overlay(hudctx);
3800
3801 } else if (PlayerCfg.CockpitMode[1] == CM_STATUS_BAR) {
3802
3803 if (Newdemo_state == ND_STATE_RECORDING)
3804 newdemo_record_player_energy(energy);
3805 sb_draw_energy_bar(hudctx, energy);
3806 #if defined(DXX_BUILD_DESCENT_I)
3807 if (PlayerCfg.HudMode == HudType::Standard)
3808 #elif defined(DXX_BUILD_DESCENT_II)
3809 if (Newdemo_state==ND_STATE_RECORDING )
3810 newdemo_record_player_afterburner(Afterburner_charge);
3811 sb_draw_afterburner(hudctx, player_info);
3812 if (PlayerCfg.HudMode == HudType::Standard && inset_window[gauge_inset_window_view::secondary].user == weapon_box_user::weapon)
3813 #endif
3814 show_bomb_count(hudctx.canvas, player_info, hudctx.xscale(SB_BOMB_COUNT_X), hudctx.yscale(SB_BOMB_COUNT_Y), gr_find_closest_color(0, 0, 0), 0, 0);
3815
3816 draw_player_ship(hudctx, player_info, cloak, SB_SHIP_GAUGE_X, SB_SHIP_GAUGE_Y);
3817
3818 if (player_info.powerup_flags & PLAYER_FLAGS_INVULNERABLE)
3819 draw_invulnerable_ship(hudctx, plrobj);
3820 else
3821 sb_draw_shield_bar(hudctx, shields);
3822 sb_draw_shield_num(hudctx, shields);
3823
3824 if (Newdemo_state==ND_STATE_RECORDING)
3825 {
3826 newdemo_record_player_shields(shields);
3827 newdemo_record_player_flags(player_info.powerup_flags.get_player_flags());
3828 }
3829 draw_keys_state(hudctx, player_info.powerup_flags).draw_all_statusbar_keys();
3830
3831 sb_show_lives(hudctx, HUD_SCALE_AR(grd_curscreen->get_screen_width(), grd_curscreen->get_screen_height(), hudctx.multires_gauge_graphic), player_info, Game_mode & GM_MULTI);
3832 const auto is_multiplayer_non_cooperative = (Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP);
3833 sb_show_score(hudctx, player_info, is_multiplayer_non_cooperative);
3834
3835 if (!is_multiplayer_non_cooperative)
3836 {
3837 sb_show_score_added(hudctx);
3838 }
3839 }
3840 #if defined(DXX_BUILD_DESCENT_I)
3841 else
3842 draw_player_ship(hudctx, player_info, cloak, SB_SHIP_GAUGE_X, SB_SHIP_GAUGE_Y);
3843 #endif
3844 }
3845
3846 // ---------------------------------------------------------------------------------------------------------
3847 // Call when picked up a quad laser powerup.
3848 // If laser is active, set old_weapon[0] to -1 to force redraw.
update_laser_weapon_info(void)3849 void update_laser_weapon_info(void)
3850 {
3851 auto &old_weapon = inset_window[gauge_inset_window_view::primary].old_weapon;
3852 if (old_weapon.primary == primary_weapon_index_t::LASER_INDEX)
3853 old_weapon = {};
3854 }
3855
3856 #if defined(DXX_BUILD_DESCENT_II)
3857
3858 //draws a 3d view into one of the cockpit windows. win is 0 for left,
3859 //1 for right. viewer is object. NULL object means give up window
3860 //user is one of the WBU_ constants. If rear_view_flag is set, show a
3861 //rear view. If label is non-NULL, print the label at the top of the
3862 //window.
do_cockpit_window_view(const gauge_inset_window_view win,const weapon_box_user user)3863 void do_cockpit_window_view(const gauge_inset_window_view win, const weapon_box_user user)
3864 {
3865 assert(user == weapon_box_user::weapon || user == weapon_box_user::post_missile_static);
3866 auto &inset = inset_window[win];
3867 auto &inset_user = inset.user;
3868 if (user == weapon_box_user::post_missile_static && inset_user != weapon_box_user::post_missile_static)
3869 inset_window[win].time_static_played = 0;
3870 if (inset_user == weapon_box_user::weapon || inset_user == weapon_box_user::post_missile_static)
3871 return; //already set
3872 inset_user = user;
3873 if (inset.overlap_dirty) {
3874 inset.overlap_dirty = 0;
3875 gr_set_default_canvas();
3876 }
3877 }
3878
do_cockpit_window_view(grs_canvas & canvas,const gauge_inset_window_view win,const object & viewer,const int rear_view_flag,const weapon_box_user user,const char * const label,const player_info * const player_info)3879 void do_cockpit_window_view(grs_canvas &canvas, const gauge_inset_window_view win, const object &viewer, const int rear_view_flag, const weapon_box_user user, const char *const label, const player_info *const player_info)
3880 {
3881 grs_subcanvas window_canv;
3882 const auto viewer_save = Viewer;
3883 static int window_x,window_y;
3884 int rear_view_save = Rear_view;
3885
3886 window_rendered_data window;
3887
3888 inset_window[win].user = user; //say who's using window
3889
3890 Viewer = &viewer;
3891 Rear_view = rear_view_flag;
3892
3893 const local_multires_gauge_graphic multires_gauge_graphic{};
3894 const hud_draw_context_hs_mr hudctx(window_canv, grd_curscreen->get_screen_width(), grd_curscreen->get_screen_height(), multires_gauge_graphic);
3895 if (PlayerCfg.CockpitMode[1] == CM_FULL_SCREEN)
3896 {
3897 const unsigned w = HUD_SCALE_AR(hudctx.xscale, hudctx.yscale)(multires_gauge_graphic.get(106, 44));
3898 const unsigned h = w;
3899
3900 const int dx = (win == gauge_inset_window_view::primary) ? -(w + (w / 10)) : (w / 10);
3901
3902 window_x = grd_curscreen->get_screen_width() / 2 + dx;
3903 window_y = grd_curscreen->get_screen_height() - h - (SHEIGHT / 15);
3904
3905 gr_init_sub_canvas(window_canv, canvas, window_x, window_y, w, h);
3906 }
3907 else {
3908 if (PlayerCfg.CockpitMode[1] != CM_FULL_COCKPIT && PlayerCfg.CockpitMode[1] != CM_STATUS_BAR)
3909 goto abort;
3910
3911 auto &resbox = gauge_boxes[multires_gauge_graphic.hiresmode];
3912 auto &weaponbox = resbox[win];
3913 const auto box = &weaponbox[(PlayerCfg.CockpitMode[1] == CM_STATUS_BAR) ? gauge_hud_type::statusbar : gauge_hud_type::cockpit];
3914 gr_init_sub_canvas(window_canv, canvas, hudctx.xscale(box->left), hudctx.yscale(box->top), hudctx.xscale(box->right - box->left + 1), hudctx.yscale(box->bot - box->top + 1));
3915 }
3916
3917 gr_set_current_canvas(window_canv);
3918
3919 render_frame(window_canv, 0, window);
3920
3921 // HACK! If guided missile, wake up robots as necessary.
3922 if (viewer.type == OBJ_WEAPON) {
3923 // -- Used to require to be GUIDED -- if (viewer->id == GUIDEDMISS_ID)
3924 wake_up_rendered_objects(viewer, window);
3925 }
3926
3927 if (label) {
3928 if (Color_0_31_0 == -1)
3929 Color_0_31_0 = BM_XRGB(0,31,0);
3930 gr_set_fontcolor(window_canv, Color_0_31_0, -1);
3931 auto &game_font = *GAME_FONT;
3932 gr_string(window_canv, game_font, 0x8000, FSPACY(1), label);
3933 }
3934
3935 if (player_info) // only non-nullptr for weapon_box_user::guided
3936 show_reticle(window_canv, *player_info, RET_TYPE_CROSS_V1, 0);
3937
3938 if (PlayerCfg.CockpitMode[1] == CM_FULL_SCREEN) {
3939 int small_window_bottom,big_window_bottom,extra_part_h;
3940
3941 gr_ubox(window_canv, 0, 0, window_canv.cv_bitmap.bm_w, window_canv.cv_bitmap.bm_h, BM_XRGB(0,0,32));
3942
3943 //if the window only partially overlaps the big 3d window, copy
3944 //the extra part to the visible screen
3945
3946 big_window_bottom = SHEIGHT - 1;
3947
3948 if (window_y > big_window_bottom) {
3949
3950 //the small window is completely outside the big 3d window, so
3951 //copy it to the visible screen
3952 gr_bitmap(canvas, window_x, window_y, window_canv.cv_bitmap);
3953 inset_window[win].overlap_dirty = 1;
3954 }
3955 else {
3956
3957 small_window_bottom = window_y + window_canv.cv_bitmap.bm_h - 1;
3958
3959 extra_part_h = small_window_bottom - big_window_bottom;
3960
3961 if (extra_part_h > 0) {
3962 grs_subcanvas overlap_canv;
3963 gr_init_sub_canvas(overlap_canv, window_canv, 0, window_canv.cv_bitmap.bm_h-extra_part_h, window_canv.cv_bitmap.bm_w, extra_part_h);
3964 gr_bitmap(canvas, window_x, big_window_bottom + 1, overlap_canv.cv_bitmap);
3965 inset_window[win].overlap_dirty = 1;
3966 }
3967 }
3968 }
3969 else if (PlayerCfg.CockpitMode[1] == CM_FULL_COCKPIT)
3970 /* `draw_wbu_overlay` has hard-coded x/y coordinates with their
3971 * origin at the root of the screen canvas, not the window
3972 * canvas. Pass the screen canvas so that the coordinates are
3973 * interpreted properly.
3974 */
3975 draw_wbu_overlay(hud_draw_context_hs_mr(canvas, grd_curscreen->get_screen_width(), grd_curscreen->get_screen_height(), multires_gauge_graphic));
3976
3977 //force redraw when done
3978 inset_window[win].old_weapon = {};
3979
3980 abort:;
3981
3982 Viewer = viewer_save;
3983
3984 Rear_view = rear_view_save;
3985 /* grd_curcanv may point to `window_canv`; if so, grd_curcanv
3986 * would become a dangling pointer when `window_canv` goes out of
3987 * scope at the end of the block. Redirect it to the default screen
3988 * to avoid pointing to freed memory. Setting grd_curcanv to
3989 * nullptr would be better, but some code assumes that grd_curcanv
3990 * is never nullptr, so instead set it to the default canvas.
3991 * Eventually, grd_curcanv will be removed entirely.
3992 */
3993 gr_set_current_canvas(canvas);
3994 }
3995 #endif
3996 }
3997