/*
 * Portions of this file are copyright Rebirth contributors and licensed as
 * described in COPYING.txt.
 * Portions of this file are copyright Parallax Software and licensed
 * according to the Parallax license below.
 * See COPYING.txt for license details.

THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
*/

/*
 *
 * Inferno gauge drivers
 *
 */

#include <algorithm>
#include <cmath>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ranges>

#include "hudmsg.h"
#include "inferno.h"
#include "game.h"
#include "gauges.h"
#include "physics.h"
#include "dxxerror.h"
#include "object.h"
#include "newdemo.h"
#include "player.h"
#include "gamefont.h"
#include "bm.h"
#include "text.h"
#include "powerup.h"
#include "sounds.h"
#include "multi.h"
#include "endlevel.h"
#include "controls.h"
#include "text.h"
#include "render.h"
#include "piggy.h"
#include "laser.h"
#include "weapon.h"
#include "common/3d/globvars.h"
#include "playsave.h"
#include "rle.h"
#if DXX_USE_OGL
#include "ogl_init.h"
#endif
#include "args.h"
#include "vclip.h"
#include "compiler-range_for.h"
#include "d_levelstate.h"
#include <utility>

using std::min;

namespace {

enum class gauge_screen_resolution : uint8_t
{
	low,
	high,
};

class local_multires_gauge_graphic
{
public:
	const gauge_screen_resolution hiresmode = gauge_screen_resolution{HIRESMODE};
	constexpr bool is_hires() const
	{
		if (hiresmode != gauge_screen_resolution::low) [[likely]]
			return true;
		return false;
	}
	template <typename T>
		constexpr T get(T h, T l) const
	{
		return is_hires() ? h : l;
	}
};

static bool show_cloak_invul_timer()
{
	return PlayerCfg.CloakInvulTimer && Newdemo_state != ND_STATE_PLAYBACK;
}

static void draw_ammo_info(grs_canvas &, unsigned x, unsigned y, unsigned ammo_count);

union weapon_index
{
	primary_weapon_index primary;
	secondary_weapon_index secondary;
	constexpr weapon_index() :
		primary(static_cast<primary_weapon_index>(~0u))
	{
	}
	constexpr weapon_index(const primary_weapon_index p) :
		primary(p)
	{
	}
	constexpr weapon_index(const secondary_weapon_index s) :
		secondary(s)
	{
	}
	constexpr bool operator==(const weapon_index w) const
	{
		return primary == w.primary;
	}
};

static constexpr const char *PRIMARY_WEAPON_NAMES_SHORT(const primary_weapon_index weapon_index)
{
	switch (weapon_index)
	{
		default:	// unreachable
		case primary_weapon_index::laser:	// reachable
			return TXT_W_LASER_S;
		case primary_weapon_index::vulcan:
			return TXT_W_VULCAN_S;
		case primary_weapon_index::spreadfire:
			return TXT_W_SPREADFIRE_S;
		case primary_weapon_index::plasma:
			return TXT_W_PLASMA_S;
		case primary_weapon_index::fusion:
			return TXT_W_FUSION_S;
#if DXX_BUILD_DESCENT == 2
		case primary_weapon_index::super_laser:
			return TXT_W_SLASER_S;
		case primary_weapon_index::gauss:
			return TXT_W_SVULCAN_S;
		case primary_weapon_index::helix:
			return TXT_W_SSPREADFIRE_S;
		case primary_weapon_index::phoenix:
			return TXT_W_SPLASMA_S;
		case primary_weapon_index::omega:
			return TXT_W_SFUSION_S;
#endif
	}
}

}

//bitmap numbers for gauges
#define GAUGE_SHIELDS			0		//0..9, in decreasing order (100%,90%...0%)
#define GAUGE_INVULNERABLE		10		//10..19
#define N_INVULNERABLE_FRAMES		10
#define GAUGE_ENERGY_LEFT		21
#define GAUGE_ENERGY_RIGHT		22
#define GAUGE_NUMERICAL			23
#define GAUGE_BLUE_KEY			24
#define GAUGE_GOLD_KEY			25
#define GAUGE_RED_KEY			26
#define GAUGE_BLUE_KEY_OFF		27
#define GAUGE_GOLD_KEY_OFF		28
#define GAUGE_RED_KEY_OFF		29
#define SB_GAUGE_BLUE_KEY		30
#define SB_GAUGE_GOLD_KEY		31
#define SB_GAUGE_RED_KEY		32
#define SB_GAUGE_BLUE_KEY_OFF		33
#define SB_GAUGE_GOLD_KEY_OFF		34
#define SB_GAUGE_RED_KEY_OFF		35
#define SB_GAUGE_ENERGY			36
#define GAUGE_LIVES			37
#define GAUGE_SHIPS			38
#define RETICLE_CROSS			46
#define RETICLE_PRIMARY			48
#define RETICLE_SECONDARY		51
#define GAUGE_HOMING_WARNING_ON	56
#define GAUGE_HOMING_WARNING_OFF	57
#define KEY_ICON_BLUE			68
#define KEY_ICON_YELLOW			69
#define KEY_ICON_RED			70

//Coordinats for gauges
#if DXX_BUILD_DESCENT == 1
#define GAUGE_BLUE_KEY_X_L		45
#define GAUGE_BLUE_KEY_X_H		91
#define GAUGE_GOLD_KEY_X_L		44
#define GAUGE_GOLD_KEY_X_H		89
#define GAUGE_RED_KEY_X_L		43
#define GAUGE_RED_KEY_X_H		87
#define GAUGE_RED_KEY_Y_H		417
#define LEFT_ENERGY_GAUGE_X_H 	137
#define RIGHT_ENERGY_GAUGE_X 	((multires_gauge_graphic.get(380, 190)))
#elif DXX_BUILD_DESCENT == 2
#define GAUGE_AFTERBURNER		20
#define SB_GAUGE_AFTERBURNER		71
#define FLAG_ICON_RED			72
#define FLAG_ICON_BLUE			73
#define GAUGE_BLUE_KEY_X_L		272
#define GAUGE_BLUE_KEY_X_H		535
#define GAUGE_GOLD_KEY_X_L		273
#define GAUGE_GOLD_KEY_X_H		537
#define GAUGE_RED_KEY_X_L		274
#define GAUGE_RED_KEY_X_H		539
#define GAUGE_RED_KEY_Y_H		416
#define LEFT_ENERGY_GAUGE_X_H 	138
#define RIGHT_ENERGY_GAUGE_X 	((multires_gauge_graphic.get(379, 190)))
#endif

#define GAUGE_BLUE_KEY_Y_L		152
#define GAUGE_BLUE_KEY_Y_H		374
#define GAUGE_BLUE_KEY_X		((multires_gauge_graphic.get(GAUGE_BLUE_KEY_X_H, GAUGE_BLUE_KEY_X_L)))
#define GAUGE_BLUE_KEY_Y		((multires_gauge_graphic.get(GAUGE_BLUE_KEY_Y_H, GAUGE_BLUE_KEY_Y_L)))
#define GAUGE_GOLD_KEY_Y_L		162
#define GAUGE_GOLD_KEY_Y_H		395
#define GAUGE_GOLD_KEY_X		((multires_gauge_graphic.get(GAUGE_GOLD_KEY_X_H, GAUGE_GOLD_KEY_X_L)))
#define GAUGE_GOLD_KEY_Y		((multires_gauge_graphic.get(GAUGE_GOLD_KEY_Y_H, GAUGE_GOLD_KEY_Y_L)))
#define GAUGE_RED_KEY_Y_L		172
#define GAUGE_RED_KEY_X			((multires_gauge_graphic.get(GAUGE_RED_KEY_X_H, GAUGE_RED_KEY_X_L)))
#define GAUGE_RED_KEY_Y			((multires_gauge_graphic.get(GAUGE_RED_KEY_Y_H, GAUGE_RED_KEY_Y_L)))
#define SB_GAUGE_KEYS_X_L		11
#define SB_GAUGE_KEYS_X_H		26
#define SB_GAUGE_KEYS_X			((multires_gauge_graphic.get(SB_GAUGE_KEYS_X_H, SB_GAUGE_KEYS_X_L)))
#define SB_GAUGE_BLUE_KEY_Y_L		153
#define SB_GAUGE_GOLD_KEY_Y_L		169
#define SB_GAUGE_RED_KEY_Y_L		185
#define SB_GAUGE_BLUE_KEY_Y_H		390
#define SB_GAUGE_GOLD_KEY_Y_H		422
#define SB_GAUGE_RED_KEY_Y_H		454
#define SB_GAUGE_BLUE_KEY_Y		((multires_gauge_graphic.get(SB_GAUGE_BLUE_KEY_Y_H, SB_GAUGE_BLUE_KEY_Y_L)))
#define SB_GAUGE_GOLD_KEY_Y		((multires_gauge_graphic.get(SB_GAUGE_GOLD_KEY_Y_H, SB_GAUGE_GOLD_KEY_Y_L)))
#define SB_GAUGE_RED_KEY_Y		((multires_gauge_graphic.get(SB_GAUGE_RED_KEY_Y_H, SB_GAUGE_RED_KEY_Y_L)))
#define LEFT_ENERGY_GAUGE_X_L 	70
#define LEFT_ENERGY_GAUGE_Y_L 	131
#define LEFT_ENERGY_GAUGE_W_L 	64
#define LEFT_ENERGY_GAUGE_H_L 	8
#define LEFT_ENERGY_GAUGE_Y_H 	314
#define LEFT_ENERGY_GAUGE_W_H 	133
#define LEFT_ENERGY_GAUGE_H_H 	21
#define LEFT_ENERGY_GAUGE_X 	((multires_gauge_graphic.get(LEFT_ENERGY_GAUGE_X_H, LEFT_ENERGY_GAUGE_X_L)))
#define LEFT_ENERGY_GAUGE_Y 	((multires_gauge_graphic.get(LEFT_ENERGY_GAUGE_Y_H, LEFT_ENERGY_GAUGE_Y_L)))
#define LEFT_ENERGY_GAUGE_W 	((multires_gauge_graphic.get(LEFT_ENERGY_GAUGE_W_H, LEFT_ENERGY_GAUGE_W_L)))
#define LEFT_ENERGY_GAUGE_H 	((multires_gauge_graphic.get(LEFT_ENERGY_GAUGE_H_H, LEFT_ENERGY_GAUGE_H_L)))
#define RIGHT_ENERGY_GAUGE_Y 	((multires_gauge_graphic.get(314, 131)))
#define RIGHT_ENERGY_GAUGE_W 	((multires_gauge_graphic.get(133, 64)))
#define RIGHT_ENERGY_GAUGE_H 	((multires_gauge_graphic.get(21, 8)))

#if DXX_BUILD_DESCENT == 1
#define SB_ENERGY_GAUGE_Y 		((multires_gauge_graphic.get(390, 155)))
#define SB_ENERGY_GAUGE_H 		((multires_gauge_graphic.get(82, 41)))
#define SB_ENERGY_NUM_Y 		((multires_gauge_graphic.get(457, 190)))
#define SHIELD_GAUGE_Y			((multires_gauge_graphic.get(374, 155)))
#define SB_SHIELD_NUM_Y 		(SB_SHIELD_GAUGE_Y-((multires_gauge_graphic.get(16, 7))))			//156 -- MWA used to be hard coded to 156
#define NUMERICAL_GAUGE_Y		((multires_gauge_graphic.get(316, 130)))
#define PRIMARY_W_PIC_X			((multires_gauge_graphic.get(135, 64)))
#define SECONDARY_W_PIC_X		((multires_gauge_graphic.get(405, 234)))
#define SECONDARY_W_PIC_Y		((multires_gauge_graphic.get(370, 154)))
#define SECONDARY_W_TEXT_X		multires_gauge_graphic.get(462, 207)
#define SECONDARY_W_TEXT_Y		multires_gauge_graphic.get(400, 157)
#define SECONDARY_AMMO_X		multires_gauge_graphic.get(475, 213)
#define SECONDARY_AMMO_Y		multires_gauge_graphic.get(425, 171)
#define SB_LIVES_X			((multires_gauge_graphic.get(550, 266)))
#define SB_LIVES_Y			((multires_gauge_graphic.get(450, 185)))
#define SB_SCORE_RIGHT_H		605
#define BOMB_COUNT_X			((multires_gauge_graphic.get(468, 210)))
#define PRIMARY_W_BOX_RIGHT_H		241
#define SECONDARY_W_BOX_RIGHT_L		264	//(SECONDARY_W_BOX_LEFT+54)
#define SECONDARY_W_BOX_LEFT_H		403
#define SECONDARY_W_BOX_TOP_H		364
#define SECONDARY_W_BOX_RIGHT_H		531
#define SB_PRIMARY_W_BOX_TOP_L		154
#define SB_PRIMARY_W_BOX_BOT_L		(195)
#define SB_SECONDARY_W_BOX_TOP_L	154
#define SB_SECONDARY_W_BOX_RIGHT_L	(SB_SECONDARY_W_BOX_LEFT_L+54)
#define SB_SECONDARY_W_BOX_BOT_L	(153+42)
#define SB_SECONDARY_AMMO_X		(SB_SECONDARY_W_BOX_LEFT + (multires_gauge_graphic.get(14, 11)))	//(212+9)
#define GET_GAUGE_INDEX(x)		(Gauges[x])

#elif DXX_BUILD_DESCENT == 2
#define AFTERBURNER_GAUGE_X_L	45-1
#define AFTERBURNER_GAUGE_Y_L	158
#define AFTERBURNER_GAUGE_H_L	32
#define AFTERBURNER_GAUGE_X_H	88
#define AFTERBURNER_GAUGE_Y_H	378
#define AFTERBURNER_GAUGE_H_H	65
#define AFTERBURNER_GAUGE_X	((multires_gauge_graphic.get(AFTERBURNER_GAUGE_X_H, AFTERBURNER_GAUGE_X_L)))
#define AFTERBURNER_GAUGE_Y	((multires_gauge_graphic.get(AFTERBURNER_GAUGE_Y_H, AFTERBURNER_GAUGE_Y_L)))
#define AFTERBURNER_GAUGE_H	((multires_gauge_graphic.get(AFTERBURNER_GAUGE_H_H, AFTERBURNER_GAUGE_H_L)))
#define SB_AFTERBURNER_GAUGE_X 		((multires_gauge_graphic.get(196, 98)))
#define SB_AFTERBURNER_GAUGE_Y 		((multires_gauge_graphic.get(445, 184)))
#define SB_AFTERBURNER_GAUGE_W 		((multires_gauge_graphic.get(33, 16)))
#define SB_AFTERBURNER_GAUGE_H 		((multires_gauge_graphic.get(29, 13)))
#define SB_ENERGY_GAUGE_Y 		((multires_gauge_graphic.get(381, 155-2)))
#define SB_ENERGY_GAUGE_H 		((multires_gauge_graphic.get(60, 29)))
#define SB_ENERGY_NUM_Y 		((multires_gauge_graphic.get(457, 175)))
#define SHIELD_GAUGE_Y			((multires_gauge_graphic.get(375, 155)))
#define SB_SHIELD_NUM_Y 		(SB_SHIELD_GAUGE_Y-((multires_gauge_graphic.get(16, 8))))			//156 -- MWA used to be hard coded to 156
#define NUMERICAL_GAUGE_Y		((multires_gauge_graphic.get(314, 130)))
#define PRIMARY_W_PIC_X			((multires_gauge_graphic.get(135-10, 64)))
#define SECONDARY_W_PIC_X		((multires_gauge_graphic.get(466, 234)))
#define SECONDARY_W_PIC_Y		((multires_gauge_graphic.get(374, 154)))
#define SECONDARY_W_TEXT_X		multires_gauge_graphic.get(413, 207)
#define SECONDARY_W_TEXT_Y		multires_gauge_graphic.get(378, 157)
#define SECONDARY_AMMO_X		multires_gauge_graphic.get(428, 213)
#define SECONDARY_AMMO_Y		multires_gauge_graphic.get(407, 171)
#define SB_LIVES_X			((multires_gauge_graphic.get(550-10-3, 266)))
#define SB_LIVES_Y			((multires_gauge_graphic.get(450-3, 185)))
#define SB_SCORE_RIGHT_H		(605+8)
#define BOMB_COUNT_X			((multires_gauge_graphic.get(546, 275)))
#define PRIMARY_W_BOX_RIGHT_H		242
#define SECONDARY_W_BOX_RIGHT_L		263	//(SECONDARY_W_BOX_LEFT+54)
#define SECONDARY_W_BOX_LEFT_H		404
#define SECONDARY_W_BOX_TOP_H		363
#define SECONDARY_W_BOX_RIGHT_H		529
#define SB_PRIMARY_W_BOX_TOP_L		153
#define SB_PRIMARY_W_BOX_BOT_L		(195+1)
#define SB_SECONDARY_W_BOX_TOP_L	153
#define SB_SECONDARY_W_BOX_RIGHT_L	(SB_SECONDARY_W_BOX_LEFT_L+54+1)
#define SB_SECONDARY_W_BOX_BOT_L	(SB_SECONDARY_W_BOX_TOP_L+43)
#define SB_SECONDARY_AMMO_X		(SB_SECONDARY_W_BOX_LEFT + (multires_gauge_graphic.get(14 - 4, 11)))	//(212+9)
#define GET_GAUGE_INDEX(x)		(multires_gauge_graphic.get<const decltype(Gauges) &>(Gauges_hires, Gauges)[x])

#endif

// MOVEME
#define HOMING_WARNING_X		((multires_gauge_graphic.get(13, 7)))
#define HOMING_WARNING_Y		((multires_gauge_graphic.get(416, 171)))

#define SB_ENERGY_GAUGE_X 		((multires_gauge_graphic.get(196, 98)))
#define SB_ENERGY_GAUGE_W 		((multires_gauge_graphic.get(32, 16)))
#define SHIP_GAUGE_X 			(SHIELD_GAUGE_X+((multires_gauge_graphic.get(11, 5))))
#define SHIP_GAUGE_Y			(SHIELD_GAUGE_Y+((multires_gauge_graphic.get(10, 5))))
#define SHIELD_GAUGE_X 			((multires_gauge_graphic.get(292, 146)))
#define SB_SHIELD_GAUGE_X 		((multires_gauge_graphic.get(247, 123)))		//139
#define SB_SHIELD_GAUGE_Y 		((multires_gauge_graphic.get(395, 163)))
#define SB_SHIP_GAUGE_X 		(SB_SHIELD_GAUGE_X+((multires_gauge_graphic.get(11, 5))))
#define SB_SHIP_GAUGE_Y 		(SB_SHIELD_GAUGE_Y+((multires_gauge_graphic.get(10, 5))))
#define SB_SHIELD_NUM_X 		(SB_SHIELD_GAUGE_X+((multires_gauge_graphic.get(21, 12))))	//151
#define NUMERICAL_GAUGE_X		((multires_gauge_graphic.get(308, 154)))
#define PRIMARY_W_PIC_Y			((multires_gauge_graphic.get(370, 154)))
#define PRIMARY_W_TEXT_X		multires_gauge_graphic.get(182, 87)
#define PRIMARY_W_TEXT_Y		multires_gauge_graphic.get(378, 157)
#define PRIMARY_AMMO_X			multires_gauge_graphic.get(186, 93)
#define PRIMARY_AMMO_Y			multires_gauge_graphic.get(407, 171)
#define SB_LIVES_LABEL_X		((multires_gauge_graphic.get(475, 237)))
#define SB_SCORE_RIGHT_L		301
#define SB_SCORE_RIGHT			((multires_gauge_graphic.get(SB_SCORE_RIGHT_H, SB_SCORE_RIGHT_L)))
#define SB_SCORE_Y			((multires_gauge_graphic.get(398, 158)))
#define SB_SCORE_LABEL_X		((multires_gauge_graphic.get(475, 237)))
#define SB_SCORE_ADDED_RIGHT		((multires_gauge_graphic.get(SB_SCORE_RIGHT_H, SB_SCORE_RIGHT_L)))
#define SB_SCORE_ADDED_Y		((multires_gauge_graphic.get(413, 165)))
#define BOMB_COUNT_Y			((multires_gauge_graphic.get(445, 186)))
#define SB_BOMB_COUNT_X			((multires_gauge_graphic.get(342, 171)))
#define SB_BOMB_COUNT_Y			((multires_gauge_graphic.get(458, 191)))

// defining box boundries for weapon pictures
#define PRIMARY_W_BOX_LEFT_L		63
#define PRIMARY_W_BOX_TOP_L		151		//154
#define PRIMARY_W_BOX_RIGHT_L		(PRIMARY_W_BOX_LEFT_L+58)
#define PRIMARY_W_BOX_BOT_L		(PRIMARY_W_BOX_TOP_L+42)
#define PRIMARY_W_BOX_LEFT_H		121
#define PRIMARY_W_BOX_TOP_H		364
#define PRIMARY_W_BOX_BOT_H		(PRIMARY_W_BOX_TOP_H+106)		//470
#define PRIMARY_W_BOX_LEFT		((multires_gauge_graphic.get(PRIMARY_W_BOX_LEFT_H, PRIMARY_W_BOX_LEFT_L)))
#define PRIMARY_W_BOX_TOP		((multires_gauge_graphic.get(PRIMARY_W_BOX_TOP_H, PRIMARY_W_BOX_TOP_L)))
#define PRIMARY_W_BOX_RIGHT		((multires_gauge_graphic.get(PRIMARY_W_BOX_RIGHT_H, PRIMARY_W_BOX_RIGHT_L)))
#define PRIMARY_W_BOX_BOT		((multires_gauge_graphic.get(PRIMARY_W_BOX_BOT_H, PRIMARY_W_BOX_BOT_L)))
#define SECONDARY_W_BOX_LEFT_L		202	//207
#define SECONDARY_W_BOX_TOP_L		151
#define SECONDARY_W_BOX_BOT_L		(SECONDARY_W_BOX_TOP_L+42)
#define SECONDARY_W_BOX_BOT_H		(SECONDARY_W_BOX_TOP_H+106)		//470
#define SECONDARY_W_BOX_LEFT		((multires_gauge_graphic.get(SECONDARY_W_BOX_LEFT_H, SECONDARY_W_BOX_LEFT_L)))
#define SECONDARY_W_BOX_TOP		((multires_gauge_graphic.get(SECONDARY_W_BOX_TOP_H, SECONDARY_W_BOX_TOP_L)))
#define SECONDARY_W_BOX_RIGHT		((multires_gauge_graphic.get(SECONDARY_W_BOX_RIGHT_H, SECONDARY_W_BOX_RIGHT_L)))
#define SECONDARY_W_BOX_BOT		((multires_gauge_graphic.get(SECONDARY_W_BOX_BOT_H, SECONDARY_W_BOX_BOT_L)))
#define SB_PRIMARY_W_BOX_LEFT_L		34		//50
#define SB_PRIMARY_W_BOX_RIGHT_L	(SB_PRIMARY_W_BOX_LEFT_L+55)
#define SB_PRIMARY_W_BOX_LEFT_H		68
#define SB_PRIMARY_W_BOX_TOP_H		381
#define SB_PRIMARY_W_BOX_RIGHT_H	179
#define SB_PRIMARY_W_BOX_BOT_H		473
#define SB_PRIMARY_W_BOX_LEFT		((multires_gauge_graphic.get(SB_PRIMARY_W_BOX_LEFT_H, SB_PRIMARY_W_BOX_LEFT_L)))
#define SB_SECONDARY_W_BOX_LEFT_L	169
#define SB_SECONDARY_W_BOX_LEFT_H	338
#define SB_SECONDARY_W_BOX_TOP_H	381
#define SB_SECONDARY_W_BOX_RIGHT_H	449
#define SB_SECONDARY_W_BOX_BOT_H	473
#define SB_SECONDARY_W_BOX_LEFT		((multires_gauge_graphic.get(SB_SECONDARY_W_BOX_LEFT_H, SB_SECONDARY_W_BOX_LEFT_L)))	//210
#define SB_PRIMARY_W_PIC_X		(SB_PRIMARY_W_BOX_LEFT+1)	//51
#define SB_PRIMARY_W_PIC_Y		((multires_gauge_graphic.get(382, 154)))
#define SB_PRIMARY_W_TEXT_X		(SB_PRIMARY_W_BOX_LEFT + multires_gauge_graphic.get(50, 24))	//(51+23)
#define SB_PRIMARY_W_TEXT_Y		(multires_gauge_graphic.get(390, 157))
#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)
#define SB_PRIMARY_AMMO_Y		multires_gauge_graphic.get(410, 171)
#define SB_SECONDARY_W_PIC_X		((multires_gauge_graphic.get(385, (SB_SECONDARY_W_BOX_LEFT+27))))	//(212+27)
#define SB_SECONDARY_W_PIC_Y		((multires_gauge_graphic.get(382, 154)))
#define SB_SECONDARY_W_TEXT_X		(SB_SECONDARY_W_BOX_LEFT + 2)	//212
#define SB_SECONDARY_W_TEXT_Y		multires_gauge_graphic.get(390, 157)
#define SB_SECONDARY_AMMO_Y		multires_gauge_graphic.get(414, 171)

#define FADE_SCALE			(2*i2f(GR_FADE_LEVELS)/REARM_TIME)		// fade out and back in REARM_TIME, in fade levels per seconds (int)

#define COCKPIT_PRIMARY_BOX		((multires_gauge_graphic.get(4, 0)))
#define COCKPIT_SECONDARY_BOX		((multires_gauge_graphic.get(5, 1)))
#define SB_PRIMARY_BOX			((multires_gauge_graphic.get(6, 2)))
#define SB_SECONDARY_BOX		((multires_gauge_graphic.get(7, 3)))

// scaling gauges
#define BASE_WIDTH(G) ((G).get(640, 320))
#define BASE_HEIGHT(G)	((G).get(480, 200))
namespace {

enum class weapon_box_state : uint8_t
{
	set,		//in correct state
	fading_out,
	fading_in,
};
static_assert(static_cast<unsigned>(weapon_box_state::set) == 0, "weapon_box_states must start at zero");

template <char tag>
class hud_scale_float;

using hud_ar_scale_float = hud_scale_float<'a'>;
using hud_x_scale_float = hud_scale_float<'x'>;
using hud_y_scale_float = hud_scale_float<'y'>;

#if DXX_USE_OGL
class base_hud_scaled_int
{
	const long v;
public:
	explicit constexpr base_hud_scaled_int(const long l) :
		v{l}
	{
	}
	constexpr operator long() const
	{
		return v;
	}
};

template <char>
class hud_scaled_int : public base_hud_scaled_int
{
public:
	using base_hud_scaled_int::base_hud_scaled_int;
};

class base_hud_scale_float
{
protected:
	const double scale;
	constexpr long operator()(const int i) const
	{
		return (this->scale * static_cast<double>(i)) + 0.5;
	}
	constexpr double get() const
	{
		return scale;
	}
public:
	constexpr base_hud_scale_float(const double s) :
		scale{s}
	{
	}
};

template <char tag>
class hud_scale_float : base_hud_scale_float
{
public:
	using base_hud_scale_float::get;
	using base_hud_scale_float::base_hud_scale_float;
	constexpr auto operator()(const int i) const
	{
		return hud_scaled_int<tag>{this->base_hud_scale_float::operator()(i)};
	}
};

static hud_x_scale_float HUD_SCALE_X(const unsigned screen_width, const local_multires_gauge_graphic multires_gauge_graphic)
{
	return static_cast<double>(screen_width) / BASE_WIDTH(multires_gauge_graphic);
}

static hud_y_scale_float HUD_SCALE_Y(const unsigned screen_height, const local_multires_gauge_graphic multires_gauge_graphic)
{
	return static_cast<double>(screen_height) / BASE_HEIGHT(multires_gauge_graphic);
}

static hud_ar_scale_float HUD_SCALE_AR(const hud_x_scale_float x, const hud_y_scale_float y)
{
	return std::min(x.get(), y.get());
}

static hud_ar_scale_float HUD_SCALE_AR(const unsigned screen_width, const unsigned screen_height, const local_multires_gauge_graphic multires_gauge_graphic)
{
	return HUD_SCALE_AR(HUD_SCALE_X(screen_width, multires_gauge_graphic), HUD_SCALE_Y(screen_height, multires_gauge_graphic));
}

#define draw_numerical_display_draw_context	hud_draw_context_hs
#else
#define hud_bitblt_free(canvas,x,y,w,h,bm)	hud_bitblt_free(canvas,x,y,bm)
#define draw_numerical_display_draw_context	hud_draw_context_hs_mr

struct base_hud_scale_float
{
	constexpr long operator()(const unsigned i) const
	{
		return i;
	}
};

/* Use a dummy tag type so that 'x'/'y' cannot be intermixed, and so that the
 * empty 'x' type and empty 'y' type can overlap.
 */
template <char tag>
class hud_scale_float : public base_hud_scale_float
{
};

static hud_ar_scale_float HUD_SCALE_AR(hud_x_scale_float, hud_y_scale_float)
{
	return {};
}

static hud_ar_scale_float HUD_SCALE_AR(unsigned, unsigned, local_multires_gauge_graphic)
{
	return {};
}
#endif

struct CockpitWeaponBoxFrameBitmaps : std::array<grs_subbitmap_ptr, 2> // Overlay subbitmaps for both weapon boxes
{
	/* `decoded_full_cockpit_image` is a member instead of a local in
	 * the initializer function because it must remain allocated.  It
	 * must remain allocated because the subbitmaps refer to the data
	 * managed by `decoded_full_cockpit_image`, so destroying it would
	 * leave the subbitmaps dangling.  For OpenGL, the texture would be
	 * dangling.  For SDL-only, the bm_data pointers would be dangling.
	 */
#if DXX_USE_OGL
	/* Use bare grs_bitmap because the caller has special rules for
	 * managing the memory pointed at by `bm_data`.
	 */
	grs_bitmap decoded_full_cockpit_image;
#else
	/* Use grs_main_bitmap because the bitmap follows the standard
	 * rules.
	 */
	grs_main_bitmap decoded_full_cockpit_image;
#endif
};

static CockpitWeaponBoxFrameBitmaps WinBoxOverlay;

}

namespace dsx {

#if DXX_BUILD_DESCENT == 1
std::array<bitmap_index, MAX_GAUGE_BMS_MAC> Gauges; // Array of all gauge bitmaps.
#elif DXX_BUILD_DESCENT == 2
std::array<bitmap_index, MAX_GAUGE_BMS> Gauges,   // Array of all gauge bitmaps.
	Gauges_hires;   // hires gauges
#endif

namespace {

static inline void PAGE_IN_GAUGE(int x
#if DXX_BUILD_DESCENT == 2
	, const local_multires_gauge_graphic multires_gauge_graphic
#endif
	)
{
	const auto &g{
#if DXX_BUILD_DESCENT == 2
		multires_gauge_graphic.is_hires() ? Gauges_hires :
#endif
		Gauges
	};
	PIGGY_PAGE_IN(g[x]);
}

}

}

namespace {
static int score_display;
static fix score_time;
static laser_level old_laser_level;
static int invulnerable_frame;
}
int	Color_0_31_0 = -1;

namespace dcx {

namespace {

struct hud_draw_context_canvas
{
	grs_canvas &canvas;
	constexpr hud_draw_context_canvas(grs_canvas &c) :
		canvas{c}
	{
	}
};

struct hud_draw_context_multires
{
	const local_multires_gauge_graphic multires_gauge_graphic;
	constexpr hud_draw_context_multires(const local_multires_gauge_graphic mr) :
		multires_gauge_graphic{mr}
	{
	}
};

struct hud_draw_context_mr : hud_draw_context_canvas, hud_draw_context_multires
{
	constexpr hud_draw_context_mr(grs_canvas &c, const local_multires_gauge_graphic mr) :
		hud_draw_context_canvas{c}, hud_draw_context_multires{mr}
	{
	}
};

struct hud_draw_context_xyscale
{
	[[no_unique_address]]
	const hud_x_scale_float xscale;
	[[no_unique_address]]
	const hud_y_scale_float yscale;
#if DXX_USE_OGL
	constexpr hud_draw_context_xyscale(const hud_x_scale_float x, const hud_y_scale_float y) :
		xscale{x}, yscale{y}
	{
	}
#else
	constexpr hud_draw_context_xyscale() :
		xscale{}, yscale{}
	{
	}
#endif
};

struct hud_draw_context_hs_mr : hud_draw_context_mr, hud_draw_context_xyscale
{
#if DXX_USE_OGL
	constexpr hud_draw_context_hs_mr(grs_canvas &c, const unsigned screen_width, const unsigned screen_height, const local_multires_gauge_graphic multires_gauge_graphic) :
		hud_draw_context_mr{c, multires_gauge_graphic},
		hud_draw_context_xyscale{HUD_SCALE_X(screen_width, multires_gauge_graphic), HUD_SCALE_Y(screen_height, multires_gauge_graphic)}
	{
	}
#else
	constexpr hud_draw_context_hs_mr(grs_canvas &c, unsigned, unsigned, const local_multires_gauge_graphic multires_gauge_graphic) :
		hud_draw_context_mr{c, multires_gauge_graphic}
	{
	}
#endif
};

struct hud_draw_context_hs : hud_draw_context_canvas, hud_draw_context_xyscale
{
	constexpr hud_draw_context_hs(const hud_draw_context_hs_mr &hudctx) :
		hud_draw_context_canvas{hudctx.canvas}, hud_draw_context_xyscale{hudctx}
	{
	}
};

struct gauge_box
{
	int left,top;
	int right,bot;		//maximal box
};

enum class gauge_hud_type : uint_fast32_t
{
	cockpit,
	statusbar,
	// fullscreen mode handled separately
};

constexpr enumerated_array<
		per_gauge_inset_window_view_array<
			enumerated_array<gauge_box, 2, gauge_hud_type>
		>,
	2, gauge_screen_resolution> gauge_boxes = {{{
	{{{
	{{{
		{PRIMARY_W_BOX_LEFT_L,PRIMARY_W_BOX_TOP_L,PRIMARY_W_BOX_RIGHT_L,PRIMARY_W_BOX_BOT_L},
		{SB_PRIMARY_W_BOX_LEFT_L,SB_PRIMARY_W_BOX_TOP_L,SB_PRIMARY_W_BOX_RIGHT_L,SB_PRIMARY_W_BOX_BOT_L},
	}}},

	{{{
		{SECONDARY_W_BOX_LEFT_L,SECONDARY_W_BOX_TOP_L,SECONDARY_W_BOX_RIGHT_L,SECONDARY_W_BOX_BOT_L},
		{SB_SECONDARY_W_BOX_LEFT_L,SB_SECONDARY_W_BOX_TOP_L,SB_SECONDARY_W_BOX_RIGHT_L,SB_SECONDARY_W_BOX_BOT_L},
	}}},
	}}},

	{{{
	{{{
		{PRIMARY_W_BOX_LEFT_H,PRIMARY_W_BOX_TOP_H,PRIMARY_W_BOX_RIGHT_H,PRIMARY_W_BOX_BOT_H},
		{SB_PRIMARY_W_BOX_LEFT_H,SB_PRIMARY_W_BOX_TOP_H,SB_PRIMARY_W_BOX_RIGHT_H,SB_PRIMARY_W_BOX_BOT_H},
	}}},

	{{{
		{SECONDARY_W_BOX_LEFT_H,SECONDARY_W_BOX_TOP_H,SECONDARY_W_BOX_RIGHT_H,SECONDARY_W_BOX_BOT_H},
		{SB_SECONDARY_W_BOX_LEFT_H,SB_SECONDARY_W_BOX_TOP_H,SB_SECONDARY_W_BOX_RIGHT_H,SB_SECONDARY_W_BOX_BOT_H},
	}}}
	}}}
}}};

struct d_gauge_span
{
	uint16_t l, r;
};

struct dspan
{
	d_gauge_span l, r;
};

//store delta x values from left of box
constexpr std::array<dspan, 43> weapon_windows_lowres = {{
	{{71,114},		{208,255}},
	{{69,116},		{206,257}},
	{{68,117},		{205,258}},
	{{66,118},		{204,259}},
	{{66,119},		{203,260}},
	{{66,119},		{203,260}},
	{{65,119},		{203,260}},
	{{65,119},		{203,260}},
	{{65,119},		{203,260}},
	{{65,119},		{203,261}},
	{{65,119},		{203,261}},
	{{65,119},		{203,261}},
	{{65,119},		{203,261}},
	{{65,119},		{203,261}},
	{{65,119},		{203,261}},
	{{64,119},		{203,261}},
	{{64,119},		{203,261}},
	{{64,119},		{203,261}},
	{{64,119},		{203,262}},
	{{64,119},		{203,262}},
	{{64,119},		{203,262}},
	{{64,119},		{203,262}},
	{{64,119},		{203,262}},
	{{64,119},		{203,262}},
	{{63,119},		{203,262}},
	{{63,118},		{203,262}},
	{{63,118},		{204,263}},
	{{63,118},		{204,263}},
	{{63,118},		{204,263}},
	{{63,118},		{204,263}},
	{{63,118},		{204,263}},
	{{63,118},		{204,263}},
	{{63,118},		{204,263}},
	{{63,118},		{204,263}},
	{{63,118},		{204,263}},
	{{63,118},		{204,263}},
	{{63,118},		{204,263}},
	{{63,117},		{204,263}},
	{{63,117},		{205,263}},
	{{64,116},		{206,262}},
	{{65,115},		{207,261}},
	{{66,113},		{208,260}},
	{{68,111},		{211,255}},
}};

//store delta x values from left of box
constexpr std::array<dspan, 107> weapon_windows_hires = {{
	{{141,231},		{416,509}},
	{{139,234},		{413,511}},
	{{137,235},		{412,513}},
	{{136,237},		{410,514}},
	{{135,238},		{409,515}},
	{{134,239},		{408,516}},
	{{133,240},		{407,517}},
	{{132,240},		{407,518}},
	{{131,241},		{406,519}},
	{{131,241},		{406,519}},
	{{130,242},		{405,520}},
	{{129,242},		{405,521}},
	{{129,242},		{405,521}},
	{{129,243},		{404,521}},
	{{128,243},		{404,522}},
	{{128,243},		{404,522}},
	{{128,243},		{404,522}},
	{{128,243},		{404,522}},
	{{128,243},		{404,522}},
	{{127,243},		{404,523}},
	{{127,243},		{404,523}},
	{{127,243},		{404,523}},
	{{127,243},		{404,523}},
	{{127,243},		{404,523}},
	{{127,243},		{404,523}},
	{{127,243},		{404,523}},
	{{127,243},		{404,523}},
	{{127,243},		{404,523}},
	{{127,243},		{404,523}},
	{{126,243},		{404,524}},
	{{126,243},		{404,524}},
	{{126,243},		{404,524}},
	{{126,243},		{404,524}},
	{{126,242},		{405,524}},
	{{126,242},		{405,524}},
	{{126,242},		{405,524}},
	{{126,242},		{405,524}},
	{{126,242},		{405,524}},
	{{126,242},		{405,524}},
	{{125,242},		{405,525}},
	{{125,242},		{405,525}},
	{{125,242},		{405,525}},
	{{125,242},		{405,525}},
	{{125,242},		{405,525}},
	{{125,242},		{405,525}},
	{{125,242},		{405,525}},
	{{125,242},		{405,525}},
	{{125,242},		{405,525}},
	{{125,242},		{405,525}},
	{{124,242},		{405,526}},
	{{124,242},		{405,526}},
	{{124,241},		{406,526}},
	{{124,241},		{406,526}},
	{{124,241},		{406,526}},
	{{124,241},		{406,526}},
	{{124,241},		{406,526}},
	{{124,241},		{406,526}},
	{{124,241},		{406,526}},
	{{124,241},		{406,526}},
	{{124,241},		{406,527}},
	{{123,241},		{406,527}},
	{{123,241},		{406,527}},
	{{123,241},		{406,527}},
	{{123,241},		{406,527}},
	{{123,241},		{406,527}},
	{{123,241},		{406,527}},
	{{123,241},		{406,527}},
	{{123,241},		{406,527}},
	{{123,241},		{406,527}},
	{{123,241},		{406,527}},
	{{123,241},		{406,527}},
	{{123,241},		{406,527}},
	{{122,241},		{406,528}},
	{{122,241},		{406,528}},
	{{122,240},		{407,528}},
	{{122,240},		{407,528}},
	{{122,240},		{407,528}},
	{{122,240},		{407,528}},
	{{122,240},		{407,528}},
	{{122,240},		{407,528}},
	{{122,240},		{407,528}},
	{{122,240},		{407,529}},
	{{121,240},		{407,529}},
	{{121,240},		{407,529}},
	{{121,240},		{407,529}},
	{{121,240},		{407,529}},
	{{121,240},		{407,529}},
	{{121,240},		{407,529}},
	{{121,240},		{407,529}},
	{{121,239},		{408,529}},
	{{121,239},		{408,529}},
	{{121,239},		{408,529}},
	{{121,238},		{409,529}},
	{{121,238},		{409,529}},
	{{121,238},		{409,529}},
	{{122,237},		{410,529}},
	{{122,237},		{410,528}},
	{{123,236},		{411,527}},
	{{123,235},		{412,527}},
	{{124,234},		{413,526}},
	{{125,233},		{414,525}},
	{{126,232},		{415,524}},
	{{126,231},		{416,524}},
	{{128,230},		{417,522}},
	{{130,228},		{419,521}},
	{{131,226},		{422,519}},
	{{133,223},		{424,518}},
}};

struct gauge_inset_window
{
	fix fade_value{};
	weapon_index old_weapon{};
	weapon_box_state box_state{weapon_box_state::set};
#if DXX_BUILD_DESCENT == 2
	weapon_box_user user{weapon_box_user::weapon};
	uint8_t overlap_dirty{};
	fix time_static_played{};
#endif
};

per_gauge_inset_window_view_array<gauge_inset_window> inset_window;

static inline void hud_bitblt_free(grs_canvas &canvas, const unsigned x, const unsigned y, const unsigned w, const unsigned h, grs_bitmap &bm)
{
#if DXX_USE_OGL
	ogl_ubitmapm_cs(canvas, x, y, w, h, bm, ogl_colors::white);
#else
	gr_ubitmapm(canvas, x, y, bm);
#endif
}

static void hud_bitblt_scaled_xy(const hud_draw_context_hs hudctx, const unsigned x, const unsigned y, grs_bitmap &bm)
{
	hud_bitblt_free(hudctx.canvas, x, y, hudctx.xscale(bm.bm_w), hudctx.yscale(bm.bm_h), bm);
}

static void hud_bitblt(const hud_draw_context_hs hudctx, const unsigned x, const unsigned y, grs_bitmap &bm)
{
	hud_bitblt_scaled_xy(hudctx, hudctx.xscale(x), hudctx.yscale(y), bm);
}

}

}

namespace dsx {

namespace {

#if DXX_BUILD_DESCENT == 1
#define hud_gauge_bitblt_draw_context	hud_draw_context_hs
#elif DXX_BUILD_DESCENT == 2
#define hud_gauge_bitblt_draw_context	hud_draw_context_hs_mr
#endif
static void hud_gauge_bitblt(const hud_gauge_bitblt_draw_context hudctx, const unsigned x, const unsigned y, const unsigned gauge)
{
#if DXX_BUILD_DESCENT == 2
	auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
#endif
	PAGE_IN_GAUGE(gauge
#if DXX_BUILD_DESCENT == 2
		, hudctx.multires_gauge_graphic
#endif
	);
	hud_bitblt(hudctx, x, y, GameBitmaps[GET_GAUGE_INDEX(gauge)]);
}

class draw_keys_state
{
	const hud_draw_context_hs_mr hudctx;
	const player_flags player_key_flags;
public:
	constexpr draw_keys_state(const hud_draw_context_hs_mr hc, const player_flags f) :
		hudctx{hc}, player_key_flags{f}
	{
	}
	void draw_all_cockpit_keys();
	void draw_all_statusbar_keys();
protected:
	void draw_one_key(const unsigned x, const unsigned y, const unsigned gauge, const PLAYER_FLAG flag) const
	{
		hud_gauge_bitblt(hudctx, x, y, (player_key_flags & flag) ? gauge : (gauge + 3));
	}
};

}

}

namespace {

static void hud_show_score(grs_canvas &canvas, const player_info &player_info, const game_mode_flags Game_mode)
{
	char	score_str[20];

	if (HUD_toolong)
		return;

	const char *label;
	int value;
	if (+(Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP))
	{
		label = TXT_KILLS;
		value = player_info.net_kills_total;
	} else {
		label = TXT_SCORE;
		value = player_info.mission.score;
  	}
	snprintf(score_str, sizeof(score_str), "%s: %5d", label, value);

	if (Color_0_31_0 == -1)
		Color_0_31_0 = BM_XRGB(0,31,0);
	gr_set_fontcolor(canvas, Color_0_31_0, -1);

	auto &game_font = *GAME_FONT;
	const auto &&[w, h] = gr_get_string_size(game_font, score_str);
	gr_string(canvas, game_font, canvas.cv_bitmap.bm_w - w - FSPACX(1), FSPACY(1), score_str, w, h);
}

static void hud_show_timer_count(grs_canvas &canvas, const game_mode_flags Game_mode)
{
	auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;

	if (HUD_toolong)
		return;

	if (!(Game_mode & GM_NETWORK))
		return;

	if (!Netgame.PlayTimeAllowed.count())
		return;

	if (LevelUniqueControlCenterState.Control_center_destroyed)
		return;

	if (Netgame.PlayTimeAllowed < ThisLevelTime)
		return;

	const auto TicksUntilPlayTimeAllowedElapses = Netgame.PlayTimeAllowed - ThisLevelTime;
	const auto SecondsUntilPlayTimeAllowedElapses = f2i(TicksUntilPlayTimeAllowedElapses.count());
	if (SecondsUntilPlayTimeAllowedElapses >= 0)
	{
		if (Color_0_31_0 == -1)
			Color_0_31_0 = BM_XRGB(0,31,0);

		gr_set_fontcolor(canvas, Color_0_31_0, -1);

			char score_str[20];
			snprintf(score_str, sizeof(score_str), "T - %5d", SecondsUntilPlayTimeAllowedElapses + 1);
		const auto &&[w, h] = gr_get_string_size(*canvas.cv_font, score_str);
			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);
	}
}

static void hud_show_score_added(grs_canvas &canvas, const game_mode_flags Game_mode)
{
	int	color;

	if (+(Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP))
		return;

	if (score_display == 0)
		return;

	score_time -= FrameTime;
	if (score_time > 0) {
		color = f2i(score_time * 20) + 12;

		if (color < 10) color = 12;
		if (color > 31) color = 30;

		color = color - (color % 4);

		char score_buf[32];
		const auto score_str = cheats.enabled
			? TXT_CHEATER
			: (snprintf(score_buf, sizeof(score_buf), "%5d", score_display), score_buf);

		gr_set_fontcolor(canvas, BM_XRGB(0, color, 0), -1);
		auto &game_font = *GAME_FONT;
		const auto &&[w, h] = gr_get_string_size(game_font, score_str);
		gr_string(canvas, game_font, canvas.cv_bitmap.bm_w - w - FSPACX(PlayerCfg.CockpitMode[1] == cockpit_mode_t::full_screen ? 1 : 12), LINE_SPACING(game_font, game_font) + FSPACY(1), score_str, w, h);
	} else {
		score_time = 0;
		score_display = 0;
	}
}

static void sb_show_score(const hud_draw_context_hs_mr hudctx, const player_info &player_info, const bool is_multiplayer_non_cooperative)
{
	char	score_str[20];

	auto &canvas = hudctx.canvas;
	gr_set_fontcolor(canvas, BM_XRGB(0, 20, 0), -1);

	auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
	const auto y = hudctx.yscale(SB_SCORE_Y);
	auto &game_font = *GAME_FONT;
	gr_printf(canvas, game_font, hudctx.xscale(SB_SCORE_LABEL_X), y, "%s:", is_multiplayer_non_cooperative ? TXT_KILLS : TXT_SCORE);

	snprintf(score_str, sizeof(score_str), "%5d",
			is_multiplayer_non_cooperative
			? player_info.net_kills_total
			: (gr_set_fontcolor(canvas, BM_XRGB(0, 31, 0), -1), player_info.mission.score));
	const auto &&[w, h] = gr_get_string_size(game_font, score_str);

	const auto scaled_score_right = hudctx.xscale(SB_SCORE_RIGHT);
	const auto x = scaled_score_right - w - FSPACX(1);

	//erase old score
	const uint8_t color = BM_XRGB(0, 0, 0);
	gr_rect(canvas, x, y, scaled_score_right, y + LINE_SPACING(game_font, game_font), color);

	gr_string(canvas, game_font, x, y, score_str, w, h);
}

static void sb_show_score_added(const hud_draw_context_hs_mr hudctx)
{
	static int x;
	static	int last_score_display = -1;

	if (score_display == 0)
		return;

	auto &canvas = hudctx.canvas;
	auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;

	score_time -= FrameTime;
	if (score_time > 0) {
		if (score_display != last_score_display)
			last_score_display = score_display;

		int	color;
		color = f2i(score_time * 20) + 10;

		if (color < 10) color = 10;
		if (color > 31) color = 31;

		char score_buf[32];
		const auto score_str = cheats.enabled
			? TXT_CHEATER
			: (snprintf(score_buf, sizeof(score_buf), "%5d", score_display), score_buf);

		auto &game_font = *GAME_FONT;
		const auto &&[w, h] = gr_get_string_size(game_font, score_str);
		x = hudctx.xscale(SB_SCORE_ADDED_RIGHT) - w - FSPACX(1);
		gr_set_fontcolor(canvas, BM_XRGB(0, color, 0), -1);
		gr_string(canvas, game_font, x, hudctx.yscale(SB_SCORE_ADDED_Y), score_str, w, h);
	} else {
		//erase old score
		const uint8_t color = BM_XRGB(0, 0, 0);
		const auto scaled_score_y = hudctx.yscale(SB_SCORE_ADDED_Y);
		gr_rect(canvas, x, scaled_score_y, hudctx.xscale(SB_SCORE_ADDED_RIGHT), scaled_score_y + LINE_SPACING(*canvas.cv_font, *GAME_FONT), color);
		score_time = 0;
		score_display = 0;
	}
}

}

namespace dsx {

//	-----------------------------------------------------------------------------
void play_homing_warning(const player_info &player_info)
{
	fix beep_delay;
	static fix64 Last_warning_beep_time = 0; // Time we last played homing missile warning beep.

	if (Endlevel_sequence || Player_dead_state != player_dead_state::no)
		return;

	const auto homing_object_dist = player_info.homing_object_dist;
	if (homing_object_dist >= 0) {
		beep_delay = homing_object_dist / 128;
		if (beep_delay > F1_0)
			beep_delay = F1_0;
		else if (beep_delay < F1_0/8)
			beep_delay = F1_0/8;

		if (GameTime64 - Last_warning_beep_time > beep_delay/2 || Last_warning_beep_time > GameTime64) {
			Last_warning_beep_time = {GameTime64};
			digi_play_sample( sound_effect::SOUND_HOMING_WARNING, F1_0 );
		}
	}
}

}

namespace {

//	-----------------------------------------------------------------------------
static void show_homing_warning(const hud_draw_context_hs_mr hudctx, const int homing_object_dist)
{
	unsigned gauge;
	if (Endlevel_sequence)
	{
		gauge = GAUGE_HOMING_WARNING_OFF;
	}
	else
	{
		gauge = ((GameTime64 & 0x4000) && homing_object_dist >= 0)
			? GAUGE_HOMING_WARNING_ON
			: GAUGE_HOMING_WARNING_OFF;
	}
	auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
	hud_gauge_bitblt(hudctx, HOMING_WARNING_X, HOMING_WARNING_Y, gauge);
}

static void hud_show_homing_warning(grs_canvas &canvas, const int homing_object_dist)
{
	if (homing_object_dist >= 0)
	{
		if (GameTime64 & 0x4000) {
			gr_set_fontcolor(canvas, BM_XRGB(0, 31, 0), -1);
			auto &game_font = *GAME_FONT;
			gr_string(canvas, game_font, 0x8000, canvas.cv_bitmap.bm_h - LINE_SPACING(*canvas.cv_font, *GAME_FONT), TXT_LOCK);
		}
	}
}

static void hud_show_keys(const hud_draw_context_mr hudctx, const hud_ar_scale_float hud_scale_ar, const player_info &player_info)
{
	const auto player_key_flags = player_info.powerup_flags;
	if (!(player_key_flags & (PLAYER_FLAGS_BLUE_KEY | PLAYER_FLAGS_GOLD_KEY | PLAYER_FLAGS_RED_KEY)))
		return;
	class gauge_key
	{
		grs_bitmap *const bm;
	public:
		gauge_key(const unsigned key_icon
#if DXX_BUILD_DESCENT == 2
			, const local_multires_gauge_graphic multires_gauge_graphic
#endif
		) :
			bm{&GameBitmaps[(PAGE_IN_GAUGE(key_icon
#if DXX_BUILD_DESCENT == 2
				, multires_gauge_graphic
#endif
			), GET_GAUGE_INDEX(key_icon))]}
		{
		}
		grs_bitmap *operator->() const
		{
			return bm;
		}
		operator grs_bitmap &() const
		{
			return *bm;
		}
	};
#if DXX_BUILD_DESCENT == 2
	auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
#endif
	const gauge_key blue{KEY_ICON_BLUE
#if DXX_BUILD_DESCENT == 2
		, hudctx.multires_gauge_graphic
#endif
	};
	const unsigned y = hud_scale_ar(GameBitmaps[ GET_GAUGE_INDEX(GAUGE_LIVES) ].bm_h + 2) + FSPACY(1);

	const unsigned blue_bitmap_width = blue->bm_w;
	const auto &&fspacx2 = FSPACX(2);
	auto &canvas = hudctx.canvas;
	if (player_key_flags & PLAYER_FLAGS_BLUE_KEY)
		hud_bitblt_free(canvas, fspacx2, y, hud_scale_ar(blue_bitmap_width), hud_scale_ar(blue->bm_h), blue);

	if (!(player_key_flags & (PLAYER_FLAGS_GOLD_KEY | PLAYER_FLAGS_RED_KEY)))
		return;
	const gauge_key yellow{KEY_ICON_YELLOW
#if DXX_BUILD_DESCENT == 2
		, hudctx.multires_gauge_graphic
#endif
	};
	const unsigned yellow_bitmap_width = yellow->bm_w;
	if (player_key_flags & PLAYER_FLAGS_GOLD_KEY)
		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);

	if (player_key_flags & PLAYER_FLAGS_RED_KEY)
	{
		const gauge_key red{KEY_ICON_RED
#if DXX_BUILD_DESCENT == 2
			, hudctx.multires_gauge_graphic
#endif
		};
		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);
	}
}

#if DXX_BUILD_DESCENT == 2
static void hud_show_orbs(grs_canvas &canvas, const player_info &player_info, const local_multires_gauge_graphic multires_gauge_graphic)
{
	if (game_mode_hoard(Game_mode))
	{
		const auto &&fspacy1 = FSPACY(1);
		int x, y = LINE_SPACING(*canvas.cv_font, *GAME_FONT) + fspacy1;
		const auto &&hud_scale_ar = HUD_SCALE_AR(grd_curscreen->get_screen_width(), grd_curscreen->get_screen_height(), multires_gauge_graphic);
		if (PlayerCfg.CockpitMode[1] == cockpit_mode_t::full_cockpit)
		{
			x = (SWIDTH/18);
		}
		else
		{
			x = FSPACX(2);
		if (PlayerCfg.CockpitMode[1] == cockpit_mode_t::status_bar)
		{
		}
		else if (PlayerCfg.CockpitMode[1] == cockpit_mode_t::full_screen)
		{
			y = hud_scale_ar(GameBitmaps[ GET_GAUGE_INDEX(GAUGE_LIVES) ].bm_h + GameBitmaps[ GET_GAUGE_INDEX(KEY_ICON_RED) ].bm_h + 4) + fspacy1;
		}
		else
		{
			Int3();		//what sort of cockpit?
			return;
		}
		}

		gr_set_fontcolor(canvas, BM_XRGB(0, 31, 0), -1);
		auto &bm = Orb_icons[multires_gauge_graphic.is_hires()];
		const auto &&scaled_width = hud_scale_ar(bm.bm_w);
		hud_bitblt_free(canvas, x, y, scaled_width, hud_scale_ar(bm.bm_h), bm);
		gr_printf(canvas, *canvas.cv_font, x + scaled_width, y, " x %d", player_info.hoard.orbs);
	}
}

static void hud_show_flag(grs_canvas &canvas, const player_info &player_info, const local_multires_gauge_graphic multires_gauge_graphic)
{
	if (game_mode_capture_flag(Game_mode) && (player_info.powerup_flags & PLAYER_FLAGS_FLAG))
	{
		int x, y = GameBitmaps[ GET_GAUGE_INDEX(GAUGE_LIVES) ].bm_h + 2, icon;
		const auto &&fspacy1 = FSPACY(1);
		if (PlayerCfg.CockpitMode[1] == cockpit_mode_t::full_cockpit)
		{
			x = (SWIDTH/10);
		}
		else
		{
			x = FSPACX(2);
		if (PlayerCfg.CockpitMode[1] == cockpit_mode_t::status_bar)
		{
		}
		else if (PlayerCfg.CockpitMode[1] == cockpit_mode_t::full_screen)
		{
			y += GameBitmaps[GET_GAUGE_INDEX(KEY_ICON_RED)].bm_h + 2;
		}
		else
		{
			Int3();		//what sort of cockpit?
			return;
		}
		}

		icon = (multi_get_team_from_player(Netgame, Player_num) == team_number::blue) ? FLAG_ICON_RED : FLAG_ICON_BLUE;
		auto &bm = GameBitmaps[GET_GAUGE_INDEX(icon)];
		PAGE_IN_GAUGE(icon, multires_gauge_graphic);
		const auto &&hud_scale_ar = HUD_SCALE_AR(grd_curscreen->get_screen_width(), grd_curscreen->get_screen_height(), multires_gauge_graphic);
		hud_bitblt_free(canvas, x, hud_scale_ar(y) + fspacy1, hud_scale_ar(bm.bm_w), hud_scale_ar(bm.bm_h), bm);
	}
}
#endif

static void hud_show_energy(grs_canvas &canvas, const player_info &player_info, const grs_font &game_font, const unsigned current_y)
{
	auto &energy = player_info.energy;
	if (PlayerCfg.HudMode == HudType::Standard || PlayerCfg.HudMode == HudType::Alternate1)
	{
		gr_set_fontcolor(canvas, BM_XRGB(0, 31, 0), -1);
		gr_printf(canvas, game_font, FSPACX(1), current_y, "%s: %i", TXT_ENERGY, f2ir(energy));
	}

	if (Newdemo_state == ND_STATE_RECORDING)
		newdemo_record_player_energy(f2ir(energy));
}

#if DXX_BUILD_DESCENT == 1
#define convert_1s(s)
#elif DXX_BUILD_DESCENT == 2
static void hud_show_afterburner(grs_canvas &canvas, const player_info &player_info, const grs_font &game_font, const unsigned current_y)
{
	if (! (player_info.powerup_flags & PLAYER_FLAGS_AFTERBURNER))
		return;		//don't draw if don't have

	gr_set_fontcolor(canvas, BM_XRGB(0, 31, 0), -1);
	gr_printf(canvas, game_font, FSPACX(1), current_y, "burn: %d%%" , fixmul(Afterburner_charge, 100));

	if (Newdemo_state==ND_STATE_RECORDING )
		newdemo_record_player_afterburner(Afterburner_charge);
}

//convert '1' characters to special wide ones
#define convert_1s(s) do {char *p=s; while ((p=strchr(p,'1')) != NULL) *p=132;} while(0)
#endif

[[nodiscard]]
static constexpr const char *SECONDARY_WEAPON_NAMES_VERY_SHORT(const secondary_weapon_index u)
{
	switch(u)
	{
		default:
			Int3();
			[[fallthrough]];
		case secondary_weapon_index::concussion:	return TXT_CONCUSSION;
		case secondary_weapon_index::homing:		return TXT_HOMING;
		case secondary_weapon_index::proximity:	return TXT_PROXBOMB;
		case secondary_weapon_index::smart:		return TXT_SMART;
		case secondary_weapon_index::mega:		return TXT_MEGA;
#if DXX_BUILD_DESCENT == 2
		case secondary_weapon_index::flash:	return "Flash";
		case secondary_weapon_index::guided:		return "Guided";
		case secondary_weapon_index::smart_mine:	return "SmrtMine";
		case secondary_weapon_index::mercury:	return "Mercury";
		case secondary_weapon_index::earthshaker:	return "Shaker";
#endif
	}
}

}

namespace dsx {

namespace {

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)
{
#if DXX_BUILD_DESCENT == 1
	if (!PlayerCfg.BombGauge)
		return;
#endif

	const auto bomb = which_bomb(player_info);
	int count = player_info.secondary_ammo[bomb];

	count = min(count,99);	//only have room for 2 digits - cheating give 200

	if (always_show && count == 0)		//no bombs, draw nothing on HUD
		return;

	gr_set_fontcolor(canvas, count
		? (bomb == secondary_weapon_index::proximity
			? gr_find_closest_color(55, 0, 0)
			: BM_XRGB(59, 50, 21)
		)
		: bg_color,	//erase by drawing in background color
		bg_color);

	char txt[5];
	snprintf(txt, sizeof(txt), "B:%02d", count);
	//convert to wide '1'
	std::replace(&txt[2], &txt[4], '1', '\x84');

	const auto &&[w, h] = gr_get_string_size(*canvas.cv_font, txt);
	gr_string(canvas, *canvas.cv_font, right_align ? x - w : x, y, txt, w, h);
}
}
}

namespace {
static void draw_primary_ammo_info(const hud_draw_context_hs_mr hudctx, const unsigned ammo_count)
{
	int x, y;
	auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
	if (PlayerCfg.CockpitMode[1] == cockpit_mode_t::status_bar)
		x = SB_PRIMARY_AMMO_X, y = SB_PRIMARY_AMMO_Y;
	else
		x = PRIMARY_AMMO_X, y = PRIMARY_AMMO_Y;
	draw_ammo_info(hudctx.canvas, hudctx.xscale(x), hudctx.yscale(y), ammo_count);
}
}

namespace dcx {
namespace {

constexpr rgb_t hud_rgb_red = {40, 0, 0};
constexpr rgb_t hud_rgb_green = {0, 30, 0};
constexpr rgb_t hud_rgb_dimgreen = {0, 12, 0};
constexpr rgb_t hud_rgb_gray = {6, 6, 6};
}
}

namespace dsx {
namespace {

#if DXX_BUILD_DESCENT == 2
constexpr rgb_t hud_rgb_yellow = {30, 30, 0};
#endif

[[nodiscard]]
static rgb_t hud_get_primary_weapon_fontcolor(const player_info &player_info, const primary_weapon_index consider_weapon)
{
	if (player_info.Primary_weapon == consider_weapon)
		/* The currently active weapon is `consider_weapon` */
		return hud_rgb_red;
	else{
		if (has_weapon(player_has_primary_weapon(player_info, consider_weapon)))
		{
			/* The player has not armed this weapon, but could do so. */
#if DXX_BUILD_DESCENT == 2
			const auto is_super = is_super_weapon(consider_weapon);
			const auto base_weapon = is_super ? primary_weapon_index{static_cast<uint8_t>(static_cast<uint8_t>(consider_weapon) - SUPER_WEAPON)} : consider_weapon;
			if (player_info.Primary_last_was_super & HAS_PRIMARY_FLAG(base_weapon))
			{
				/* The player would select the super-weapon version on next press. */
				if (is_super)
					/* The weapon is a super-weapon, so it would be selected on
					 * next press.
					 */
					return hud_rgb_green;
				else
					/* The weapon is not a super-weapon, so the player will
					 * need to press twice to select it (once to make the
					 * non-super active, then again to toggle to super).
					 */
					return hud_rgb_yellow;
			}
			/* Else the player will select the non-super weapon on next press */
			else if (is_super)
				/* The candidate weapon is a super-weapon, so the player will
				 * need to press twice to select it.
				 */
				return hud_rgb_yellow;
			else
#endif
				return hud_rgb_green;
		}
		else
			return hud_rgb_gray;
	}
}

static void hud_set_primary_weapon_fontcolor(const player_info &player_info, const primary_weapon_index consider_weapon, grs_canvas &canvas)
{
	auto rgb = hud_get_primary_weapon_fontcolor(player_info, consider_weapon);
	gr_set_fontcolor(canvas, gr_find_closest_color(rgb.r, rgb.g, rgb.b), -1);
}

[[nodiscard]]
static rgb_t hud_get_secondary_weapon_fontcolor(const player_info &player_info, const secondary_weapon_index consider_weapon)
{
	if (player_info.Secondary_weapon == consider_weapon)
		/* The currently active weapon is `consider_weapon` */
		return hud_rgb_red;
	else{
		if (player_info.secondary_ammo[consider_weapon])
		{
			/* The player has not armed this weapon, but has ammo for it. */
#if DXX_BUILD_DESCENT == 2
			const auto is_super = is_super_weapon(consider_weapon);
			const auto base_weapon = is_super ? secondary_weapon_index{static_cast<uint8_t>(static_cast<uint8_t>(consider_weapon) - SUPER_WEAPON)} : consider_weapon;
			if (player_info.Secondary_last_was_super & HAS_SECONDARY_FLAG(base_weapon))
			{
				/* The player would select the super-weapon version on next press. */
				if (is_super)
					/* The weapon is a super-weapon, so it would be selected on
					 * next press.
					 */
					return hud_rgb_green;
				else
					/* The weapon is not a super-weapon, so the player will
					 * need to press twice to select it (once to make the
					 * non-super active, then again to toggle to super).
					 */
					return hud_rgb_yellow;
			}
			/* Else the player will select the non-super weapon on next press */
			else if (is_super)
				/* The candidate weapon is a super-weapon, so the player will
				 * need to press twice to select it.
				 */
				return hud_rgb_yellow;
			else
#endif
				return hud_rgb_green;
		}
		else
			/* The weapon has no ammo, so dim it out. */
			return hud_rgb_dimgreen;
	}
}

static void hud_set_secondary_weapon_fontcolor(const player_info &player_info, const secondary_weapon_index consider_weapon, grs_canvas &canvas)
{
	auto rgb = hud_get_secondary_weapon_fontcolor(player_info, consider_weapon);
	gr_set_fontcolor(canvas, gr_find_closest_color(rgb.r, rgb.g, rgb.b), -1);
}

[[nodiscard]]
static rgb_t hud_get_vulcan_ammo_fontcolor(const player_info &player_info, const unsigned has_weapon_uses_vulcan_ammo)
{
	if (weapon_index_uses_vulcan_ammo(player_info.Primary_weapon))
		return hud_rgb_red;
	else if (has_weapon_uses_vulcan_ammo)
		return hud_rgb_green;
	else
		return hud_rgb_gray;
}

static void hud_set_vulcan_ammo_fontcolor(const player_info &player_info, const unsigned has_weapon_uses_vulcan_ammo, grs_canvas &canvas)
{
	auto rgb = hud_get_vulcan_ammo_fontcolor(player_info, has_weapon_uses_vulcan_ammo);
	gr_set_fontcolor(canvas, gr_find_closest_color(rgb.r, rgb.g, rgb.b), -1);
}

static void hud_printf_vulcan_ammo(grs_canvas &canvas, const player_info &player_info, const int x, const int y)
{
	const unsigned primary_weapon_flags = player_info.primary_weapon_flags;
	const auto vulcan_mask{HAS_VULCAN_FLAG};
#if DXX_BUILD_DESCENT == 1
	const auto gauss_mask{vulcan_mask};
#elif DXX_BUILD_DESCENT == 2
	const auto gauss_mask{HAS_GAUSS_FLAG};
#endif
	const auto fmt_vulcan_ammo = vulcan_ammo_scale(player_info.vulcan_ammo);
	const unsigned has_weapon_uses_vulcan_ammo = (primary_weapon_flags & (gauss_mask | vulcan_mask));
	if (!has_weapon_uses_vulcan_ammo && !fmt_vulcan_ammo)
		return;
	hud_set_vulcan_ammo_fontcolor(player_info, has_weapon_uses_vulcan_ammo, canvas);
	const char c =
#if DXX_BUILD_DESCENT == 2
		((primary_weapon_flags & gauss_mask) && ((player_info.Primary_last_was_super & HAS_VULCAN_FLAG) || !(primary_weapon_flags & vulcan_mask)))
		? 'G'
		: 
#endif
		(primary_weapon_flags & vulcan_mask)
			? 'V'
			: 'A'
	;
	gr_printf(canvas, *canvas.cv_font, x, y, "%c:%u", c, fmt_vulcan_ammo);
}

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)
{
	int x=orig_x,y=orig_y;

	const auto &&line_spacing = LINE_SPACING(*canvas.cv_font, *GAME_FONT);
	if (vertical){
		y += line_spacing * 4;
	}

	const auto &&fspacx = FSPACX();
	const auto &&fspacx3 = fspacx(3);
	const auto &&fspacy2 = FSPACY(2);
	{
		for (uint_fast32_t ui = 5; ui --;)
		{
			const auto i = static_cast<primary_weapon_index>(ui);
			const char *txtweapon;
			char weapon_str[10];
			hud_set_primary_weapon_fontcolor(player_info, i, canvas);
			switch(i)
			{
				case primary_weapon_index::laser:
					{
						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);
					txtweapon = weapon_str;
					}
					break;
				case primary_weapon_index::vulcan:
					txtweapon = "V";
					break;
				case primary_weapon_index::spreadfire:
					txtweapon = "S";
					break;
				case primary_weapon_index::plasma:
					txtweapon = "P";
					break;
				case primary_weapon_index::fusion:
					txtweapon = "F";
					break;
				default:
					continue;
			}
			const auto &&[w, h] = gr_get_string_size(*canvas.cv_font, txtweapon);
			if (vertical){
				y -= h + fspacy2;
			}else
				x -= w + fspacx3;
			gr_string(canvas, *canvas.cv_font, x, y, txtweapon, w, h);
			if (i == primary_weapon_index::vulcan)
			{
				/*
				 * In Descent 1, this will always draw the ammo, but the
				 * position depends on fullscreen and, if in fullscreen,
				 * whether in vertical mode.
				 *
				 * In Descent 2, this will draw in non-fullscreen and in
				 * fullscreen non-vertical, but not in fullscreen
				 * vertical.  The fullscreen vertical case is handled
				 * specially in a large Descent2 block below.
				 */
				int vx, vy;
				if (PlayerCfg.CockpitMode[1] == cockpit_mode_t::full_screen ? (
						vertical ? (
#if DXX_BUILD_DESCENT == 1
							vx = x, vy = y, true
#else
							false
#endif
						) : (
							vx = x, vy = y - line_spacing, true
						)
					) : (
						vx = x - (w + fspacx3), vy = y - ((h + fspacy2) * 2), true
					)
				)
					hud_printf_vulcan_ammo(canvas, player_info, vx, vy);
			}
		}
	}
#if DXX_BUILD_DESCENT == 2
	x = orig_x;
	y = orig_y;
	if (vertical)
	{
		x += fspacx(15);
		y += line_spacing * 4;
	}
	else
	{
		y += line_spacing;
	}

	{
		for (uint_fast32_t ui = 10; ui -- != 5;)
		{
			const auto i = static_cast<primary_weapon_index>(ui);
			const char *txtweapon;
			char weapon_str[10];
			hud_set_primary_weapon_fontcolor(player_info, i, canvas);
			switch(i)
			{
				case primary_weapon_index::super_laser:
					txtweapon = " ";
					break;
				case primary_weapon_index::gauss:
					txtweapon = "G";
					break;
				case primary_weapon_index::helix:
					txtweapon = "H";
					break;
				case primary_weapon_index::phoenix:
					txtweapon = "P";
					break;
				case primary_weapon_index::omega:
					if (PlayerCfg.CockpitMode[1] == cockpit_mode_t::full_screen && (player_info.primary_weapon_flags & HAS_OMEGA_FLAG))
					{
						snprintf(weapon_str, sizeof(weapon_str), "O%3i", player_info.Omega_charge * 100 / MAX_OMEGA_CHARGE);
						txtweapon = weapon_str;
					}
					else
						txtweapon = "O";
					break;
				default:
					continue;
			}
			const auto &&[w, h] = gr_get_string_size(*canvas.cv_font, txtweapon);
			if (vertical){
				y -= h + fspacy2;
			}else
				x -= w + fspacx3;
			if (i == primary_weapon_index::super_laser)
			{
				if (vertical && (PlayerCfg.CockpitMode[1] == cockpit_mode_t::full_screen))
					hud_printf_vulcan_ammo(canvas, player_info, x, y);
				continue;
			}
			gr_string(canvas, *canvas.cv_font, x, y, txtweapon, w, h);
		}
	}
#endif
	gr_set_fontcolor(canvas, BM_XRGB(0, 31, 0), -1);
}

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)
{
	int x=orig_x,y=orig_y;

	const auto &&line_spacing = LINE_SPACING(*canvas.cv_font, *GAME_FONT);
	if (vertical){
		y += line_spacing * 4;
	}

	const auto &&fspacx = FSPACX();
	const auto &&fspacx3 = fspacx(3);
	const auto &&fspacy2 = FSPACY(2);
	auto &secondary_ammo = player_info.secondary_ammo;
	{
		for (uint_fast32_t ui = 5; ui --;)
		{
			const auto i = static_cast<secondary_weapon_index>(ui);
			char weapon_str[10];
			hud_set_secondary_weapon_fontcolor(player_info, i, canvas);
			snprintf(weapon_str, sizeof(weapon_str), "%i", secondary_ammo[i]);
			const auto &&[w, h] = gr_get_string_size(*canvas.cv_font, weapon_str);
			if (vertical){
				y -= h + fspacy2;
			}else
				x -= w + fspacx3;
			gr_string(canvas, *canvas.cv_font, x, y, weapon_str, w, h);
		}
	}

#if DXX_BUILD_DESCENT == 2
	x = orig_x;
	y = orig_y;
	if (vertical)
	{
		x += fspacx(15);
		y += line_spacing * 4;
	}
	else
	{
		y += line_spacing;
	}

	{
		for (uint_fast32_t ui = 10; ui -- != 5;)
		{
			const auto i = static_cast<secondary_weapon_index>(ui);
			char weapon_str[10];
			hud_set_secondary_weapon_fontcolor(player_info, i, canvas);
			snprintf(weapon_str, sizeof(weapon_str), "%u", secondary_ammo[i]);
			const auto &&[w, h] = gr_get_string_size(*canvas.cv_font, weapon_str);
			if (vertical){
				y -= h + fspacy2;
			}else
				x -= w + fspacx3;
			gr_string(canvas, *canvas.cv_font, x, y, weapon_str, w, h);
		}
	}
#endif
	gr_set_fontcolor(canvas, BM_XRGB(0, 31, 0), -1);
}

static void hud_show_weapons(grs_canvas &canvas, const object &plrobj, const grs_font &game_font, const bool is_multiplayer)
{
	auto &player_info = plrobj.ctype.player_info;
	int	y;
	const char	*weapon_name;
	char	weapon_str[32];

	gr_set_fontcolor(canvas, BM_XRGB(0, 31, 0), -1);

	y = canvas.cv_bitmap.bm_h;

	const auto &&line_spacing = LINE_SPACING(game_font, game_font);
	if (is_multiplayer)
		y -= line_spacing * 4;

	if (PlayerCfg.HudMode == HudType::Alternate1)
	{
#if DXX_BUILD_DESCENT == 1
		constexpr unsigned multiplier = 1;
#elif DXX_BUILD_DESCENT == 2
		constexpr unsigned multiplier = 2;
#endif
		hud_show_primary_weapons_mode(canvas, player_info, 0, canvas.cv_bitmap.bm_w, y - (line_spacing * 2 * multiplier));
		hud_show_secondary_weapons_mode(canvas, player_info, 0, canvas.cv_bitmap.bm_w, y - (line_spacing * multiplier));
		return;
	}
	const auto &&fspacx = FSPACX();
	if (PlayerCfg.HudMode == HudType::Alternate2)
	{
		int x1;
		const auto w = gr_get_string_size(game_font, "V1000").width;
		const auto w0 = gr_get_string_size(game_font, "0 ").width;
		y = canvas.cv_bitmap.bm_h / 1.75;
		x1 = canvas.cv_bitmap.bm_w / 2.1 - (fspacx(40) + w);
		const auto x2 = canvas.cv_bitmap.bm_w / 1.9 + (fspacx(42) + w0);
		hud_show_primary_weapons_mode(canvas, player_info, 1, x1, y);
		hud_show_secondary_weapons_mode(canvas, player_info, 1, x2, y);
		gr_set_fontcolor(canvas, BM_XRGB(14, 14, 23), -1);
		gr_printf(canvas, game_font, x2, y - (line_spacing * 4), "%i", f2ir(plrobj.shields));
		gr_set_fontcolor(canvas, BM_XRGB(25, 18, 6), -1);
		gr_printf(canvas, game_font, x1, y - (line_spacing * 4), "%i", f2ir(player_info.energy));
	}
	else
	{
		const char *disp_primary_weapon_name;
		const auto Primary_weapon = player_info.Primary_weapon;

		weapon_name = PRIMARY_WEAPON_NAMES_SHORT(Primary_weapon);
		switch (Primary_weapon) {
			case primary_weapon_index::laser:
				{
					const auto level = static_cast<unsigned>(player_info.laser_level) + 1;
				if (player_info.powerup_flags & PLAYER_FLAGS_QUAD_LASERS)
					snprintf(weapon_str, sizeof(weapon_str), "%s %s %u", TXT_QUAD, weapon_name, level);
				else
					snprintf(weapon_str, sizeof(weapon_str), "%s %u", weapon_name, level);
				}
				disp_primary_weapon_name = weapon_str;
				break;

			case primary_weapon_index::vulcan:
#if DXX_BUILD_DESCENT == 2
			case primary_weapon_index::gauss:
#endif
				snprintf(weapon_str, sizeof(weapon_str), "%s: %u", weapon_name, vulcan_ammo_scale(player_info.vulcan_ammo));
				convert_1s(weapon_str);
				disp_primary_weapon_name = weapon_str;
				break;

			case primary_weapon_index::spreadfire:
			case primary_weapon_index::plasma:
			case primary_weapon_index::fusion:
#if DXX_BUILD_DESCENT == 2
			case primary_weapon_index::helix:
			case primary_weapon_index::phoenix:
#endif
				disp_primary_weapon_name = weapon_name;
				break;
#if DXX_BUILD_DESCENT == 2
			case primary_weapon_index::omega:
				snprintf(weapon_str, sizeof(weapon_str), "%s: %03i", weapon_name, player_info.Omega_charge * 100 / MAX_OMEGA_CHARGE);
				convert_1s(weapon_str);
				disp_primary_weapon_name = weapon_str;
				break;

			case primary_weapon_index::super_laser:	//no such thing as super laser
#endif
			default:
				Int3();
				disp_primary_weapon_name = "";
				break;
		}

		const auto &&bmwx = canvas.cv_bitmap.bm_w - fspacx(1);
		{
			const auto &&[w, h] = gr_get_string_size(game_font, disp_primary_weapon_name);
		gr_string(canvas, game_font, bmwx - w, y - (line_spacing * 2), disp_primary_weapon_name, w, h);
		}
		const char *disp_secondary_weapon_name;

		auto &Secondary_weapon = player_info.Secondary_weapon;
		disp_secondary_weapon_name = SECONDARY_WEAPON_NAMES_VERY_SHORT(Secondary_weapon);

		snprintf(weapon_str, sizeof(weapon_str), "%s %u", disp_secondary_weapon_name, player_info.secondary_ammo[Secondary_weapon]);
		{
			const auto &&[w, h] = gr_get_string_size(game_font, weapon_str);
		gr_string(canvas, game_font, bmwx - w, y - line_spacing, weapon_str, w, h);
		}

		show_bomb_count(canvas, player_info, bmwx, y - (line_spacing * 3), -1, 1, 1);
	}
}
}
}

namespace {

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)
{
	if (!(player_flags & (PLAYER_FLAGS_CLOAKED | PLAYER_FLAGS_INVULNERABLE)))
		return;
	gr_set_fontcolor(canvas, BM_XRGB(0, 31, 0), -1);
	const auto &&line_spacing = LINE_SPACING(*canvas.cv_font, *GAME_FONT);
	const auto gametime64{GameTime64};
	const auto &&fspacx1 = FSPACX(1);

	const auto cloak_invul_timer = show_cloak_invul_timer();
	const auto a = [&](const fix64 effect_end, int y, const char *txt) {
		if (cloak_invul_timer)
			gr_printf(canvas, *canvas.cv_font, fspacx1, y, "%s: %lu", txt, static_cast<unsigned long>(effect_end / F1_0));
		else
			gr_string(canvas, *canvas.cv_font, fspacx1, y, txt);
	};

	if (player_flags & PLAYER_FLAGS_CLOAKED)
	{
		const fix64 effect_end = cloak_time + CLOAK_TIME_MAX - gametime64;
		if (effect_end > F1_0*3 || gametime64 & 0x8000)
		{
			a(effect_end, base_y, TXT_CLOAKED);
		}
	}

	if (player_flags & PLAYER_FLAGS_INVULNERABLE)
	{
		const fix64 effect_end = invulnerable_time + INVULNERABLE_TIME_MAX - gametime64;
		if (effect_end > F1_0*4 || gametime64 & 0x8000)
		{
			a(effect_end, base_y - line_spacing, TXT_INVULNERABLE);
		}
	}
}

static void hud_show_shield(grs_canvas &canvas, const object &plrobj, const grs_font &game_font, const unsigned current_y)
{
	if (PlayerCfg.HudMode == HudType::Standard || PlayerCfg.HudMode == HudType::Alternate1)
	{
		gr_set_fontcolor(canvas, BM_XRGB(0, 31, 0), -1);

		const auto shields{plrobj.shields};
		gr_printf(canvas, game_font, FSPACX(1), current_y, "%s: %i", TXT_SHIELD, shields >= 0 ? f2ir(shields) : 0);
	}

	if (Newdemo_state==ND_STATE_RECORDING )
		newdemo_record_player_shields(f2ir(plrobj.shields));
}

//draw the icons for number of lives
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)
{
	if (HUD_toolong)
		return;
	if (Newdemo_state == ND_STATE_PLAYBACK)
		return;

	const int x = (PlayerCfg.CockpitMode[1] == cockpit_mode_t::full_cockpit)
		? static_cast<int>(hudctx.xscale(7))
		: static_cast<int>(FSPACX(2));

	auto &canvas = hudctx.canvas;
	auto &game_font = *GAME_FONT;
	if (is_multiplayer)
	{
		gr_set_fontcolor(canvas, BM_XRGB(0, 31, 0), -1);
		gr_printf(canvas, game_font, x, FSPACY(1), "%s: %d", TXT_DEATHS, player_info.net_killed_total);
	}
	else if (const uint16_t lives = get_local_player().lives - 1)
	{
		gr_set_curfont(canvas, game_font);
		gr_set_fontcolor(canvas, BM_XRGB(0, 20, 0), -1);
#if DXX_BUILD_DESCENT == 2
		auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
#endif
		PAGE_IN_GAUGE(GAUGE_LIVES
#if DXX_BUILD_DESCENT == 2
			, multires_gauge_graphic
#endif
		);
		auto &bm = GameBitmaps[GET_GAUGE_INDEX(GAUGE_LIVES)];
		const auto &&fspacy1 = FSPACY(1);
		hud_bitblt_free(canvas, x, fspacy1, hud_scale_ar(bm.bm_w), hud_scale_ar(bm.bm_h), bm);
		auto &game_font = *GAME_FONT;
		gr_printf(canvas, game_font, hud_scale_ar(bm.bm_w) + x, fspacy1, " x %hu", lives);
	}
}

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)
{
	if (Newdemo_state == ND_STATE_PLAYBACK)
		return;
	auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
	const auto y{SB_LIVES_Y};

	auto &canvas = hudctx.canvas;
	gr_set_fontcolor(canvas, BM_XRGB(0, 20, 0), -1);
	const auto scaled_y = hudctx.yscale(y);
	auto &game_font = *GAME_FONT;
	gr_printf(canvas, game_font, hudctx.xscale(SB_LIVES_LABEL_X), scaled_y, "%s:", is_multiplayer ? TXT_DEATHS : TXT_LIVES);

	const uint8_t color = BM_XRGB(0,0,0);
	const auto scaled_score_right = hudctx.xscale(SB_SCORE_RIGHT);
	if (is_multiplayer)
	{
		char killed_str[20];
		static std::array<int, 4> last_x{{SB_SCORE_RIGHT_L, SB_SCORE_RIGHT_L, SB_SCORE_RIGHT_H, SB_SCORE_RIGHT_H}};

		snprintf(killed_str, sizeof(killed_str), "%5d", player_info.net_killed_total);
		const auto &&[w, h] = gr_get_string_size(game_font, killed_str);
		const auto x = scaled_score_right - w - FSPACX(1);
		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);
		gr_string(canvas, game_font, x, scaled_y, killed_str, w, h);
		return;
	}

	const int x = SB_LIVES_X;
	//erase old icons
	auto &bm = GameBitmaps[GET_GAUGE_INDEX(GAUGE_LIVES)];
	const auto scaled_x = hudctx.xscale(x);
	gr_rect(canvas, scaled_x, scaled_y, scaled_score_right, hudctx.yscale(y + bm.bm_h), color);

	if (const uint16_t lives = get_local_player().lives - 1)
	{
		PAGE_IN_GAUGE(GAUGE_LIVES
#if DXX_BUILD_DESCENT == 2
			, hudctx.multires_gauge_graphic
#endif
		);
		const auto scaled_width = hud_scale_ar(bm.bm_w);
		hud_bitblt_free(canvas, scaled_x, scaled_y, scaled_width, hud_scale_ar(bm.bm_h), bm);
		gr_printf(canvas, game_font, scaled_x + scaled_width, scaled_y, " x %hu", lives);
	}
}

#ifndef RELEASE
static void show_time(grs_canvas &canvas, const grs_font &cv_font)
{
	auto &plr = get_local_player();
	const unsigned secs = f2i(plr.time_level) % 60;
	const unsigned mins = f2i(plr.time_level) / 60;

	if (Color_0_31_0 == -1)
		Color_0_31_0 = BM_XRGB(0,31,0);
	gr_set_fontcolor(canvas, Color_0_31_0, -1);
	auto &game_font = *GAME_FONT;
	gr_printf(canvas, game_font, FSPACX(2), (LINE_SPACING(cv_font, game_font) * 15), "%d:%02d", mins, secs);
}
#endif

#define EXTRA_SHIP_SCORE	50000		//get new ship every this many points

static void common_add_points_to_score(const int points, int &score, const game_mode_flags Game_mode)
{
	if (points == 0 || cheats.enabled)
		return;

	if (Newdemo_state == ND_STATE_RECORDING)
		newdemo_record_player_score(points);

	const auto prev_score{score};
	score += points;

	if (+(Game_mode & GM_MULTI))
		/* In single player, fall through and check whether an extra
		 * life has been earned.  In multiplayer, extra lives cannot be
		 * earned, so return.
		 */
		return;

	const auto current_ship_score = score / EXTRA_SHIP_SCORE;
	const auto previous_ship_score = prev_score / EXTRA_SHIP_SCORE;
	if (current_ship_score != previous_ship_score)
	{
		get_local_player().lives += current_ship_score - previous_ship_score;
		const auto &&m = TXT_EXTRA_LIFE;
		powerup_basic_str(20, 20, 20, 0, {m, strlen(m)});
		if (const auto snd{Powerup_info[powerup_type_t::POW_EXTRA_LIFE].hit_sound}; snd != sound_effect::None)
			digi_play_sample( snd, F1_0 );
	}
}

}

namespace dsx {

void add_points_to_score(player_info &player_info, const unsigned points, const game_mode_flags Game_mode)
{
	if (+(Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP))
		return;
	score_time += f1_0*2;
	score_display += points;
	if (score_time > f1_0*4) score_time = f1_0*4;

	common_add_points_to_score(points, player_info.mission.score, Game_mode);
	if (+(Game_mode & GM_MULTI_COOP))
		multi_send_score();
}

/* This is only called in single player when the player is between
 * levels.
 */
void add_bonus_points_to_score(player_info &player_info, unsigned points, const game_mode_flags Game_mode)
{
	assert(!(Game_mode & GM_MULTI));
	common_add_points_to_score(points, player_info.mission.score, Game_mode);
}

}

namespace {

// 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
static void cockpit_decode_alpha(const hud_draw_context_mr hudctx, grs_bitmap *const bm)
{
	static const uint8_t *cur;
	static uint16_t cur_w, cur_h;
#ifndef DXX_MAX_COCKPIT_BITMAP_SIZE
	/* 640 wide by 480 high should be enough for all bitmaps shipped
	 * with shareware or commercial data.
	 * Use a #define so that the value can be easily overridden at build
	 * time.
	 */
#define DXX_MAX_COCKPIT_BITMAP_SIZE	(640 * 480)
#endif

	const unsigned bm_h = bm->bm_h;
	if (unlikely(!bm_h))
		/* Invalid, but later code has undefined results if bm_h==0 */
		return;
	const unsigned bm_w = bm->bm_w;
	// check if we processed this bitmap already
	if (cur == bm->bm_data && cur_w == bm_w && cur_h == bm_h)
	{
#if DXX_USE_OGL
		// check if textures are still valid
		const ogl_texture *gltexture;
		if ((gltexture = WinBoxOverlay[0].get()->gltexture) &&
			gltexture->handle &&
			(gltexture = WinBoxOverlay[1].get()->gltexture) &&
			gltexture->handle)
			return;
#else
		return;
#endif
	}

	auto cockpitbuf = std::make_unique<uint8_t[]>(DXX_MAX_COCKPIT_BITMAP_SIZE);

	// decode the bitmap
	if (bm->get_flag_mask(BM_FLAG_RLE))
	{
		if (!bm_rle_expand(*bm).loop(bm_w, bm_rle_expand_range(cockpitbuf.get(), cockpitbuf.get() + DXX_MAX_COCKPIT_BITMAP_SIZE)))
		{
				/* Out of space.  Return without adjusting the bitmap.
				 * The result will look ugly, but run correctly.
				 */
				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);
				return;
		}
	}
	else
	{
		const std::size_t len = bm_w * bm_h;
		if (len > DXX_MAX_COCKPIT_BITMAP_SIZE)
		{
			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);
			return;
		}
		memcpy(cockpitbuf.get(), bm->bm_data, len);
	}

	// add alpha color to the pixels which are inside the window box spans
	auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
	const unsigned lower_y = ((multires_gauge_graphic.get(364, 151)));
	unsigned i = bm_w * lower_y;
	const auto fill_alpha_one_line = [&cockpitbuf](unsigned o, const d_gauge_span &s) {
		std::fill_n(&cockpitbuf[o + s.l], s.r - s.l + 1, TRANSPARENCY_COLOR);
	};
	range_for (auto &s,
		multires_gauge_graphic.is_hires()
		? std::ranges::subrange(weapon_windows_hires)
		: std::ranges::subrange(weapon_windows_lowres)
	)
	{
		fill_alpha_one_line(i, s.l);
		fill_alpha_one_line(i, s.r);
		i += bm_w;
	}
	constexpr auto lowres_primary_height = PRIMARY_W_BOX_BOT_L - PRIMARY_W_BOX_TOP_L;
	constexpr auto highres_primary_height = PRIMARY_W_BOX_BOT_H - PRIMARY_W_BOX_TOP_H;
	static_assert(lowres_primary_height == SECONDARY_W_BOX_BOT_L - SECONDARY_W_BOX_TOP_L);
	static_assert(highres_primary_height == SECONDARY_W_BOX_BOT_H - SECONDARY_W_BOX_TOP_H);
	const auto primary_left = (PRIMARY_W_BOX_LEFT) - 2;
	const auto primary_top = (PRIMARY_W_BOX_TOP) - 2;
	const auto primary_width = PRIMARY_W_BOX_RIGHT - PRIMARY_W_BOX_LEFT + 4;
	const auto primary_height = PRIMARY_W_BOX_BOT - PRIMARY_W_BOX_TOP + 4;
	const auto secondary_left = (SECONDARY_W_BOX_LEFT) - 2;
	const auto secondary_top = (SECONDARY_W_BOX_TOP) - 2;
	const auto secondary_width = (SECONDARY_W_BOX_RIGHT - SECONDARY_W_BOX_LEFT) + 4;
	const auto sum_box_width = primary_width + secondary_width;
	RAIIdmem<uint8_t[]> inset_window_buffer;
	MALLOC(inset_window_buffer, uint8_t[], primary_height * sum_box_width);
	{
		const uint8_t *primary_src_iter = &cockpitbuf[primary_left + (primary_top * bm_w)];
		const uint8_t *secondary_src_iter = &cockpitbuf[secondary_left + (secondary_top * bm_w)];
		uint8_t *primary_dst_iter = &inset_window_buffer[0];
		for (const auto iter_y : xrange(primary_height, std::integral_constant<unsigned, 0>(), xrange_descending()))
		{
			(void)iter_y;
			std::move(primary_src_iter, std::next(primary_src_iter, primary_width), primary_dst_iter);
			std::move(secondary_src_iter, std::next(secondary_src_iter, secondary_width), std::next(primary_dst_iter, primary_width));
			primary_src_iter += bm_w;
			primary_dst_iter += sum_box_width;
			secondary_src_iter += bm_w;
		}
		cockpitbuf.reset();
	}
#if DXX_USE_OGL
	ogl_freebmtexture(*bm);
	/* In the OpenGL build, copy the data pointer into the bitmap for
	 * use until the OpenGL texture is created.
	 */
	gr_init_bitmap(WinBoxOverlay.decoded_full_cockpit_image, bm_mode::linear, 0, 0, sum_box_width, primary_height, sum_box_width, inset_window_buffer.get());
#else
	/* In the SDL-only build, move the data pointer into the bitmap so
	 * that the underlying data buffer remains allocated.
	 */
	gr_init_main_bitmap(WinBoxOverlay.decoded_full_cockpit_image, bm_mode::linear, 0, 0, sum_box_width, primary_height, sum_box_width, std::move(inset_window_buffer));
#endif
	gr_set_transparent(WinBoxOverlay.decoded_full_cockpit_image, 1);
#if DXX_USE_OGL
	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
#endif
	WinBoxOverlay[0] = gr_create_sub_bitmap(WinBoxOverlay.decoded_full_cockpit_image, 0, 0, primary_width, primary_height);
	WinBoxOverlay[1] = gr_create_sub_bitmap(WinBoxOverlay.decoded_full_cockpit_image, primary_width, 0, secondary_width, primary_height);
#if DXX_USE_OGL
	/* The image has been copied to OpenGL as a texture.  The underlying
	 * main application memory will be freed when `inset_window_buffer`
	 * goes out of scope.
	 * Clear bm_data to avoid leaving a dangling pointer.
	 */
	WinBoxOverlay.decoded_full_cockpit_image.bm_data = nullptr;
	WinBoxOverlay[0]->bm_data = nullptr;
	WinBoxOverlay[1]->bm_data = nullptr;
#endif

	cur = bm->get_bitmap_data();
	cur_w = bm_w;
	cur_h = bm_h;
}
}

namespace dsx {
namespace {
static void draw_wbu_overlay(const hud_draw_context_hs_mr hudctx)
{
	auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
	const auto cockpit_idx{
#if DXX_BUILD_DESCENT == 2
		multires_gauge_graphic.is_hires()
		? cockpit_bitmap_index::hires_full_cockpit
		:
#endif
		cockpit_bitmap_index::full_cockpit
	};
	const auto cb{cockpit_bitmap[cockpit_idx]};
	PIGGY_PAGE_IN(cb);
	grs_bitmap *const bm = &GameBitmaps[cb];

	cockpit_decode_alpha(hudctx, bm);

	/* The code that rendered the inset windows drew simple square
	 * boxes, which partially overwrote the frame surrounding the inset
	 * windows in the cockpit graphic.  These calls reapply the
	 * overwritten frame, while leaving untouched the portion that was
	 * supposed to be overwritten.
	 */
	if (WinBoxOverlay[0])
		hud_bitblt(hudctx, PRIMARY_W_BOX_LEFT - 2, PRIMARY_W_BOX_TOP - 2, *WinBoxOverlay[0].get());
	if (WinBoxOverlay[1])
		hud_bitblt(hudctx, SECONDARY_W_BOX_LEFT - 2, SECONDARY_W_BOX_TOP - 2, *WinBoxOverlay[1].get());
}
}
}

void close_gauges()
{
	WinBoxOverlay = {};
}

namespace dsx {
void init_gauges()
{
	inset_window = {};
	old_laser_level	= {};
}
}

namespace {

static void draw_energy_bar(grs_canvas &canvas, const hud_draw_context_hs_mr hudctx, const int energy)
{
	auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
	const int not_energy = hudctx.xscale(multires_gauge_graphic.is_hires() ? (125 - (energy * 125) / 100) : (63 - (energy * 63) / 100));
	const double aplitscale = static_cast<double>(hudctx.xscale(65) / hudctx.yscale(8)) / (65 / 8); //scale amplitude of energy bar to current resolution aspect

	// Draw left energy bar
	hud_gauge_bitblt(hudctx, LEFT_ENERGY_GAUGE_X, LEFT_ENERGY_GAUGE_Y, GAUGE_ENERGY_LEFT);

	const auto color = BM_XRGB(0, 0, 0);

	if (energy < 100)
	{
		const auto xscale_energy_gauge_x = hudctx.xscale(LEFT_ENERGY_GAUGE_X);
		const auto xscale_energy_gauge_w = hudctx.xscale(LEFT_ENERGY_GAUGE_W);
		const auto xscale_energy_gauge_h2 = hudctx.xscale(LEFT_ENERGY_GAUGE_H - 2);
		const auto yscale_energy_gauge_y = hudctx.yscale(LEFT_ENERGY_GAUGE_Y);
		const auto yscale_energy_gauge_h = hudctx.yscale(LEFT_ENERGY_GAUGE_H);
		for (unsigned y = 0; y < yscale_energy_gauge_h; ++y)
		{
			const auto bound = xscale_energy_gauge_w - (y * aplitscale) / 3;
			const auto x1 = xscale_energy_gauge_h2 - y * aplitscale;
			const auto x2 = std::min(x1 + not_energy, bound);

			if (x2 > x1)
			{
				const auto ly = i2f(y + yscale_energy_gauge_y);
				gr_uline(canvas, i2f(x1 + xscale_energy_gauge_x), ly, i2f(x2 + xscale_energy_gauge_x), ly, color);
			}
		}
	}

	// Draw right energy bar
	hud_gauge_bitblt(hudctx, RIGHT_ENERGY_GAUGE_X, RIGHT_ENERGY_GAUGE_Y, GAUGE_ENERGY_RIGHT);

	if (energy < 100)
	{
		const auto xscale_energy_gauge_x = hudctx.xscale(RIGHT_ENERGY_GAUGE_X);
		const auto yscale_energy_gauge_y = hudctx.yscale(RIGHT_ENERGY_GAUGE_Y);
		const auto yscale_energy_gauge_h = hudctx.yscale(RIGHT_ENERGY_GAUGE_H);
		const auto xscale_right_energy = hudctx.xscale(RIGHT_ENERGY_GAUGE_W - RIGHT_ENERGY_GAUGE_H + 2);
		for (unsigned y = 0; y < yscale_energy_gauge_h; ++y)
		{
			const auto bound = (y * aplitscale) / 3;
			const auto x2 = xscale_right_energy + y * aplitscale;
			auto x1 = x2 - not_energy;

			if (x1 < bound)
				x1 = bound;

			if (x2 > x1)
			{
				const auto ly = i2f(y + yscale_energy_gauge_y);
				gr_uline(canvas, i2f(x1 + xscale_energy_gauge_x), ly, i2f(x2 + xscale_energy_gauge_x), ly, color);
			}
		}
	}
}

}

#if DXX_BUILD_DESCENT == 2
namespace dsx {
namespace {
static void draw_afterburner_bar(const hud_draw_context_hs_mr hudctx, const int afterburner)
{
	struct lr
	{
		uint8_t l, r;
	};
	static const std::array<lr, AFTERBURNER_GAUGE_H_L> afterburner_bar_table = {{
		{3, 11},
		{3, 11},
		{3, 11},
		{3, 11},
		{3, 11},
		{3, 11},
		{2, 11},
		{2, 10},
		{2, 10},
		{2, 10},
		{2, 10},
		{2, 10},
		{2, 10},
		{1, 10},
		{1, 10},
		{1, 10},
		{1, 9},
		{1, 9},
		{1, 9},
		{1, 9},
		{0, 9},
		{0, 9},
		{0, 8},
		{0, 8},
		{0, 8},
		{0, 8},
		{1, 8},
		{2, 8},
		{3, 8},
		{4, 8},
		{5, 8},
		{6, 7}
	}};
	static const std::array<lr, AFTERBURNER_GAUGE_H_H> afterburner_bar_table_hires = {{
		{5, 20},
		{5, 20},
		{5, 19},
		{5, 19},
		{5, 19},
		{5, 19},
		{4, 19},
		{4, 19},
		{4, 19},
		{4, 19},
		{4, 19},
		{4, 18},
		{4, 18},
		{4, 18},
		{4, 18},
		{3, 18},
		{3, 18},
		{3, 18},
		{3, 18},
		{3, 18},
		{3, 18},
		{3, 17},
		{3, 17},
		{2, 17},
		{2, 17},
		{2, 17},
		{2, 17},
		{2, 17},
		{2, 17},
		{2, 17},
		{2, 17},
		{2, 16},
		{2, 16},
		{1, 16},
		{1, 16},
		{1, 16},
		{1, 16},
		{1, 16},
		{1, 16},
		{1, 16},
		{1, 16},
		{1, 15},
		{1, 15},
		{1, 15},
		{0, 15},
		{0, 15},
		{0, 15},
		{0, 15},
		{0, 15},
		{0, 15},
		{0, 14},
		{0, 14},
		{0, 14},
		{1, 14},
		{2, 14},
		{3, 14},
		{4, 14},
		{5, 14},
		{6, 13},
		{7, 13},
		{8, 13},
		{9, 13},
		{10, 13},
		{11, 13},
		{12, 13}
	}};

	// Draw afterburner bar
	auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
	const auto afterburner_gauge_x{AFTERBURNER_GAUGE_X};
	const auto afterburner_gauge_y{AFTERBURNER_GAUGE_Y};
	hud_gauge_bitblt(hudctx, afterburner_gauge_x, afterburner_gauge_y, GAUGE_AFTERBURNER);
	const auto table_span{
		multires_gauge_graphic.is_hires()
			? std::span<const lr>(afterburner_bar_table_hires)
			: std::span<const lr>(afterburner_bar_table)
	};
	const unsigned not_afterburner = fixmul(f1_0 - afterburner, table_span.size());
	if (not_afterburner > table_span.size())
		return;
	const uint8_t color = BM_XRGB(0, 0, 0);
	const int base_top = hudctx.yscale(afterburner_gauge_y - 1);
	const int base_bottom = hudctx.yscale(afterburner_gauge_y);
	int y{0};
	for (auto &ab : table_span.first(not_afterburner))
	{
		const int left = hudctx.xscale(afterburner_gauge_x + ab.l);
		const int right = hudctx.xscale(afterburner_gauge_x + ab.r + 1);
		for (int i = hudctx.yscale(y), j = hudctx.yscale(++y); i < j; ++i)
		{
			gr_rect(hudctx.canvas, left, base_top + i, right, base_bottom + i, color);
		}
	}
}

}
}
#endif

namespace {

static void draw_shield_bar(const hud_draw_context_hs_mr hudctx, const int shield)
{
	int bm_num = shield>=100?9:(shield / 10);
	auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
	hud_gauge_bitblt(hudctx, SHIELD_GAUGE_X, SHIELD_GAUGE_Y, GAUGE_SHIELDS + 9 - bm_num);
}

static void show_cockpit_cloak_invul_timer(grs_canvas &canvas, const fix64 effect_end, const int y)
{
	char countdown[8];
	snprintf(countdown, sizeof(countdown), "%lu", static_cast<unsigned long>(effect_end / F1_0));
	gr_set_fontcolor(canvas, BM_XRGB(31, 31, 31), -1);
	const auto &&[ow, oh] = gr_get_string_size(*canvas.cv_font, countdown);
	const int x = grd_curscreen->get_screen_width() / (PlayerCfg.CockpitMode[1] == cockpit_mode_t::status_bar
		? 2.266
		: 1.951
	);
	gr_string(canvas, *canvas.cv_font, x - (ow / 2), y, countdown, ow, oh);
}

}

#define CLOAK_FADE_WAIT_TIME  0x400

namespace dsx {

namespace {

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)
{
	static fix cloak_fade_timer=0;
	static int8_t cloak_fade_value = GR_FADE_LEVELS - 1;

	if (cloak_state)
	{
		static int step = 0;
		const auto cloak_time = player_info.cloak_time;

		if (GameTime64 - cloak_time < F1_0)
		{
			step = -2;
		}
		else if (cloak_time + CLOAK_TIME_MAX - GameTime64 <= F1_0*3)
		{
			if (cloak_fade_value >= static_cast<signed>(GR_FADE_LEVELS-1))
			{
				step = -2;
			}
			else if (cloak_fade_value <= 0)
			{
				step = 2;
			}
		}
		else
		{
			step = 0;
			cloak_fade_value = 0;
		}

		cloak_fade_timer -= FrameTime;

		while (cloak_fade_timer < 0)
		{
			cloak_fade_timer += CLOAK_FADE_WAIT_TIME;
			cloak_fade_value += step;
		}

		if (cloak_fade_value > static_cast<signed>(GR_FADE_LEVELS-1))
			cloak_fade_value = (GR_FADE_LEVELS-1);
		if (cloak_fade_value <= 0)
			cloak_fade_value = 0;
	}
	else
	{
		cloak_fade_timer = 0;
		cloak_fade_value = GR_FADE_LEVELS-1;
	}

	const auto color{get_player_or_team_color(Netgame, Game_mode, Player_num)};
	const auto gauge_index{GAUGE_SHIPS + underlying_value(color)};
#if DXX_BUILD_DESCENT == 2
	auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
#endif
	PAGE_IN_GAUGE(gauge_index
#if DXX_BUILD_DESCENT == 2
		, hudctx.multires_gauge_graphic
#endif
	);
	auto &bm = GameBitmaps[GET_GAUGE_INDEX(gauge_index)];
	hud_bitblt(hudctx, x, y, bm);
	auto &canvas = hudctx.canvas;
	gr_settransblend(canvas, gr_fade_level{static_cast<uint8_t>(cloak_fade_value)}, gr_blend::normal);
	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);
	gr_settransblend(canvas, GR_FADE_OFF, gr_blend::normal);
        // Show Cloak Timer if enabled
	if (cloak_fade_value < GR_FADE_LEVELS/2 && show_cloak_invul_timer())
		show_cockpit_cloak_invul_timer(canvas, player_info.cloak_time + CLOAK_TIME_MAX - GameTime64, hudctx.yscale(y + (bm.bm_h / 2)));
}

}

}

#define INV_FRAME_TIME	(f1_0/10)		//how long for each frame

namespace dcx {

namespace {

static const char *get_gauge_width_string(const unsigned v)
{
	if (v > 199)
		return "200";
	return &"100"[(v > 99)
		? 0
		: (v > 9) ? 1 : 2
	];
}

}

}

namespace {

static void draw_numerical_display(const draw_numerical_display_draw_context hudctx, const int shield, const int energy)
{
	auto &canvas = hudctx.canvas;
#if !DXX_USE_OGL
	auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
	hud_gauge_bitblt(hudctx, NUMERICAL_GAUGE_X, NUMERICAL_GAUGE_Y, GAUGE_NUMERICAL);
#endif
	// cockpit is not 100% geometric so we need to divide shield and energy X position by 1.951 which should be most accurate
	// gr_get_string_size is used so we can get the numbers finally in the correct position with sw and ew
	const int xb = grd_curscreen->get_screen_width() / 1.951;
	auto &game_font = *GAME_FONT;
	const auto a = [&canvas, &game_font, xb](int v, int y) {
		const auto w = gr_get_string_size(game_font, get_gauge_width_string(v)).width;
		gr_printf(canvas, game_font, xb - (w / 2), y, "%d", v);
	};
	gr_set_fontcolor(canvas, BM_XRGB(14, 14, 23), -1);
	const auto screen_height = grd_curscreen->get_screen_height();
	a(shield, screen_height / 1.365);

	gr_set_fontcolor(canvas, BM_XRGB(25, 18, 6), -1);
	a(energy, screen_height / 1.5);
}

}

namespace dsx {
namespace {

void draw_keys_state::draw_all_cockpit_keys()
{
	auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
	draw_one_key(GAUGE_BLUE_KEY_X, GAUGE_BLUE_KEY_Y, GAUGE_BLUE_KEY, PLAYER_FLAGS_BLUE_KEY);
	draw_one_key(GAUGE_GOLD_KEY_X, GAUGE_GOLD_KEY_Y, GAUGE_GOLD_KEY, PLAYER_FLAGS_GOLD_KEY);
	draw_one_key(GAUGE_RED_KEY_X, GAUGE_RED_KEY_Y, GAUGE_RED_KEY, PLAYER_FLAGS_RED_KEY);
}

static void draw_weapon_info_sub(const hud_draw_context_hs_mr hudctx, const player_info &player_info, const weapon_id_type 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)
{
	//clear the window
	const uint8_t color = BM_XRGB(0, 0, 0);
	{
#if DXX_BUILD_DESCENT == 1
		constexpr unsigned bottom_bias = 1;
#elif DXX_BUILD_DESCENT == 2
		constexpr unsigned bottom_bias = 0;
#endif
		gr_rect(hudctx.canvas, hudctx.xscale(box->left), hudctx.yscale(box->top), hudctx.xscale(box->right), hudctx.yscale(box->bot + bottom_bias), color);
	}
	const auto picture = 
#if DXX_BUILD_DESCENT == 2
	// !SHAREWARE
		(Piggy_hamfile_version >= pig_hamfile_version::_3 && hudctx.multires_gauge_graphic.is_hires()) ?
			Weapon_info[info_index].hires_picture :
#endif
			Weapon_info[info_index].picture;
	PIGGY_PAGE_IN(picture);
	auto &bm = GameBitmaps[picture];
	hud_bitblt(hudctx, pic_x, pic_y, bm);

	if (PlayerCfg.HudMode == HudType::Standard)
	{
		auto &canvas = hudctx.canvas;
		gr_set_fontcolor(canvas, BM_XRGB(0, 20, 0), -1);

		gr_string(canvas, *canvas.cv_font, text_x, text_y, name);

		//	For laser, show level and quadness
#if DXX_BUILD_DESCENT == 1
		if (info_index == weapon_id_type::LASER_ID)
#elif DXX_BUILD_DESCENT == 2
		if (info_index == weapon_id_type::LASER_ID || info_index == weapon_id_type::SUPER_LASER_ID)
#endif
		{
			const auto &&line_spacing = LINE_SPACING(*canvas.cv_font, *GAME_FONT);
			gr_printf(canvas, *canvas.cv_font, text_x, text_y + line_spacing, "%s: %u", TXT_LVL, static_cast<unsigned>(player_info.laser_level) + 1);
			if (player_info.powerup_flags & PLAYER_FLAGS_QUAD_LASERS)
				gr_string(canvas, *canvas.cv_font, text_x, text_y + (line_spacing * 2), TXT_QUAD);
		}
	}
}

static void draw_primary_weapon_info(const hud_draw_context_hs_mr hudctx, const player_info &player_info, const primary_weapon_index weapon_num, const laser_level level)
{
#if DXX_BUILD_DESCENT == 1
	(void)level;
#endif
	int x,y;

	{
		const auto weapon_id = Primary_weapon_to_weapon_info[weapon_num];
		const auto info_index = 
#if DXX_BUILD_DESCENT == 2
			(weapon_id == weapon_id_type::LASER_ID && level > MAX_LASER_LEVEL)
			? weapon_id_type::SUPER_LASER_ID
			:
#endif
			weapon_id;

		const gauge_box *box;
		int pic_x, pic_y, text_x, text_y;
		auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
		auto &resbox = gauge_boxes[multires_gauge_graphic.hiresmode];
		auto &weaponbox = resbox[gauge_inset_window_view::primary];
		if (PlayerCfg.CockpitMode[1] == cockpit_mode_t::status_bar)
		{
			box = &weaponbox[gauge_hud_type::statusbar];
			pic_x = SB_PRIMARY_W_PIC_X;
			pic_y = SB_PRIMARY_W_PIC_Y;
			text_x = SB_PRIMARY_W_TEXT_X;
			text_y = SB_PRIMARY_W_TEXT_Y;
			x=SB_PRIMARY_AMMO_X;
			y=SB_PRIMARY_AMMO_Y;
		}
		else
		{
			box = &weaponbox[gauge_hud_type::cockpit];
			pic_x = PRIMARY_W_PIC_X;
			pic_y = PRIMARY_W_PIC_Y;
			text_x = PRIMARY_W_TEXT_X;
			text_y = PRIMARY_W_TEXT_Y;
			x=PRIMARY_AMMO_X;
			y=PRIMARY_AMMO_Y;
		}
		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));
		if (PlayerCfg.HudMode != HudType::Standard)
		{
#if DXX_BUILD_DESCENT == 2
			if (inset_window[gauge_inset_window_view::primary].user == weapon_box_user::weapon)
#endif
				hud_show_primary_weapons_mode(hudctx.canvas, player_info, 1, hudctx.xscale(x), hudctx.yscale(y));
		}
	}
}

static void draw_secondary_weapon_info(const hud_draw_context_hs_mr hudctx, const player_info &player_info, const secondary_weapon_index weapon_num)
{
	int x,y;

	{
		const auto info_index = Secondary_weapon_to_weapon_info[weapon_num];
		const gauge_box *box;
		int pic_x, pic_y, text_x, text_y;
		auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
		auto &resbox = gauge_boxes[multires_gauge_graphic.hiresmode];
		auto &weaponbox = resbox[gauge_inset_window_view::secondary];
		if (PlayerCfg.CockpitMode[1] == cockpit_mode_t::status_bar)
		{
			box = &weaponbox[gauge_hud_type::statusbar];
			pic_x = SB_SECONDARY_W_PIC_X;
			pic_y = SB_SECONDARY_W_PIC_Y;
			text_x = SB_SECONDARY_W_TEXT_X;
			text_y = SB_SECONDARY_W_TEXT_Y;
			x=SB_SECONDARY_AMMO_X;
			y=SB_SECONDARY_AMMO_Y;
		}
		else
		{
			box = &weaponbox[gauge_hud_type::cockpit];
			pic_x = SECONDARY_W_PIC_X;
			pic_y = SECONDARY_W_PIC_Y;
			text_x = SECONDARY_W_TEXT_X;
			text_y = SECONDARY_W_TEXT_Y;
			x=SECONDARY_AMMO_X;
			y=SECONDARY_AMMO_Y;
		}
		const char *weapon_name;
		switch (weapon_num)
		{
			default:	// unreachable
			case secondary_weapon_index::concussion:	// reachable
				weapon_name = TXT_W_C_MISSILE_S;
				break;
			case secondary_weapon_index::homing:
				weapon_name = TXT_W_H_MISSILE_S;
				break;
			case secondary_weapon_index::proximity:
				weapon_name = TXT_W_P_BOMB_S;
				break;
			case secondary_weapon_index::smart:
				weapon_name = TXT_W_S_MISSILE_S;
				break;
			case secondary_weapon_index::mega:
				weapon_name = TXT_W_M_MISSILE_S;
				break;
#if DXX_BUILD_DESCENT == 2
			case secondary_weapon_index::flash:
				weapon_name = TXT_W_SMISSILE1_S;
				break;
			case secondary_weapon_index::guided:
				weapon_name = TXT_W_SMISSILE2_S;
				break;
			case secondary_weapon_index::smart_mine:
				weapon_name = TXT_W_SMISSILE3_S;
				break;
			case secondary_weapon_index::mercury:
				weapon_name = TXT_W_SMISSILE4_S;
				break;
			case secondary_weapon_index::earthshaker:
				weapon_name = TXT_W_SMISSILE5_S;
				break;
#endif
		}
		draw_weapon_info_sub(hudctx, player_info, info_index, box, pic_x, pic_y, weapon_name, hudctx.xscale(text_x), hudctx.yscale(text_y));
		if (PlayerCfg.HudMode != HudType::Standard)
		{
#if DXX_BUILD_DESCENT == 2
			if (inset_window[gauge_inset_window_view::secondary].user == weapon_box_user::weapon)
#endif
				hud_show_secondary_weapons_mode(hudctx.canvas, player_info, 1, hudctx.xscale(x), hudctx.yscale(y));
		}
	}
}

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)
{
	if (wt == gauge_inset_window_view::primary)
		draw_primary_weapon_info(hudctx, player_info, weapon_num.primary, laser_level);
	else
		draw_secondary_weapon_info(hudctx, player_info, weapon_num.secondary);
}

}
}

namespace {

static void draw_ammo_info(grs_canvas &canvas, const unsigned x, const unsigned y, const unsigned ammo_count)
{
	if (PlayerCfg.HudMode == HudType::Standard)
	{
		gr_set_fontcolor(canvas, BM_XRGB(20, 0, 0), -1);
		gr_printf(canvas, *canvas.cv_font, x, y, "%03u", ammo_count);
	}
}

static void draw_secondary_ammo_info(const hud_draw_context_hs_mr hudctx, const unsigned ammo_count)
{
	int x, y;
	auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
	if (PlayerCfg.CockpitMode[1] == cockpit_mode_t::status_bar)
		x = SB_SECONDARY_AMMO_X, y = SB_SECONDARY_AMMO_Y;
	else
		x = SECONDARY_AMMO_X, y = SECONDARY_AMMO_Y;
	draw_ammo_info(hudctx.canvas, hudctx.xscale(x), hudctx.yscale(y), ammo_count);
}

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)
{
	auto &canvas = hudctx.canvas;
	gr_set_curfont(canvas, *GAME_FONT);

	const auto laser_level_changed = (wt == gauge_inset_window_view::primary && weapon_num.primary == primary_weapon_index::laser && (player_info.laser_level != old_laser_level));

	auto &inset = inset_window[wt];
	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)
	{
		inset.box_state = weapon_box_state::fading_out;
		inset.fade_value = i2f(GR_FADE_LEVELS - 1);
	}

	const local_multires_gauge_graphic multires_gauge_graphic{};
	if (inset.old_weapon == weapon_index{})
	{
		draw_weapon_info(hudctx, player_info, weapon_num, player_info.laser_level, wt);
		inset.old_weapon = weapon_num;
		inset.box_state = weapon_box_state::set;
	}

	if (inset.box_state == weapon_box_state::fading_out)
	{
		draw_weapon_info(hudctx, player_info, inset.old_weapon, old_laser_level, wt);
		inset.fade_value -= FrameTime * FADE_SCALE;
		if (inset.fade_value <= 0)
		{
			inset.fade_value = 0;
			inset.box_state = weapon_box_state::fading_in;
			inset.old_weapon = weapon_num;
			old_laser_level = player_info.laser_level;
		}
	}
	else if (inset.box_state == weapon_box_state::fading_in)
	{
		if (weapon_num != inset.old_weapon) {
			inset.box_state = weapon_box_state::fading_out;
		}
		else {
			draw_weapon_info(hudctx, player_info, weapon_num, player_info.laser_level, wt);
			inset.fade_value += FrameTime * FADE_SCALE;
			if (inset.fade_value >= i2f(GR_FADE_LEVELS - 1))
			{
				inset.old_weapon = {};
				inset.box_state = weapon_box_state::set;
			}
		}
	} else
	{
		draw_weapon_info(hudctx, player_info, weapon_num, player_info.laser_level, wt);
		inset.old_weapon = weapon_num;
		old_laser_level = player_info.laser_level;
	}

	if (inset.box_state != weapon_box_state::set)		//fade gauge
	{
		int fade_value = f2i(inset_window[wt].fade_value);

		gr_settransblend(canvas, static_cast<gr_fade_level>(fade_value), gr_blend::normal);
		auto &resbox = gauge_boxes[multires_gauge_graphic.hiresmode];
		auto &weaponbox = resbox[wt];
		auto &g = weaponbox[(PlayerCfg.CockpitMode[1] == cockpit_mode_t::status_bar) ? gauge_hud_type::statusbar : gauge_hud_type::cockpit];
		auto &canvas = hudctx.canvas;
		gr_rect(canvas, hudctx.xscale(g.left), hudctx.yscale(g.top), hudctx.xscale(g.right), hudctx.yscale(g.bot), 0);

		gr_settransblend(canvas, GR_FADE_OFF, gr_blend::normal);
	}
}

}

namespace dsx {
namespace {
#if DXX_BUILD_DESCENT == 2
static void draw_static(const d_vclip_array &Vclip, const hud_draw_context_hs_mr hudctx, const gauge_inset_window_view win)
{
	const vclip *const vc = &Vclip[vclip_index::monitor_static];
	int framenum;
	auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
#if !DXX_USE_OGL
	int x,y;
#endif

	auto &time_static_played = inset_window[win].time_static_played;
	time_static_played += FrameTime;
	if (time_static_played >= vc->play_time) {
		inset_window[win].user = weapon_box_user::weapon;
		return;
	}

	framenum = time_static_played * vc->num_frames / vc->play_time;

	PIGGY_PAGE_IN(vc->frames[framenum]);

	auto &bmp = GameBitmaps[vc->frames[framenum]];
	auto &resbox = gauge_boxes[multires_gauge_graphic.hiresmode];
	auto &weaponbox = resbox[win];
	auto &box = weaponbox[(PlayerCfg.CockpitMode[1] == cockpit_mode_t::status_bar) ? gauge_hud_type::statusbar : gauge_hud_type::cockpit];
#if !DXX_USE_OGL
	for (x = box.left; x < box.right; x += bmp.bm_w)
		for (y = box.top; y < box.bot; y += bmp.bm_h)
			gr_bitmap(hudctx.canvas, x, y, bmp);
#else
	if (multires_gauge_graphic.is_hires())
	{
		const auto scaled_left = hudctx.xscale(box.left);
		const auto scaled_top = hudctx.yscale(box.top);
		const auto scaled_bottom = hudctx.yscale(box.bot - bmp.bm_h);
		hud_bitblt_scaled_xy(hudctx, scaled_left, scaled_top, bmp);
		hud_bitblt_scaled_xy(hudctx, scaled_left, scaled_bottom, bmp);
		const auto scaled_right = hudctx.xscale(box.right - bmp.bm_w);
		hud_bitblt_scaled_xy(hudctx, scaled_right, scaled_top, bmp);
		hud_bitblt_scaled_xy(hudctx, scaled_right, scaled_bottom, bmp);
	}
#endif
}
#endif

static void draw_weapon_box0(const hud_draw_context_hs_mr hudctx, const player_info &player_info)
{
#if DXX_BUILD_DESCENT == 2
	const auto user = inset_window[gauge_inset_window_view::primary].user;
	if (user == weapon_box_user::weapon)
#endif
	{
		const auto Primary_weapon = player_info.Primary_weapon;
		draw_weapon_box(hudctx, player_info, Primary_weapon.get_active(), gauge_inset_window_view::primary);

		if (inset_window[gauge_inset_window_view::primary].box_state == weapon_box_state::set)
		{
			unsigned nd_ammo;
			unsigned ammo_count;
			if (weapon_index_uses_vulcan_ammo(Primary_weapon))
			{
				nd_ammo = player_info.vulcan_ammo;
				ammo_count = vulcan_ammo_scale(nd_ammo);
			}
#if DXX_BUILD_DESCENT == 2
			else if (Primary_weapon == primary_weapon_index::omega)
			{
				auto &Omega_charge = player_info.Omega_charge;
				nd_ammo = Omega_charge;
				ammo_count = Omega_charge * 100/MAX_OMEGA_CHARGE;
			}
#endif
			else
				return;
			if (Newdemo_state == ND_STATE_RECORDING)
				newdemo_record_primary_ammo(nd_ammo);
			draw_primary_ammo_info(hudctx, ammo_count);
		}
	}
#if DXX_BUILD_DESCENT == 2
	else if (user == weapon_box_user::post_missile_static)
		draw_static(Vclip, hudctx, gauge_inset_window_view::primary);
#endif
}

static void draw_weapon_box1(const hud_draw_context_hs_mr hudctx, const player_info &player_info)
{
#if DXX_BUILD_DESCENT == 2
	if (inset_window[gauge_inset_window_view::secondary].user == weapon_box_user::weapon)
#endif
	{
		auto &Secondary_weapon = player_info.Secondary_weapon;
		draw_weapon_box(hudctx, player_info, Secondary_weapon.get_active(), gauge_inset_window_view::secondary);
		if (inset_window[gauge_inset_window_view::secondary].box_state == weapon_box_state::set)
		{
			const auto ammo = player_info.secondary_ammo[Secondary_weapon];
			if (Newdemo_state == ND_STATE_RECORDING)
				newdemo_record_secondary_ammo(ammo);
			draw_secondary_ammo_info(hudctx, ammo);
		}
	}
#if DXX_BUILD_DESCENT == 2
	else if (inset_window[gauge_inset_window_view::secondary].user == weapon_box_user::post_missile_static)
		draw_static(Vclip, hudctx, gauge_inset_window_view::secondary);
#endif
}

static void draw_weapon_boxes(const hud_draw_context_hs_mr hudctx, const player_info &player_info)
{
	draw_weapon_box0(hudctx, player_info);
	draw_weapon_box1(hudctx, player_info);
}

static void sb_draw_energy_bar(const hud_draw_context_hs_mr hudctx, const unsigned energy)
{

	auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
	hud_gauge_bitblt(hudctx, SB_ENERGY_GAUGE_X, SB_ENERGY_GAUGE_Y, SB_GAUGE_ENERGY);

	auto &canvas = hudctx.canvas;
	if (energy <= 100)
	{
		const auto color{0};
		const int erase_x0 = i2f(hudctx.xscale(SB_ENERGY_GAUGE_X));
		const int erase_x1 = i2f(hudctx.xscale(SB_ENERGY_GAUGE_X + SB_ENERGY_GAUGE_W));
		const int erase_y_base = hudctx.yscale(SB_ENERGY_GAUGE_Y);
		for (int i = hudctx.yscale((100 - energy) * SB_ENERGY_GAUGE_H / 100); i-- > 0;)
		{
		const int erase_y = i2f(erase_y_base + i);
		gr_uline(canvas, erase_x0, erase_y, erase_x1, erase_y, color);
		}
	}

	//draw numbers
	gr_set_fontcolor(canvas, BM_XRGB(25, 18, 6), -1);
	const auto ew = gr_get_string_size(*canvas.cv_font, get_gauge_width_string(energy)).width;
#if DXX_BUILD_DESCENT == 1
	unsigned y = SB_ENERGY_NUM_Y;
#elif DXX_BUILD_DESCENT == 2
	unsigned y = SB_ENERGY_GAUGE_Y + SB_ENERGY_GAUGE_H - GAME_FONT->ft_h - (GAME_FONT->ft_h / 4);
#endif
	gr_printf(canvas, *canvas.cv_font, (grd_curscreen->get_screen_width() / 3) - (ew / 2), hudctx.yscale(y), "%d", energy);
}

#if DXX_BUILD_DESCENT == 2
static void sb_draw_afterburner(const hud_draw_context_hs_mr hudctx, const player_info &player_info)
{
	auto &ab_str = "AB";

	auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
	hud_gauge_bitblt(hudctx, SB_AFTERBURNER_GAUGE_X, SB_AFTERBURNER_GAUGE_Y, SB_GAUGE_AFTERBURNER);

	const auto color{0};
	const int erase_x0 = i2f(hudctx.xscale(SB_AFTERBURNER_GAUGE_X));
	const int erase_x1 = i2f(hudctx.xscale(SB_AFTERBURNER_GAUGE_X + (SB_AFTERBURNER_GAUGE_W)));
	const int erase_y_base = hudctx.yscale(SB_AFTERBURNER_GAUGE_Y);
	for (int i = hudctx.yscale(fixmul((f1_0 - Afterburner_charge), SB_AFTERBURNER_GAUGE_H)); i-- > 0;)
	{
		const int erase_y = i2f(erase_y_base + i);
		gr_uline(hudctx.canvas, erase_x0, erase_y, erase_x1, erase_y, color);
	}

	//draw legend
	unsigned r, g, b;
	if (player_info.powerup_flags & PLAYER_FLAGS_AFTERBURNER)
		r = 90, g = b = 0;
	else
		r = g = b = 24;
	auto &canvas = hudctx.canvas;
	gr_set_fontcolor(canvas, gr_find_closest_color(r, g, b), -1);

	const auto &&[w, h] = gr_get_string_size(*canvas.cv_font, ab_str);
	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);
}
#endif

}
}

namespace {

static void sb_draw_shield_num(const hud_draw_context_hs_mr hudctx, const unsigned shield)
{
	//draw numbers
	auto &canvas = hudctx.canvas;
	gr_set_fontcolor(canvas, BM_XRGB(14, 14, 23), -1);

	auto &game_font = *GAME_FONT;
	const auto sw = gr_get_string_size(game_font, get_gauge_width_string(shield)).width;
	auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
	gr_printf(canvas, game_font, (grd_curscreen->get_screen_width() / 2.266) - (sw / 2), hudctx.yscale(SB_SHIELD_NUM_Y), "%d", shield);
}

static void sb_draw_shield_bar(const hud_draw_context_hs_mr hudctx, const int shield)
{
	int bm_num = shield>=100?9:(shield / 10);
	auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
	hud_gauge_bitblt(hudctx, SB_SHIELD_GAUGE_X, SB_SHIELD_GAUGE_Y, GAUGE_SHIELDS+9-bm_num);
}

}

namespace dsx {
namespace {

void draw_keys_state::draw_all_statusbar_keys()
{
	auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
	draw_one_key(SB_GAUGE_KEYS_X, SB_GAUGE_BLUE_KEY_Y, SB_GAUGE_BLUE_KEY, PLAYER_FLAGS_BLUE_KEY);
	draw_one_key(SB_GAUGE_KEYS_X, SB_GAUGE_GOLD_KEY_Y, SB_GAUGE_GOLD_KEY, PLAYER_FLAGS_GOLD_KEY);
	draw_one_key(SB_GAUGE_KEYS_X, SB_GAUGE_RED_KEY_Y, SB_GAUGE_RED_KEY, PLAYER_FLAGS_RED_KEY);
}

//	Draws invulnerable ship, or maybe the flashing ship, depending on invulnerability time left.
static void draw_invulnerable_ship(const hud_draw_context_hs_mr hudctx, const object &plrobj)
{
	auto &player_info = plrobj.ctype.player_info;
	const auto cmmode = PlayerCfg.CockpitMode[1];
	const auto t = player_info.invulnerable_time;
	if (t + INVULNERABLE_TIME_MAX - GameTime64 > F1_0*4 || GameTime64 & 0x8000)
	{
		static fix time;
		auto ltime = time + FrameTime;
		const auto old_invulnerable_frame{invulnerable_frame};
		while (ltime > INV_FRAME_TIME)
		{
			ltime -= INV_FRAME_TIME;
			if (++invulnerable_frame == N_INVULNERABLE_FRAMES)
				invulnerable_frame=0;
		}
		time = ltime;
		unsigned x, y;
		auto &multires_gauge_graphic = hudctx.multires_gauge_graphic;
		if (cmmode == cockpit_mode_t::status_bar)
		{
			x = SB_SHIELD_GAUGE_X;
			y = SB_SHIELD_GAUGE_Y;
		}
		else
		{
			x = SHIELD_GAUGE_X;
			y = SHIELD_GAUGE_Y;
		}
		hud_gauge_bitblt(hudctx, x, y, GAUGE_INVULNERABLE + old_invulnerable_frame);

                // Show Invulnerability Timer if enabled
		if (show_cloak_invul_timer())
			show_cockpit_cloak_invul_timer(hudctx.canvas, t + INVULNERABLE_TIME_MAX - GameTime64, hudctx.yscale(y));
	}
	else
	{
		const auto shields_ir = f2ir(plrobj.shields);
		if (cmmode == cockpit_mode_t::status_bar)
			sb_draw_shield_bar(hudctx, shields_ir);
		else
			draw_shield_bar(hudctx, shields_ir);
	}
}

}
}

constexpr rgb_array_t player_rgb_normal{{
	/* These colors are mentioned by approximate name as members of
	 * `player_ship_color`.
	 */
							{15,15,23},
							{27,0,0},
							{0,23,0},
							{30,11,31},
							{31,16,0},
							{24,17,6},
							{14,21,12},
							{29,29,0},
}};

namespace {

struct xy {
	sbyte x, y;
};

//offsets for reticle parts: high-big  high-sml  low-big  low-sml
const std::array<xy, 4> cross_offsets{{
	{-8,-5},
	{-4,-2},
	{-4,-2},
	{-2,-1}
}},
	primary_offsets{{
	{-30,14},
	{-16,6},
	{-15,6},
	{-8, 2}
}},
	secondary_offsets{{
	{-24,2},
	{-12,0},
	{-12,1},
	{-6,-2}
}};

}

//draw the reticle
namespace dsx {
void show_reticle(grs_canvas &canvas, const player_info &player_info, enum reticle_type reticle_type, int secondary_display)
{
	int x,y,size;
	int laser_ready,missile_ready;
	int cross_bm_num,primary_bm_num,secondary_bm_num;
	int gauge_index;

#if DXX_BUILD_DESCENT == 2
	if (Newdemo_state==ND_STATE_PLAYBACK && Viewer->type != object_type::OBJ_PLAYER)
		 return;
#endif

	x = canvas.cv_bitmap.bm_w/2;
	y = canvas.cv_bitmap.bm_h/2;
	size = (canvas.cv_bitmap.bm_h / (32-(PlayerCfg.ReticleSize*4)));

	laser_ready = allowed_to_fire_laser(player_info);

	missile_ready = allowed_to_fire_missile(player_info);
	auto &Primary_weapon = player_info.Primary_weapon;
	primary_bm_num = (laser_ready && has_all(player_has_primary_weapon(player_info, Primary_weapon)));
	auto &Secondary_weapon = player_info.Secondary_weapon;
	secondary_bm_num = (missile_ready && has_all(player_has_secondary_weapon(player_info, Secondary_weapon)));

	if (primary_bm_num && Primary_weapon == primary_weapon_index::laser && (player_info.powerup_flags & PLAYER_FLAGS_QUAD_LASERS))
		primary_bm_num++;

	const auto opt_secondary_weapon_num{Secondary_weapon_to_gun_num.valid_index(Secondary_weapon)};
	if (!opt_secondary_weapon_num) [[unlikely]]
		return;
	if (Secondary_weapon_to_gun_num[*opt_secondary_weapon_num] == player_gun_number::_7)
		secondary_bm_num += 3;		//now value is 0,1 or 3,4
	else if (secondary_bm_num && !(player_info.missile_gun & 1))
			secondary_bm_num++;

	cross_bm_num = ((primary_bm_num > 0) || (secondary_bm_num > 0));

	Assert(primary_bm_num <= 2);
	Assert(secondary_bm_num <= 4);
	Assert(cross_bm_num <= 1);

	const auto color = BM_XRGB(PlayerCfg.ReticleRGBA[0],PlayerCfg.ReticleRGBA[1],PlayerCfg.ReticleRGBA[2]);
	gr_settransblend(canvas, static_cast<gr_fade_level>(PlayerCfg.ReticleRGBA[3]), gr_blend::normal);

	[&]{
		int x0, x1, y0, y1;
	switch (reticle_type)
	{
		case reticle_type::classic:
		{
			const local_multires_gauge_graphic multires_gauge_graphic{};
			const hud_draw_context_hs_mr hudctx(canvas, grd_curscreen->get_screen_width(), grd_curscreen->get_screen_height(), multires_gauge_graphic);
			const auto use_hires_reticle = multires_gauge_graphic.is_hires();
			const unsigned ofs = (use_hires_reticle ? 0 : 2);
			gauge_index = RETICLE_CROSS + cross_bm_num;
			PAGE_IN_GAUGE(gauge_index
#if DXX_BUILD_DESCENT == 2
				, multires_gauge_graphic
#endif
			);
			auto &cross = GameBitmaps[GET_GAUGE_INDEX(gauge_index)];
			const auto &&hud_scale_ar = HUD_SCALE_AR(hudctx.xscale, hudctx.yscale);
			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);

			gauge_index = RETICLE_PRIMARY + primary_bm_num;
			PAGE_IN_GAUGE(gauge_index
#if DXX_BUILD_DESCENT == 2
				, multires_gauge_graphic
#endif
			);
			auto &primary = GameBitmaps[GET_GAUGE_INDEX(gauge_index)];
			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);

			gauge_index = RETICLE_SECONDARY + secondary_bm_num;
			PAGE_IN_GAUGE(gauge_index
#if DXX_BUILD_DESCENT == 2
				, multires_gauge_graphic
#endif
			);
			auto &secondary = GameBitmaps[GET_GAUGE_INDEX(gauge_index)];
			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);
			return;
		}
		case reticle_type::classic_reboot:
#if DXX_USE_OGL
			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);
#endif
			return;
		case reticle_type::x:
			{
			gr_uline(canvas, i2f(x-(size/2)), i2f(y-(size/2)), i2f(x-(size/5)), i2f(y-(size/5)), color); // top-left
			gr_uline(canvas, i2f(x+(size/2)), i2f(y-(size/2)), i2f(x+(size/5)), i2f(y-(size/5)), color); // top-right
			gr_uline(canvas, i2f(x-(size/2)), i2f(y+(size/2)), i2f(x-(size/5)), i2f(y+(size/5)), color); // bottom-left
			gr_uline(canvas, i2f(x+(size/2)), i2f(y+(size/2)), i2f(x+(size/5)), i2f(y+(size/5)), color); // bottom-right
			if (secondary_display && secondary_bm_num == 1)
				x0 = i2f(x-(size/2)-(size/5)), y0 = i2f(y-(size/2)), x1 = i2f(x-(size/5)-(size/5)), y1 = i2f(y-(size/5));
			else if (secondary_display && secondary_bm_num == 2)
				x0 = i2f(x+(size/2)+(size/5)), y0 = i2f(y-(size/2)), x1 = i2f(x+(size/5)+(size/5)), y1 = i2f(y-(size/5));
			else if (secondary_display && secondary_bm_num == 4)
				x0 = i2f(x+(size/2)), y0 = i2f(y+(size/2)+(size/5)), x1 = i2f(x+(size/5)), y1 = i2f(y+(size/5)+(size/5));
			else
				return;
			}
			break;
		case reticle_type::dot:
			{
				gr_disk(canvas, i2f(x), i2f(y), i2f(size/5), color);
			if (secondary_display && secondary_bm_num == 1)
				x0 = i2f(x-(size/2)-(size/5)), y0 = i2f(y-(size/2)), x1 = i2f(x-(size/5)-(size/5)), y1 = i2f(y-(size/5));
			else if (secondary_display && secondary_bm_num == 2)
				x0 = i2f(x+(size/2)+(size/5)), y0 = i2f(y-(size/2)), x1 = i2f(x+(size/5)+(size/5)), y1 = i2f(y-(size/5));
			else if (secondary_display && secondary_bm_num == 4)
				x0 = i2f(x), y0 = i2f(y+(size/2)+(size/5)), x1 = i2f(x), y1 = i2f(y+(size/5)+(size/5));
			else
				return;
			}
			break;
		case reticle_type::circle:
			{
				gr_ucircle(canvas, i2f(x), i2f(y), i2f(size/4), color);
			if (secondary_display && secondary_bm_num == 1)
				x0 = i2f(x-(size/2)-(size/5)), y0 = i2f(y-(size/2)), x1 = i2f(x-(size/5)-(size/5)), y1 = i2f(y-(size/5));
			else if (secondary_display && secondary_bm_num == 2)
				x0 = i2f(x+(size/2)+(size/5)), y0 = i2f(y-(size/2)), x1 = i2f(x+(size/5)+(size/5)), y1 = i2f(y-(size/5));
			else if (secondary_display && secondary_bm_num == 4)
				x0 = i2f(x), y0 = i2f(y+(size/2)+(size/5)), x1 = i2f(x), y1 = i2f(y+(size/5)+(size/5));
			else
				return;
			}
			break;
		case reticle_type::cross_v1:
			{
			gr_uline(canvas, i2f(x),i2f(y-(size/2)),i2f(x),i2f(y+(size/2)+1), color); // horiz
			gr_uline(canvas, i2f(x-(size/2)),i2f(y),i2f(x+(size/2)+1),i2f(y), color); // vert
			if (secondary_display && secondary_bm_num == 1)
				x0 = i2f(x-(size/2)), y0 = i2f(y-(size/2)), x1 = i2f(x-(size/5)), y1 = i2f(y-(size/5));
			else if (secondary_display && secondary_bm_num == 2)
				x0 = i2f(x+(size/2)), y0 = i2f(y-(size/2)), x1 = i2f(x+(size/5)), y1 = i2f(y-(size/5));
			else if (secondary_display && secondary_bm_num == 4)
				x0 = i2f(x-(size/2)), y0 = i2f(y+(size/2)), x1 = i2f(x-(size/5)), y1 = i2f(y+(size/5));
			else
				return;
			}
			break;
		case reticle_type::cross_v2:
			{
			gr_uline(canvas, i2f(x), i2f(y-(size/2)), i2f(x), i2f(y-(size/6)), color); // vert-top
			gr_uline(canvas, i2f(x), i2f(y+(size/2)), i2f(x), i2f(y+(size/6)), color); // vert-bottom
			gr_uline(canvas, i2f(x-(size/2)), i2f(y), i2f(x-(size/6)), i2f(y), color); // horiz-left
			gr_uline(canvas, i2f(x+(size/2)), i2f(y), i2f(x+(size/6)), i2f(y), color); // horiz-right
			if (secondary_display && secondary_bm_num == 1)
				x0 = i2f(x-(size/2)), y0 = i2f(y-(size/2)), x1 = i2f(x-(size/5)), y1 = i2f(y-(size/5));
			else if (secondary_display && secondary_bm_num == 2)
				x0 = i2f(x+(size/2)), y0 = i2f(y-(size/2)), x1 = i2f(x+(size/5)), y1 = i2f(y-(size/5));
			else if (secondary_display && secondary_bm_num == 4)
				x0 = i2f(x-(size/2)), y0 = i2f(y+(size/2)), x1 = i2f(x-(size/5)), y1 = i2f(y+(size/5));
			else
				return;
			}
			break;
		case reticle_type::angle:
			{
			gr_uline(canvas, i2f(x),i2f(y),i2f(x),i2f(y+(size/2)), color); // vert
			gr_uline(canvas, i2f(x),i2f(y),i2f(x+(size/2)),i2f(y), color); // horiz
			if (secondary_display && secondary_bm_num == 1)
				x0 = i2f(x-(size/2)), y0 = i2f(y-(size/2)), x1 = i2f(x-(size/5)), y1 = i2f(y-(size/5));
			else if (secondary_display && secondary_bm_num == 2)
				x0 = i2f(x+(size/2)), y0 = i2f(y-(size/2)), x1 = i2f(x+(size/5)), y1 = i2f(y-(size/5));
			else if (secondary_display && secondary_bm_num == 4)
				x0 = i2f(x-(size/2)), y0 = i2f(y+(size/2)), x1 = i2f(x-(size/5)), y1 = i2f(y+(size/5));
			else
				return;
			}
			break;
		case reticle_type::none:
		default:
			return;
	}
	gr_uline(canvas, x0, y0, x1, y1, color);
	}();
	gr_settransblend(canvas, GR_FADE_OFF, gr_blend::normal);
}
}

namespace dcx {

void show_mousefs_indicator(grs_canvas &canvas, int mx, int my, int mz, int x, int y, int size)
{
	int axscale = (MOUSEFS_DELTA_RANGE*2)/size, xaxpos = x+(mx/axscale), yaxpos = y+(my/axscale), zaxpos = y+(mz/axscale);

	gr_settransblend(canvas, static_cast<gr_fade_level>(PlayerCfg.ReticleRGBA[3]), gr_blend::normal);
	auto &rgba = PlayerCfg.ReticleRGBA;
	const auto color = BM_XRGB(rgba[0], rgba[1], rgba[2]);
	gr_uline(canvas, i2f(xaxpos), i2f(y-(size/2)), i2f(xaxpos), i2f(y-(size/4)), color);
	gr_uline(canvas, i2f(xaxpos), i2f(y+(size/2)), i2f(xaxpos), i2f(y+(size/4)), color);
	gr_uline(canvas, i2f(x-(size/2)), i2f(yaxpos), i2f(x-(size/4)), i2f(yaxpos), color);
	gr_uline(canvas, i2f(x+(size/2)), i2f(yaxpos), i2f(x+(size/4)), i2f(yaxpos), color);
	const local_multires_gauge_graphic multires_gauge_graphic{};
	auto &&hud_scale_ar = HUD_SCALE_AR(grd_curscreen->get_screen_width(), grd_curscreen->get_screen_height(), multires_gauge_graphic);
	auto &&hud_scale_ar2 = hud_scale_ar(2);
	gr_uline(canvas, i2f(x + (size / 2) + hud_scale_ar2), i2f(y), i2f(x + (size / 2) + hud_scale_ar2), i2f(zaxpos), color);
	gr_settransblend(canvas, GR_FADE_OFF, gr_blend::normal);
}

}

namespace dsx {
namespace {

static void hud_show_kill_list(fvcobjptr &vcobjptr, grs_canvas &canvas, const game_mode_flags Game_mode)
{
	playernum_array_t player_list;
	int n_left,x0,x1,x2,y,save_y;

	if (Show_kill_list_timer > 0)
	{
		Show_kill_list_timer -= FrameTime;
		if (Show_kill_list_timer < 0)
			Show_kill_list = show_kill_list_mode::None;
	}

	const playernum_t n_players = (Show_kill_list == show_kill_list_mode::team_kills)
		? 2
		: multi_get_kill_list(player_list);

	if (n_players <= 4)
		n_left = n_players;
	else
		n_left = (n_players+1)/2;

	const auto &&fspacx = FSPACX();
	const auto &&fspacx43 = fspacx(43);

	x1 = fspacx43;

	const auto is_multiplayer_cooperative{+(Game_mode & GM_MULTI_COOP)};
	if (is_multiplayer_cooperative)
		x1 = fspacx(31);

	auto &game_font = *GAME_FONT;
	const auto &&line_spacing = LINE_SPACING(game_font, game_font);
	save_y = y = canvas.cv_bitmap.bm_h - n_left * line_spacing;

	if (PlayerCfg.CockpitMode[1] == cockpit_mode_t::full_cockpit)
	{
		save_y = y -= fspacx(6);
		if (is_multiplayer_cooperative)
			x1 = fspacx(33);
	}

	const auto bm_w = canvas.cv_bitmap.bm_w;
	const auto &&bmw_x0_cockpit = bm_w - fspacx(PlayerCfg.CockpitMode[1] == cockpit_mode_t::full_cockpit ? 53 : 60);
	// Right edge of name, change this for width problems
	const auto &&bmw_x1_multi = bm_w - fspacx(is_multiplayer_cooperative ? 27 : 15);
	const auto &&fspacx1 = fspacx(1);
	const auto &&fspacx2 = fspacx(2);
	const auto &&fspacx18 = fspacx(18);
        const auto &&fspacx35 = fspacx(35);
        const auto &&fspacx64 = fspacx(64);
	x0 = fspacx1;
	for (const uint8_t i : xrange(n_players))
	{
		playernum_t player_num;
		callsign_t name;

		if (i>=n_left) {
			x0 = bmw_x0_cockpit;
			x1 = bmw_x1_multi;
                        if (PlayerCfg.MultiPingHud)
                        {
                                x0 -= fspacx35;
                                x1 -= fspacx35;
                        }
			if (i==n_left)
				y = save_y;

			if (Netgame.KillGoal || Netgame.PlayTimeAllowed.count())
				x1 -= fspacx18;
		}
		else  if (Netgame.KillGoal || Netgame.PlayTimeAllowed.count())
		{
			x1 = fspacx43;
			x1 -= fspacx18;
		}

		if (Show_kill_list == show_kill_list_mode::team_kills)
			player_num = i;
		else
			player_num = player_list[i];
		auto &p = *vcplayerptr(player_num);

		color_t fontcolor;
		rgb color;
		if (Show_kill_list == show_kill_list_mode::_1 || Show_kill_list == show_kill_list_mode::efficiency)
		{
			if (vcplayerptr(player_num)->connected != player_connection_status::playing)
				color.r = color.g = color.b = 12;
			else {
				color = player_rgb[get_player_or_team_color(Netgame, Game_mode, player_num)];
			}
		}
		else
		{
			color = player_rgb[static_cast<player_ship_color>(player_num)];
		}
		fontcolor = BM_XRGB(color.r, color.g, color.b);
		gr_set_fontcolor(canvas, fontcolor, -1);

		if (Show_kill_list == show_kill_list_mode::team_kills)
			name = Netgame.team_name[(team_number{i})];
		else if (+(Game_mode & GM_BOUNTY) && player_num == Bounty_target && (GameTime64 & 0x10000))
		{
			name = "[TARGET]";
		}
		else
			name = vcplayerptr(player_num)->callsign;	// Note link to above if!!
		auto [sw, sh] = gr_get_string_size(game_font, static_cast<const char *>(name));
		{
			const auto b = x1 - x0 - fspacx2;
			if (sw > b)
				for (char *e = &name.buffer()[strlen(name)];;)
				{
					 *--e = 0;
					 sw = gr_get_string_size(game_font, name).width;
					 if (!(sw > b))
						 break;
				}
		}
		gr_string(canvas, game_font, x0, y, name, sw, sh);

		auto &player_info = vcobjptr(p.objnum)->ctype.player_info;
		if (Show_kill_list == show_kill_list_mode::efficiency)
		{
			const int eff = (player_info.net_killed_total + player_info.net_kills_total <= 0)
				? 0
				: static_cast<int>(
					static_cast<float>(player_info.net_kills_total) / (
						static_cast<float>(player_info.net_killed_total) + static_cast<float>(player_info.net_kills_total)
					) * 100.0
				);
			gr_printf(canvas, game_font, x1, y, "%i%%", eff <= 0 ? 0 : eff);
		}
		else if (Show_kill_list == show_kill_list_mode::team_kills)
			gr_printf(canvas, game_font, x1, y, "%3d", team_kills[(team_number{i})]);
		else if (is_multiplayer_cooperative)
			gr_printf(canvas, game_font, x1, y, "%-6d", player_info.mission.score);
		else if (Netgame.KillGoal || Netgame.PlayTimeAllowed.count())
			gr_printf(canvas, game_font, x1, y, "%3d(%d)", player_info.net_kills_total, player_info.KillGoalCount);
		else
			gr_printf(canvas, game_font, x1, y, "%3d", player_info.net_kills_total);

		if (PlayerCfg.MultiPingHud && Show_kill_list != show_kill_list_mode::team_kills)
                {
					if (is_multiplayer_cooperative)
                                x2 = SWIDTH - (fspacx64/2);
                        else
                                x2 = x0 + fspacx64;
			gr_printf(canvas, game_font, x2, y, "%4dms", Netgame.players[player_num].ping);
                }

		y += line_spacing;
	}
}

//returns true if viewer can see object
static int see_object(const d_robot_info_array &Robot_info, fvcobjptridx &vcobjptridx, const vcobjptridx_t objnum)
{
	fvi_info hit_data;

	//see if we can see this player
	const auto hit_type = find_vector_intersection(fvi_query{
		Viewer->pos,
		objnum->pos,
		fvi_query::unused_ignore_obj_list,
		&LevelUniqueObjectState,
		/* This is only necessary if `Viewer` can be a robot.  In developer
		 * builds, the user can view from any object, not just a player.
		 */
		&Robot_info,
		FQ_TRANSWALL,
		vcobjptridx(Viewer),
	}, Viewer->segnum, 0, hit_data);
	return hit_type == fvi_hit_type::Object && hit_data.hit_object == objnum;
}

}

//show names of teammates & players carrying flags

void show_HUD_names(const d_robot_info_array &Robot_info, grs_canvas &canvas, const game_mode_flags Game_mode)
{
	auto &Objects = LevelUniqueObjectState.Objects;
	auto &vcobjptr = Objects.vcptr;
	auto &vcobjptridx = Objects.vcptridx;
	for (playernum_t pnum = 0;pnum < N_players; ++pnum)
	{
		if (pnum == Player_num)
			continue;
		auto &plr = *vcplayerptr(pnum);
		if (plr.connected != player_connection_status::playing)
			continue;
		// ridiculusly complex to check if we want to show something... but this is readable at least.

		objnum_t objnum;
		if (Newdemo_state == ND_STATE_PLAYBACK) {
			//if this is a demo, the objnum in the player struct is wrong, so we search the object list for the objnum
			for (objnum=0;objnum<=Highest_object_index;objnum++)
			{
				auto &objp = *vcobjptr(objnum);
				if (objp.type == object_type::OBJ_PLAYER && get_player_id(objp) == pnum)
					break;
			}
			if (objnum > Highest_object_index)	//not in list, thus not visible
				continue;			//..so don't show name
		}
		else
			objnum = plr.objnum;

		const auto &&objp = vcobjptridx(objnum);
		const auto &pl_flags = objp->ctype.player_info.powerup_flags;
		const auto is_friend{(+(Game_mode & GM_MULTI_COOP) || (+(Game_mode & GM_TEAM) && multi_get_team_from_player(Netgame, pnum) == multi_get_team_from_player(Netgame, Player_num)))};
		const auto show_friend_name{Show_reticle_name};
		const auto is_cloaked = pl_flags & PLAYER_FLAGS_CLOAKED;
		const auto show_enemy_name = Show_reticle_name && Netgame.ShowEnemyNames && !is_cloaked;
		const auto show_name = is_friend ? show_friend_name : show_enemy_name;
		const auto show_typing = is_friend || !is_cloaked;
		const auto is_bounty_target{+(Game_mode & GM_BOUNTY) && pnum == Bounty_target};
		const auto show_indi = (is_friend || !is_cloaked) &&
#if DXX_BUILD_DESCENT == 1
			is_bounty_target;
#elif DXX_BUILD_DESCENT == 2
			(is_bounty_target || ((game_mode_capture_flag(Game_mode) || game_mode_hoard(Game_mode)) && (pl_flags & PLAYER_FLAGS_FLAG)));
#endif

		if ((show_name || show_typing || show_indi) && see_object(Robot_info, vcobjptridx, objp))
		{
			auto player_point = g3_rotate_point(objp->pos);
			if (player_point.p3_codes == clipping_code::None) //on screen
			{
				g3_project_point(player_point);
				if (!(player_point.p3_flags & projection_flag::overflow))
				{
					const fix x = player_point.p3_sx;
					const fix y = player_point.p3_sy;
					const fix dy = -fixmuldiv(fixmul(objp->size, Matrix_scale.y), i2f(canvas.cv_bitmap.bm_h) / 2, player_point.p3_vec.z);
					const fix dx = fixmul(dy, grd_curscreen->sc_aspect);
					/* Set the text to show */
					const auto name = is_bounty_target
						? "Target"
						: (show_name
							? plr.callsign.operator const char *()
							: nullptr);
					const auto trailer = show_typing
						? ({
							const auto m = multi_sending_message[pnum];
							m == msgsend_state::typing
							? ", Typing"
							: m == msgsend_state::automap
								? ", Map"
								: nullptr;
							})
						: nullptr;
					/* If both `name` and `trailer` are present, then
					 * concatenate them into label_storage.  If successful, set
					 * `s` to `label_storage`.  Otherwise, set `s` to
					 * `nullptr`.
					 *
					 * If exactly one of `name` or `trailer` is present, set
					 * `s` to the one that is present.  In the case that
					 * `trailer` is present, skip the leading literal `", "`
					 * that is necessary for the name-present case, but not
					 * necessary for the name-absent case.
					 *
					 * If neither is present, set `s` to `trailer`, which by
					 * definition is `nullptr` in this branch.
					 *
					 * Finally, if `s` is not `nullptr`, then something can be
					 * shown.  Show whatever `s` points at, which will be one
					 * of:
					 * - `label_storage`
					 * - `name`
					 * - `&trailer[2]`
					 */
					std::array<char, CALLSIGN_LEN + 10> label_storage;
					if (const auto s = name
						? (
							trailer
							? (std::snprintf(label_storage.data(), label_storage.size(), "%s%s", name, trailer) > 0 ? label_storage.data() : nullptr)
							: name
						)
						: (
							trailer ? &trailer[2] : trailer
						)
					)
					{
						const auto &&[w, h] = gr_get_string_size(*canvas.cv_font, s);
						const auto color{get_player_or_team_color(Netgame, Game_mode, pnum)};
						auto &c = player_rgb[color];
						gr_set_fontcolor(canvas, BM_XRGB(c.r, c.g, c.b), -1);
						const int x1 = f2i(x) - w / 2;
						const int y1 = f2i(y - dy) + FSPACY(1);
						gr_string(canvas, *canvas.cv_font, x1, y1, s, w, h);
					}

					/* Draw box on HUD */
					if (show_indi)
					{
						const fix w = dx / 4;
						const fix h = dy / 4;

							struct {
								int r, g, b;
							} c{};
#if DXX_BUILD_DESCENT == 2
						if (game_mode_capture_flag(Game_mode))
								((multi_get_team_from_player(Netgame, pnum) == team_number::blue) ? c.r : c.b) = 31;
						else if (game_mode_hoard(Game_mode))
						{
								(+(Game_mode & GM_TEAM)
									? ((multi_get_team_from_player(Netgame, pnum) == team_number::blue) ? c.b : c.r)
									: c.g
								) = 31;
						}
						else
#endif
							{
								auto &color = player_rgb[get_player_color(pnum)];
								c = {color.r, color.g, color.b};
							}
						const uint8_t color = BM_XRGB(c.r, c.g, c.b);

						gr_line(canvas, x + dx - w, y - dy, x + dx, y - dy, color);
						gr_line(canvas, x + dx, y - dy, x + dx, y - dy + h, color);
						gr_line(canvas, x - dx, y - dy, x - dx + w, y - dy, color);
						gr_line(canvas, x - dx, y - dy, x - dx, y - dy + h, color);
						gr_line(canvas, x + dx - w, y + dy, x + dx, y + dy, color);
						gr_line(canvas, x + dx, y + dy, x + dx, y + dy - h, color);
						gr_line(canvas, x - dx, y + dy, x - dx + w, y + dy, color);
						gr_line(canvas, x - dx, y + dy, x - dx, y + dy - h, color);
					}
				}
			}
		}
	}
}

//draw all the things on the HUD
void draw_hud(const d_robot_info_array &Robot_info, grs_canvas &canvas, const object &plrobj, const control_info &Controls, const game_mode_flags Game_mode)
{
	auto &Objects = LevelUniqueObjectState.Objects;
	auto &vcobjptr = Objects.vcptr;
	auto &player_info = plrobj.ctype.player_info;
	if (Newdemo_state == ND_STATE_RECORDING)
	{
		int ammo;
		auto &Primary_weapon = player_info.Primary_weapon;
		if ((Primary_weapon == primary_weapon_index::vulcan && (ammo = player_info.vulcan_ammo, true))
#if DXX_BUILD_DESCENT == 2
			||
			(Primary_weapon == primary_weapon_index::omega && (ammo = player_info.Omega_charge, true))
#endif
		)
			newdemo_record_primary_ammo(ammo);
		newdemo_record_secondary_ammo(player_info.secondary_ammo[player_info.Secondary_weapon]);
	}
	if (PlayerCfg.HudMode == HudType::Hidden) // no hud, "immersion mode"
		return;

	// Cruise speed
	if (const auto cruise_speed{Cruise_speed}; cruise_speed && PlayerCfg.CockpitMode[1] != cockpit_mode_t::rear_view)
		if (auto &viewer{*Viewer}; viewer.type == object_type::OBJ_PLAYER && get_player_id(viewer) == Player_num)
		{
		gr_set_fontcolor(canvas, BM_XRGB(0, 31, 0), -1);
			auto &game_font = *GAME_FONT;
			const auto &&line_spacing = LINE_SPACING(game_font, game_font);
			const auto is_multiplayer{+(Game_mode & GM_MULTI)};
			int y{};
			switch (PlayerCfg.CockpitMode[1])
			{
				case cockpit_mode_t::full_screen:
					y -= is_multiplayer
						? line_spacing * 10
						: line_spacing * 6;
					break;
				case cockpit_mode_t::status_bar:
					y -= is_multiplayer
						? line_spacing * 6
						: line_spacing;
					break;
				default:
					y -= is_multiplayer
						? line_spacing * 7
						: line_spacing * 2;
					break;
			}
			gr_printf(canvas, game_font, FSPACX(1), canvas.cv_bitmap.bm_h + y, "%s %2d%%", TXT_CRUISE, f2i(cruise_speed));
		}

	//	Show score so long as not in rearview
	if (!Rear_view && PlayerCfg.CockpitMode[1] != cockpit_mode_t::rear_view && PlayerCfg.CockpitMode[1] != cockpit_mode_t::status_bar)
	{
		hud_show_score(canvas, player_info, Game_mode);
		if (score_time)
			hud_show_score_added(canvas, Game_mode);
	}

	if (!Rear_view && PlayerCfg.CockpitMode[1] != cockpit_mode_t::rear_view)
		hud_show_timer_count(canvas, Game_mode);

	//	Show other stuff if not in rearview or letterbox.
	if (!Rear_view && PlayerCfg.CockpitMode[1] != cockpit_mode_t::rear_view)
	{
		show_HUD_names(Robot_info, canvas, Game_mode);

		if (PlayerCfg.CockpitMode[1] == cockpit_mode_t::status_bar || PlayerCfg.CockpitMode[1] == cockpit_mode_t::full_screen)
			hud_show_homing_warning(canvas, player_info.homing_object_dist);

		const local_multires_gauge_graphic multires_gauge_graphic{};
		const hud_draw_context_hs_mr hudctx(canvas, grd_curscreen->get_screen_width(), grd_curscreen->get_screen_height(), multires_gauge_graphic);
		if (PlayerCfg.CockpitMode[1] == cockpit_mode_t::full_screen)
		{

			auto &game_font = *GAME_FONT;
			const auto &&line_spacing = LINE_SPACING(game_font, game_font);
			const unsigned base_y = canvas.cv_bitmap.bm_h - (+(Game_mode & GM_MULTI) ? (line_spacing * (5 + (N_players > 3))) : line_spacing);
			unsigned current_y = base_y;
			hud_show_energy(canvas, player_info, game_font, current_y);
			current_y -= line_spacing;
			hud_show_shield(canvas, plrobj, game_font, current_y);
			current_y -= line_spacing;
#if DXX_BUILD_DESCENT == 2
			hud_show_afterburner(canvas, player_info, game_font, current_y);
			current_y -= line_spacing;
#endif
			hud_show_weapons(canvas, plrobj, game_font, +(Game_mode & GM_MULTI));
#if DXX_BUILD_DESCENT == 1
			if (!PCSharePig)
#endif
			{
				if (!(+(Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP)))
					/* In non-cooperative multiplayer games, everyone always has all
					 * keys.  Hide them to reduce visual clutter.
					 */
					hud_show_keys(hudctx, HUD_SCALE_AR(hudctx.xscale, hudctx.yscale), player_info);
			}
			hud_show_cloak_invuln(canvas, player_info.powerup_flags, player_info.cloak_time, player_info.invulnerable_time, current_y);

			if (Newdemo_state==ND_STATE_RECORDING)
				newdemo_record_player_flags(player_info.powerup_flags.get_player_flags());
		}

#ifndef RELEASE
		if (!(+(Game_mode & GM_MULTI) && Show_kill_list != show_kill_list_mode::None))
			show_time(canvas, *canvas.cv_font);
#endif

#if DXX_BUILD_DESCENT == 2
		if (PlayerCfg.CockpitMode[1] != cockpit_mode_t::letterbox && PlayerCfg.CockpitMode[1] != cockpit_mode_t::rear_view)
		{
			hud_show_flag(canvas, player_info, multires_gauge_graphic);
			hud_show_orbs(canvas, player_info, multires_gauge_graphic);
		}
#endif
		HUD_render_message_frame(canvas);

		if (PlayerCfg.CockpitMode[1] != cockpit_mode_t::status_bar)
			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));
		if (+(Game_mode & GM_MULTI) && Show_kill_list != show_kill_list_mode::None)
			hud_show_kill_list(vcobjptr, canvas, Game_mode);
		if (PlayerCfg.CockpitMode[1] != cockpit_mode_t::letterbox)
			show_reticle(canvas, player_info, PlayerCfg.ReticleType, 1);
		if (PlayerCfg.CockpitMode[1] != cockpit_mode_t::letterbox && Newdemo_state != ND_STATE_PLAYBACK && PlayerCfg.MouseFlightSim && PlayerCfg.MouseFSIndicator)
		{
			const auto gwidth = canvas.cv_bitmap.bm_w;
			const auto gheight = canvas.cv_bitmap.bm_h;
			auto &raw_mouse_axis = Controls.raw_mouse_axis;
			show_mousefs_indicator(canvas, raw_mouse_axis[0], raw_mouse_axis[1], raw_mouse_axis[2], gwidth / 2, gheight / 2, gheight / 4);
		}
	}

	if (Rear_view && PlayerCfg.CockpitMode[1] != cockpit_mode_t::rear_view)
	{
		HUD_render_message_frame(canvas);
		gr_set_fontcolor(canvas, BM_XRGB(0, 31, 0), -1);
		auto &game_font = *GAME_FONT;
		gr_string(canvas, game_font, 0x8000, canvas.cv_bitmap.bm_h - LINE_SPACING(game_font, game_font), TXT_REAR_VIEW);
	}
}

//print out some player statistics
void render_gauges(grs_canvas &canvas, const game_mode_flags Game_mode)
{
	auto &Objects = LevelUniqueObjectState.Objects;
	auto &vmobjptr = Objects.vmptr;
	auto &plrobj = get_local_plrobj();
	auto &player_info = plrobj.ctype.player_info;
	const auto energy = f2ir(player_info.energy);
	auto &pl_flags = player_info.powerup_flags;
	const auto cloak = (pl_flags & PLAYER_FLAGS_CLOAKED);

	assert(PlayerCfg.CockpitMode[1] == cockpit_mode_t::full_cockpit || PlayerCfg.CockpitMode[1] == cockpit_mode_t::status_bar);

	auto shields = f2ir(plrobj.shields);
	if (shields < 0 ) shields = 0;

	gr_set_curfont(canvas, *GAME_FONT);

	if (Newdemo_state == ND_STATE_RECORDING)
	{
		const auto homing_object_dist = player_info.homing_object_dist;
		if (homing_object_dist >= 0)
			newdemo_record_homing_distance(homing_object_dist);
	}

	const local_multires_gauge_graphic multires_gauge_graphic{};
	const hud_draw_context_hs_mr hudctx(canvas, grd_curscreen->get_screen_width(), grd_curscreen->get_screen_height(), multires_gauge_graphic);
	draw_weapon_boxes(hudctx, player_info);
	if (PlayerCfg.CockpitMode[1] == cockpit_mode_t::full_cockpit)
	{
		if (Newdemo_state == ND_STATE_RECORDING)
			newdemo_record_player_energy(energy);
		draw_energy_bar(*grd_curcanv, hudctx, energy);
		gr_set_default_canvas();
#if DXX_BUILD_DESCENT == 1
		if (PlayerCfg.HudMode == HudType::Standard)
#elif DXX_BUILD_DESCENT == 2
		if (Newdemo_state==ND_STATE_RECORDING )
			newdemo_record_player_afterburner(Afterburner_charge);
		draw_afterburner_bar(hudctx, Afterburner_charge);
#endif
		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);
		draw_player_ship(hudctx, player_info, cloak, SHIP_GAUGE_X, SHIP_GAUGE_Y);

		if (player_info.powerup_flags & PLAYER_FLAGS_INVULNERABLE)
			draw_invulnerable_ship(hudctx, plrobj);
		else
			draw_shield_bar(hudctx, shields);
		draw_numerical_display(hudctx, shields, energy);

		if (Newdemo_state==ND_STATE_RECORDING)
		{
			newdemo_record_player_shields(shields);
			newdemo_record_player_flags(player_info.powerup_flags.get_player_flags());
		}
		draw_keys_state(hudctx, player_info.powerup_flags).draw_all_cockpit_keys();

		show_homing_warning(hudctx, player_info.homing_object_dist);
		draw_wbu_overlay(hudctx);

	}
	else if (PlayerCfg.CockpitMode[1] == cockpit_mode_t::status_bar)
	{

		if (Newdemo_state == ND_STATE_RECORDING)
			newdemo_record_player_energy(energy);
		sb_draw_energy_bar(hudctx, energy);
#if DXX_BUILD_DESCENT == 1
		if (PlayerCfg.HudMode == HudType::Standard)
#elif DXX_BUILD_DESCENT == 2
		if (Newdemo_state==ND_STATE_RECORDING )
			newdemo_record_player_afterburner(Afterburner_charge);
		sb_draw_afterburner(hudctx, player_info);
		if (PlayerCfg.HudMode == HudType::Standard && inset_window[gauge_inset_window_view::secondary].user == weapon_box_user::weapon)
#endif
			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);

		draw_player_ship(hudctx, player_info, cloak, SB_SHIP_GAUGE_X, SB_SHIP_GAUGE_Y);

		if (player_info.powerup_flags & PLAYER_FLAGS_INVULNERABLE)
			draw_invulnerable_ship(hudctx, plrobj);
		else
			sb_draw_shield_bar(hudctx, shields);
		sb_draw_shield_num(hudctx, shields);

		if (Newdemo_state==ND_STATE_RECORDING)
		{
			newdemo_record_player_shields(shields);
			newdemo_record_player_flags(player_info.powerup_flags.get_player_flags());
		}
		draw_keys_state(hudctx, player_info.powerup_flags).draw_all_statusbar_keys();

		const auto is_multiplayer{+(Game_mode & GM_MULTI)}; sb_show_lives(hudctx, HUD_SCALE_AR(grd_curscreen->get_screen_width(), grd_curscreen->get_screen_height(), hudctx.multires_gauge_graphic), player_info, is_multiplayer);
		const auto is_multiplayer_non_cooperative{is_multiplayer && !(Game_mode & GM_MULTI_COOP)};
		sb_show_score(hudctx, player_info, is_multiplayer_non_cooperative);

		if (!is_multiplayer_non_cooperative)
		{
			sb_show_score_added(hudctx);
		}
	}
#if DXX_BUILD_DESCENT == 1
	else
		draw_player_ship(hudctx, player_info, cloak, SB_SHIP_GAUGE_X, SB_SHIP_GAUGE_Y);
#endif
}

//	---------------------------------------------------------------------------------------------------------
//	Call when picked up a quad laser powerup.
//	If laser is active, set old_weapon[0] to -1 to force redraw.
void update_laser_weapon_info(void)
{
	auto &old_weapon = inset_window[gauge_inset_window_view::primary].old_weapon;
	if (old_weapon.primary == primary_weapon_index::laser)
		old_weapon = {};
}

#if DXX_BUILD_DESCENT == 2

//draws a 3d view into one of the cockpit windows.  win is 0 for left,
//1 for right.  viewer is object.  NULL object means give up window
//user is one of the WBU_ constants.  If rear_view_flag is set, show a
//rear view.  If label is non-NULL, print the label at the top of the
//window.
void do_cockpit_window_view(const gauge_inset_window_view win, const weapon_box_user user)
{
	assert(user == weapon_box_user::weapon || user == weapon_box_user::post_missile_static);
	auto &inset = inset_window[win];
	auto &inset_user = inset.user;
	if (user == weapon_box_user::post_missile_static && inset_user != weapon_box_user::post_missile_static)
		inset_window[win].time_static_played = 0;
	if (inset_user == weapon_box_user::weapon || inset_user == weapon_box_user::post_missile_static)
		return;		//already set
	inset_user = user;
	if (inset.overlap_dirty) {
		inset.overlap_dirty = 0;
		gr_set_default_canvas();
	}
}

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)
{
	grs_subcanvas window_canv;
	const auto viewer_save{Viewer};
	static int window_x,window_y;
	int rear_view_save = Rear_view;

	window_rendered_data window;

	inset_window[win].user = user;						//say who's using window

	Viewer = &viewer;
	Rear_view = rear_view_flag;

	const local_multires_gauge_graphic multires_gauge_graphic{};
	const hud_draw_context_hs_mr hudctx(window_canv, grd_curscreen->get_screen_width(), grd_curscreen->get_screen_height(), multires_gauge_graphic);
	if (PlayerCfg.CockpitMode[1] == cockpit_mode_t::full_screen)
	{
		int w = HUD_SCALE_AR(hudctx.xscale, hudctx.yscale)(multires_gauge_graphic.get(106, 44));
		int h = w;

		const int dx = (win == gauge_inset_window_view::primary) ? -(w + (w / 10)) : (w / 10);

		window_x = grd_curscreen->get_screen_width() / 2 + dx;
		window_y = grd_curscreen->get_screen_height() - h - (SHEIGHT / 15);

#if DXX_USE_STEREOSCOPIC_RENDER
		if (VR_stereo != StereoFormat::None)
		{
			const auto &&[vx, vy, vw, vh]{gr_build_stereo_viewport_window(VR_stereo, window_x, window_y, w, h)};
			window_x = vx;
			window_y = vy;
			w = vw;
			h = vh;
		}
#endif

		gr_init_sub_canvas(window_canv, canvas, window_x, window_y, w, h);
	}
	else {
		if (PlayerCfg.CockpitMode[1] != cockpit_mode_t::full_cockpit && PlayerCfg.CockpitMode[1] != cockpit_mode_t::status_bar)
			goto abort;

		auto &resbox = gauge_boxes[multires_gauge_graphic.hiresmode];
		auto &weaponbox = resbox[win];
		const auto box = &weaponbox[(PlayerCfg.CockpitMode[1] == cockpit_mode_t::status_bar) ? gauge_hud_type::statusbar : gauge_hud_type::cockpit];
		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));
	}

	gr_set_current_canvas(window_canv);

#if DXX_USE_STEREOSCOPIC_RENDER
	if (VR_stereo != StereoFormat::None) {
		render_frame(window_canv, -VR_eye_width, window);
		render_frame(window_canv,  VR_eye_width, window);
		goto abort;
	}
	else
#endif
		render_frame(window_canv, 0, window);

	//	HACK! If guided missile, wake up robots as necessary.
	if (viewer.type == object_type::OBJ_WEAPON) {
		// -- Used to require to be GUIDED -- if (viewer->id == GUIDEDMISS_ID)
		wake_up_rendered_objects(viewer, window);
	}

	if (label) {
		if (Color_0_31_0 == -1)
			Color_0_31_0 = BM_XRGB(0,31,0);
		gr_set_fontcolor(window_canv, Color_0_31_0, -1);
		auto &game_font = *GAME_FONT;
		gr_string(window_canv, game_font, 0x8000, FSPACY(1), label);
	}

	if (player_info)	// only non-nullptr for weapon_box_user::guided
		show_reticle(window_canv, *player_info, reticle_type::cross_v1, 0);

	if (PlayerCfg.CockpitMode[1] == cockpit_mode_t::full_screen)
	{
		int small_window_bottom,big_window_bottom,extra_part_h;

		gr_ubox(window_canv, 0, 0, window_canv.cv_bitmap.bm_w, window_canv.cv_bitmap.bm_h, BM_XRGB(0,0,32));

		//if the window only partially overlaps the big 3d window, copy
		//the extra part to the visible screen

		big_window_bottom = SHEIGHT - 1;

		if (window_y > big_window_bottom) {

			//the small window is completely outside the big 3d window, so
			//copy it to the visible screen
			gr_bitmap(canvas, window_x, window_y, window_canv.cv_bitmap);
			inset_window[win].overlap_dirty = 1;
		}
		else {

			small_window_bottom = window_y + window_canv.cv_bitmap.bm_h - 1;

			extra_part_h = small_window_bottom - big_window_bottom;

			if (extra_part_h > 0) {
				grs_subcanvas overlap_canv;
				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);
				gr_bitmap(canvas, window_x, big_window_bottom + 1, overlap_canv.cv_bitmap);
				inset_window[win].overlap_dirty = 1;
			}
		}
	}
	else if (PlayerCfg.CockpitMode[1] == cockpit_mode_t::full_cockpit)
		/* `draw_wbu_overlay` has hard-coded x/y coordinates with their
		 * origin at the root of the screen canvas, not the window
		 * canvas.  Pass the screen canvas so that the coordinates are
		 * interpreted properly.
		 */
		draw_wbu_overlay(hud_draw_context_hs_mr(canvas, grd_curscreen->get_screen_width(), grd_curscreen->get_screen_height(), multires_gauge_graphic));

	//force redraw when done
	inset_window[win].old_weapon = {};

abort:;

	Viewer = viewer_save;

	Rear_view = rear_view_save;
	/* grd_curcanv may point to `window_canv`; if so, grd_curcanv
	 * would become a dangling pointer when `window_canv` goes out of
	 * scope at the end of the block.  Redirect it to the default screen
	 * to avoid pointing to freed memory.  Setting grd_curcanv to
	 * nullptr would be better, but some code assumes that grd_curcanv
	 * is never nullptr, so instead set it to the default canvas.
	 * Eventually, grd_curcanv will be removed entirely.
	 */
	gr_set_current_canvas(canvas);
}
#endif
}
