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