// An almost 1:1 recreation of the Unreal 1 HUD
Class UnrealHUD : BaseStatusBar
{
	double FracTic;

	// for easy access to ammo slots
	UnrealMainHandler hnd;

	// Unreal HUD variables
	Color RedColor, GreenColor, BlackColor;
	int HudMode;

	// Helpers
	Vector2 scalev;
	double CurX, CurY, ClipX, ClipY;
	Color DrawColor;
	ViewTracer vtracer;
	Actor lastseen;
	int lastseentic, Count;

	// Fonts
	Font LargeFont, LargeRedFont, UBigFont, MedFont, WhiteFont, TinyFont, TinyWhiteFont, TinyRedFont;
	HUDFont mMapFont;

	// Common Textures
	TextureID HalfHud, HudLine, HudAmmo, IconHeal, IconSkul, IconSel, IconBase, KeyIcons[7];

	// messaging stuff
	String PickupMsg;
	int PickupMsgTic;
	String ShortMsg[4];
	int ShortMsgTic[4];
	int ShortMsgCol[4];
	String MidPrintStr;
	int MidPrintTic;
	bool MidPrintLarge;

	// 0.83 HUD stuff
	String OldAmmo[19];
	Class<Inventory> OldAmmoType[19];
	String OldArmor[7];
	Class<Inventory> OldArmorType[7];
	String OldKeys[7];
	HUDFont mOldDigits, mOldDigitsSmall;
	Font OldLargeFont, OldSmallFont;

	// Translations
	int RedIcon;

	// Translator menu interaction
	transient bool bTranslatorActive;

	override void Init()
	{
		Super.Init();
		SetSize(64,640,400);
		// Initialize
		Count = 0;
		vtracer = new("ViewTracer");
		RedColor = "FF 00 00";
		GreenColor = "00 FF 00";
		BlackColor = "00 00 00";
		DrawColor = "FF FF FF";
		LargeFont = Font.GetFont('ULargeFont');
		LargeRedFont = Font.GetFont('ULargeRedFont');
		UBigFont = Font.GetFont('UBigFont');
		MedFont = Font.GetFont('UMedFont');
		WhiteFont = Font.GetFont('UWhiteFont');
		TinyFont = Font.GetFont('UTinyFont');
		TinyWhiteFont = Font.GetFont('UTinyWhiteFont');
		TinyRedFont = Font.GetFont('UTinyRedFont');
		HalfHud = TexMan.CheckForTexture("HalfHud",TexMan.Type_Any);
		HudLine = TexMan.CheckForTexture("HudLine",TexMan.Type_Any);
		HudAmmo = TexMan.CheckForTexture("HudAmmo",TexMan.Type_Any);
		IconHeal = TexMan.CheckForTexture("IconHeal",TexMan.Type_Any);
		IconSkul = TexMan.CheckForTexture("IconSkul",TexMan.Type_Any);
		IconSel = TexMan.CheckForTexture("IconSel",TexMan.Type_Any);
		IconBase = TexMan.CheckForTexture("IconBase",TexMan.Type_Any);
		RedIcon = Translation.GetID('RedIcon');
		KeyIcons[0] = TexMan.CheckForTexture("I_KeyR",TexMan.Type_Any);
		KeyIcons[1] = TexMan.CheckForTexture("I_KeyB",TexMan.Type_Any);
		KeyIcons[2] = TexMan.CheckForTexture("I_KeyY",TexMan.Type_Any);
		KeyIcons[3] = TexMan.CheckForTexture("I_SkullR",TexMan.Type_Any);
		KeyIcons[4] = TexMan.CheckForTexture("I_SkullB",TexMan.Type_Any);
		KeyIcons[5] = TexMan.CheckForTexture("I_SkullY",TexMan.Type_Any);
		KeyIcons[6] = TexMan.CheckForTexture("I_KeyG",TexMan.Type_Any);
		mOldDigits = HUDFont.Create(Font.GetFont('U083Digits'),26,Mono_CellLeft);
		mOldDigitsSmall = HUDFont.Create(Font.GetFont('U083DigitsSmall'),13,Mono_CellLeft);
		mMapFont = HUDFont.Create(WhiteFont);
		OldLargeFont = Font.GetFont('UOldLargeFont');
		OldSmallFont = Font.GetFont('UOldSmallFont');
		// arranged so weapon-specific icons are checked first
		OldAmmo[0] = "Mini083";
		OldAmmo[1] = "Disp083";
		OldAmmo[2] = "Clip083";
		OldAmmo[3] = "Tary083";
		OldAmmo[4] = "Asmd083";
		OldAmmo[5] = "Rokt083";
		OldAmmo[6] = "Flak083";
		OldAmmo[7] = "Razor083";
		OldAmmo[8] = "Bio083";
		OldAmmo[9] = "Rifle083";
		OldAmmo[10] = "Shell083";
		OldAmmo[11] = "Impal083";
		OldAmmo[12] = "Flame083";
		OldAmmo[13] = "Tele083";
		OldAmmo[14] = "Stun083";
		OldAmmo[15] = "Big083";
		OldAmmo[16] = "Smini083";
		OldAmmo[17] = "Peace083";
		OldAmmo[18] = "OLSMP083";
		OldAmmoType[0] = "UMinigun";
		OldAmmoType[1] = "DefaultAmmo";
		OldAmmoType[2] = "UMiniAmmo";
		OldAmmoType[3] = "StingerAmmo";
		OldAmmoType[4] = "AsmdAmmo";
		OldAmmoType[5] = "URocketAmmo";
		OldAmmoType[6] = "UFlakBox";
		OldAmmoType[7] = "RazorAmmo";
		OldAmmoType[8] = "UBioAmmo";
		OldAmmoType[9] = "URifleAmmo";
		OldAmmoType[10] = "UShells";
		OldAmmoType[11] = "ImpalerAmmo";
		OldAmmoType[12] = "FlameAmmo";
		OldAmmoType[13] = "UTranslocatorAmmo";
		OldAmmoType[14] = "StunnerAmmo";
		OldAmmoType[15] = "BigAmmo";
		OldAmmoType[16] = "SMiniAmmo";
		OldAmmoType[17] = "PeaceAmmo";
		OldAmmoType[18] = "OLSMPAmmo";
		OldArmor[0] = "Armor083";
		OldArmor[1] = "Kev083";
		OldArmor[2] = "Asb083";
		OldArmor[3] = "Tox083";
		OldArmor[4] = "Belt083";
		OldArmor[5] = "Pbelt083";
		OldArmor[6] = "Bonus083";
		OldArmorType[0] = "UArmor";
		OldArmorType[1] = "KevlarSuit";
		OldArmorType[2] = "AsbestosSuit";
		OldArmorType[3] = "ToxinSuit";
		OldArmorType[4] = "ShieldBelt";
		OldArmorType[5] = "PowerShield";
		OldArmorType[6] = "UArmorBonus";
		OldKeys[0] = "Redk083";
		OldKeys[1] = "Bluek083";
		OldKeys[2] = "Goldk083";
		OldKeys[3] = "Rskul083";
		OldKeys[4] = "Bskul083";
		OldKeys[5] = "Gskul083";
		OldKeys[6] = "Grenk083";
	}

	override void Draw( int state, double TicFrac )
	{
		Super.Draw(state,TicFrac);
		FracTic = TicFrac;
		HudMode = CVar.GetCVar('stinger_hudmode',players[consoleplayer]).GetInt();
		scalev.x = scalev.y = CVar.GetCVar('hud_scale',players[consoleplayer]).GetInt();
		if ( scalev.x < 0 ) scalev.x = scalev.y = max(1,min(Screen.GetWidth()/640.,Screen.GetHeight()/480.));	// the typical behavior is scaling to 640x400 but we're expecting 4:3 here
		else if ( scalev.x == 0 )
		{
			scalev.x = CleanXFac_1;
			scalev.y = CleanYFac_1;
		}
		ClipX = Screen.GetWidth()/scalev.x;
		ClipY = Screen.GetHeight()/scalev.y;
		CurX = 0;
		CurY = 0;
		double lbottom = Screen.GetHeight()-32*scalev.y;
		for ( Inventory i=CPlayer.mo.inv; i; i=i.inv )
			if ( i is 'UnrealInventory' )
				UnrealInventory(i).PreRender(lbottom);
		if ( CPlayer.ReadyWeapon is 'UTWeapon' )
			UTWeapon(CPlayer.ReadyWeapon).PreRender(lbottom);
		if ( state == HUD_Fullscreen )
		{
			BeginHUD();
			DrawUnrealHUD();
		}
		else if ( state == HUD_StatusBar )
		{
			BeginStatusBar();
			DrawUnrealBar();
		}
		for ( Inventory i=CPlayer.mo.inv; i; i=i.inv )
			if ( i is 'UnrealInventory' )
				UnrealInventory(i).PostRender(lbottom);
		if ( CPlayer.ReadyWeapon is 'UTWeapon' )
			UTWeapon(CPlayer.ReadyWeapon).PostRender(lbottom);
		DrawIdentifyInfo(state);
		DrawMessages(state);
	}

	private void DrawNumberOf( Inventory i, double x, double y )
	{
		if ( (i.Amount <= 1) && !((i is 'UnrealInventory') && UnrealInventory(i).bDRAWSPECIAL) ) return;
		double TempX = CurX, TempY = CurY;
		string itxt = String.Format("%d",((i is 'UnrealInventory')&&UnrealInventory(i).bDRAWSPECIAL)?i.special1:min(99999,i.Amount));
		CurX += 30;
		CurY += 23;
		CurX -= TinyRedFont.StringWidth(itxt);
		Screen.DrawText(TinyRedFont,Font.CR_UNTRANSLATED,CurX,CurY,itxt,DTA_VirtualWidthF,ClipX,DTA_VirtualHeightF,ClipY,DTA_KeepRatio,true);
		CurX = TempX;
		CurY = TempY;
	}

	private void DrawIconValue( int n, bool bIsArmor = false )
	{
		if ( !HudMode || (HudMode == 3) ) return;
		double TempX = CurX, TempY = CurY;
		string itxt = String.Format("%d",n);
		CurX -= 2;
		CurY -= 6;
		CurX -= TinyFont.StringWidth(itxt);
		Screen.DrawText(TinyFont,Font.CR_UNTRANSLATED,CurX,CurY,itxt,DTA_VirtualWidthF,ClipX,DTA_VirtualHeightF,ClipY,DTA_KeepRatio,true);
		CurX = TempX;
		CurY = TempY;
	}

	private void DrawHudIcon( double x, double y, Inventory i, bool bRed )
	{
		if ( i.Icon.IsNull() ) return;
		double width = CurX;
		CurX = x;
		CurY = y;
		// scale to fit
		Vector2 scl = TexMan.GetScaledSize(i.Icon);
		double mscl = 32./max(scl.x,scl.y);
		double dw = (ClipX/mscl), dh = (ClipY/mscl);
		double dx = CurX/mscl, dy = CurY/mscl;
		if ( bRed )
		{
			Screen.DrawTexture(IconBase,false,CurX,CurY,DTA_VirtualWidthF,ClipX,DTA_VirtualHeightF,ClipY,DTA_KeepRatio,true,DTA_TranslationIndex,RedIcon);
			Screen.DrawTexture(i.Icon,false,dx,dy,DTA_VirtualWidthF,dw,DTA_VirtualHeightF,dh,DTA_KeepRatio,true,DTA_TranslationIndex,RedIcon,DTA_TopOffset,0,DTA_LeftOffset,0);
		}
		else
		{
			Screen.DrawTexture(IconBase,false,CurX,CurY,DTA_VirtualWidthF,ClipX,DTA_VirtualHeightF,ClipY,DTA_KeepRatio,true);
			Screen.DrawTexture(i.Icon,false,dx,dy,DTA_VirtualWidthF,dw,DTA_VirtualHeightF,dh,DTA_KeepRatio,true,DTA_TopOffset,0,DTA_LeftOffset,0);
		}
		if ( (i is 'UnrealInventory') && (UnrealInventory(i).DefaultCharge > 0) && (UnrealInventory(i).bActive || (UnrealInventory(i).Charge < UnrealInventory(i).DefaultCharge)) )
			Screen.DrawTexture(HudLine,false,CurX+2,CurY+29,DTA_VirtualWidthF,ClipX,DTA_VirtualHeightF,ClipY,DTA_KeepRatio,true,DTA_WindowRightF,Min(28.*(UnrealInventory(i).Charge/double(UnrealInventory(i).DefaultCharge)),28.));
		else if ( (i is 'UTArmor') && ((HudMode == 0) || (HudMode == 3)) )
			Screen.DrawTexture(HudLine,false,CurX+2,CurY+29,DTA_VirtualWidthF,ClipX,DTA_VirtualHeightF,ClipY,DTA_KeepRatio,true,DTA_WindowRightF,Min(28.*(i.Amount/double(i.MaxAmount)),28.));
	}

	private void DrawFragCount( double x, double y )
	{
		CurX = X;
		CurY = Y;
		Screen.DrawTexture(IconSkul,false,CurX,CurY,DTA_VirtualWidthF,ClipX,DTA_VirtualHeightF,ClipY,DTA_KeepRatio,true);
		CurX += 30;
		CurY += 23;
		string score = String.Format("%d",(deathmatch||teamplay)?CPlayer.fragcount:CPlayer.killcount);
		CurX -= TinyWhiteFont.StringWidth(score);
		Screen.DrawText(TinyWhiteFont,Font.CR_UNTRANSLATED,CurX,CurY,score,DTA_VirtualWidthF,ClipX,DTA_VirtualHeightF,ClipY,DTA_KeepRatio,true);
	}

	private void DrawInventory( double x, double y, bool bDrawOne = false, bool bNoWeapons = false )
	{
		bool bGotNext, bGotPrev, bGotSelected, bRed, bFlashTranslator;
		Inventory Inv, Prev, Next, SelectedItem;
		UTranslator translator;
		double HalfHUDX, HalfHUDY;
		double AmmoBarSize;
		if ( (HudMode < 4) && !bNoWeapons )	// then draw HalfHUD
		{
			HalfHUDX = ClipX-64;
			HalfHUDY = ClipY-32;
			Screen.DrawTexture(HalfHUD,false,HalfHUDX,HalfHUDY,DTA_VirtualWidthF,ClipX,DTA_VirtualHeightF,ClipY,DTA_KeepRatio,true);
		}
		if ( !CPlayer.mo.Inv ) return;	// can this even happen?
		bFlashTranslator = false;
		bGotSelected = false;
		bGotNext = false;
		bGotPrev = false;
		Prev = null;
		Next = null;
		SelectedItem = CPlayer.mo.InvSel;
		for ( Inv=CPlayer.mo.Inv; Inv; Inv=Inv.Inv )
		{
			if ( bDrawOne ) break;
			// if drawing more than one inventory, find next and previous items
			if ( Inv == SelectedItem ) bGotSelected = true;
			else if ( Inv.bINVBAR )
			{
				if ( bGotSelected )
				{
					if ( !bGotNext )
					{
						Next = Inv;
						bGotNext = true;
					}
					else if ( !bGotPrev ) Prev = Inv;
				}
				else
				{
					if ( !Next ) Next = Prev;
					Prev = Inv;
					bGotPrev = true;
				}
			}
			if ( Inv is 'UTranslator' ) translator = UTranslator(Inv);
		}
		// drawing weapon slots differently than Unreal 1 because we have better methods here
		let cw = CPlayer.ReadyWeapon;
		int cwslot = -1;
		if ( cw && (cw.SlotNumber != -1) ) cwslot = cw.SlotNumber;
		let pw = CPlayer.PendingWeapon;
		int pwslot = -1;
		if ( pw && (pw != WP_NOCHANGE) && (pw.SlotNumber != -1) ) pwslot = pw.SlotNumber;
		Array<Ammo> aslots[10];
		// clear the arrays before work
		for ( int i=0; i<10; i++ ) aslots[i].Clear();
		// we need the ammo slots array, if it isn't accessible then we're fucked
		if ( !hnd ) hnd = UnrealMainHandler(EventHandler.Find("UnrealMainHandler"));
		if ( hnd )
		{
			// populate the array
			for ( int i=0; i<hnd.AmmoSlots.Size(); i++ )
			{
				let a = Ammo(CPlayer.mo.FindInventory(hnd.AmmoSlots[i].AmmoType));
				if ( !a ) continue;
				for ( int j=0; j<10; j++ )
				{
					if ( !hnd.AmmoSlots[i].UsedInSlot[j] ) continue;
					aslots[j].Push(a);
				}
			}
		}
		// draw the slots
		if ( (HudMode < 4) && !bNoWeapons )
		{
			for ( int i=0; i<10; i++ )
			{
				Font cfont = TinyFont;
				if ( (pwslot != -1) && (pwslot == i) || (pwslot == -1) && (cwslot == i) )
					cfont = TinyWhiteFont;
				int realslot = i?i:10;
				CurX = HalfHUDX-3+realslot*6;
				CurY = HalfHUDY+4;
				if ( CPlayer.HasWeaponsInSlot(i) )
					Screen.DrawText(cfont,Font.CR_UNTRANSLATED,CurX,CurY,String.Format("%d",i),DTA_VirtualWidthF,ClipX,DTA_VirtualHeightF,ClipY,DTA_KeepRatio,true);
				// Draw ammo bar(s)
				int nammo = 0;
				for ( int j=0; j<aslots[i].Size(); j++ ) if ( aslots[i][j].Amount ) nammo++;
				if ( nammo <= 0 ) continue;
				int cbar = 0;
				for ( int j=0; j<aslots[i].Size(); j++ )
				{
					let amo = aslots[i][j];
					if ( amo.Amount <= 0 ) continue;
					AmmoBarSize = 16*min(1.0,amo.Amount/double(amo.MaxAmount));
					CurX = HalfHUDX-3+realslot*6+(4./nammo)*cbar;
					CurY = HalfHUDY+29-AmmoBarSize;
					if ( (AmmoBarSize < 8) && (amo.Amount < 10) && (amo.Amount > 0) && (nammo == 1) )
					{
						CurY -= 9;
						Screen.DrawText(TinyRedFont,Font.CR_UNTRANSLATED,CurX,CurY,String.Format("%d",amo.Amount),DTA_VirtualWidthF,ClipX,DTA_VirtualHeightF,ClipY,DTA_KeepRatio,true);
						CurY += 9;
					}
					DrawColor = Color(0,255,0);
					if ( AmmoBarSize < 8 ) DrawColor = Color(255-int(AmmoBarSize)*30,int(AmmoBarSize)*30+40,0);
					if ( !cw || (cw.Ammo1 != amo) ) DrawColor = Color(DrawColor.r/2,DrawColor.g/2,DrawColor.b/2);
					if ( amo.Amount > 0 )
					{
						Screen.DrawTexture(HudAmmo,false,CurX,CurY,DTA_VirtualWidthF,ClipX,DTA_VirtualHeightF,ClipY,DTA_KeepRatio,true,DTA_FillColor,BlackColor,DTA_DestWidthF,4./nammo,DTA_DestHeightF,AmmoBarSize);
						Screen.DrawTexture(HudAmmo,false,CurX,CurY,DTA_VirtualWidthF,ClipX,DTA_VirtualHeightF,ClipY,DTA_KeepRatio,true,DTA_AlphaChannel,true,DTA_FillColor,DrawColor,DTA_DestWidthF,4./nammo,DTA_DestHeightF,AmmoBarSize);
					}
					cbar++;
				}
			}
		}
		// flash translator icon
		if ( translator ) bFlashTranslator = (translator.bNewMessage || translator.bNotNewMessage);
		// draw the inventory bar
		if ( (HUDMode == 5) || !SelectedItem ) return;
		Count++;
		if ( Count > 20 ) Count = 0;
		if ( Prev && !Next && !bDrawOne ) x += 32;	// this was missing from the original, causing a gap when there's only two items in the inventory
		if ( Prev )
		{
			bRed = ((Prev is 'UnrealInventory') && UnrealInventory(Prev).bActive) || (Prev is 'Powerup') || ((Prev is 'UTranslator') && ((bTranslatorActive) || (bFlashTranslator && ((gametic%8)<4))));
			DrawHudIcon(x,y,Prev,bRed);
			if ( (Prev.MaxAmount > 1) || ((Prev is 'UnrealInventory') && UnrealInventory(Prev).bDRAWSPECIAL) ) DrawNumberOf(Prev,x,y);
		}
		bRed = ((SelectedItem is 'UnrealInventory') && UnrealInventory(SelectedItem).bActive) || (SelectedItem is 'Powerup') || ((SelectedItem is 'UTranslator') && ((bTranslatorActive) || (bFlashTranslator && ((gametic%8)<4))));
		if ( !Next && !Prev && !bDrawOne ) DrawHudIcon(x+64,y,SelectedItem,bRed);
		else DrawHudIcon(x+32,y,SelectedItem,bRed);
		CurX = x+32;
		if ( !Next && !Prev && !bDrawOne ) CurX = x+64;
		CurY = y;
		Screen.DrawTexture(IconSel,false,CurX,CurY,DTA_VirtualWidthF,ClipX,DTA_VirtualHeightF,ClipY,DTA_KeepRatio,true,DTA_Alpha,0.5);
		if ( (SelectedItem.MaxAmount > 1) || ((SelectedItem is 'UnrealInventory') && UnrealInventory(SelectedItem).bDRAWSPECIAL) ) DrawNumberOf(SelectedItem,CurX,y);
		if ( Next )
		{
			bRed = ((Next is 'UnrealInventory') && UnrealInventory(Next).bActive) || (Next is 'Powerup') || ((Next is 'UTranslator') && ((bTranslatorActive) || (bFlashTranslator && ((gametic%8)<4))));
			DrawHudIcon(x+64,y,Next,bRed);
			if ( (Next.MaxAmount > 1) || ((Next is 'UnrealInventory') && UnrealInventory(Next).bDRAWSPECIAL) ) DrawNumberOf(Next,x+64,y);
		}
	}

	private bool DrawArmor( double x, double y, bool bDrawOne = false, bool bCheckOnly = false )
	{
		bool hasdrawn = false;
		int ArmorAmount = 0, CurAbs = 0;
		Inventory Inv, BestArmor;
		double XL, YL;
		CurX = x;
		CurY = y;
		for ( Inv=CPlayer.mo.Inv; Inv; Inv=Inv.Inv )
		{
			if ( !(Inv is 'UTArmor') ) continue;
			ArmorAmount += Inv.Amount;
			if ( (Inv.Amount <= 0) || Inv.Icon.IsNull() ) continue;
			if ( !bDrawOne )
			{
				hasdrawn = true;
				if ( !bCheckOnly ) DrawHudIcon(CurX,y,Inv,false);
				CurX += 32;
				CurY += HudMode?29:27;
				if ( !bCheckOnly ) DrawIconValue(Inv.Amount);
				CurY -= HudMode?29:27;
			}
			else if ( UTArmor(Inv).absorb > CurAbs )
			{
				CurAbs = UTArmor(Inv).absorb;
				BestArmor = Inv;
			}
		}
		if ( bDrawOne && BestArmor )
		{
			hasdrawn = true;
			if ( !bCheckOnly ) DrawHudIcon(CurX,Y,BestArmor,false);
			CurX += 32;
			CurY += HudMode?29:27;
			if ( !bCheckOnly ) DrawIconValue(BestArmor.Amount);
			CurY -= HudMode?29:27;
		}
		if ( (ArmorAmount > 0) && !HudMode )
		{
			hasdrawn = true;
			if ( !bCheckOnly ) Screen.DrawText(LargeFont,Font.CR_UNTRANSLATED,CurX+2,Y,String.Format("%d",ArmorAmount),DTA_VirtualWidthF,ClipX,DTA_VirtualHeightF,ClipY,DTA_KeepRatio,true);
		}
		return hasdrawn;
	}

	private void DrawAmmo( double x, double y )
	{
		CurX = x;
		CurY = y;
		if ( !CPlayer.ReadyWeapon || !CPlayer.ReadyWeapon.Ammo1 ) return;
		Font cfont = LargeFont;
		if ( CPlayer.ReadyWeapon.Ammo1.Amount <= min(9,CPlayer.ReadyWeapon.Ammo1.MaxAmount/3) ) cfont = LargeRedFont;
		int sec = -1, sec2 = -1;
		bool red = false, red2 = false;
		if ( CPlayer.ReadyWeapon is 'UnrealWeapon' )
			[sec, sec2, red, red2] = UnrealWeapon(CPlayer.ReadyWeapon).GetClipAmount();
		if ( !HudMode )
		{
			CurX -= cfont.StringWidth(String.Format("%d",CPlayer.ReadyWeapon.Ammo1.Amount))+2;
			Screen.DrawText(cfont,Font.CR_UNTRANSLATED,CurX,CurY,String.Format("%d",CPlayer.ReadyWeapon.Ammo1.Amount),DTA_VirtualWidthF,ClipX,DTA_VirtualHeightF,ClipY,DTA_KeepRatio,true);
			double bx = CurX;
			CurX = bx-MedFont.StringWidth(String.Format("%d",sec))-2;
			CurY += cfont.GetHeight()-MedFont.GetHeight()-4;
			if ( sec != -1 )
				Screen.DrawText(MedFont,red?Font.CR_RED:Font.CR_UNTRANSLATED,CurX,CurY,String.Format("%d",sec),DTA_VirtualWidthF,ClipX,DTA_VirtualHeightF,ClipY,DTA_KeepRatio,true);
			CurX = bx-MedFont.StringWidth(String.Format("%d",sec2))-2;
			CurY -= MedFont.GetHeight()+2;
			if ( sec2 != -1 )
				Screen.DrawText(MedFont,red2?Font.CR_RED:Font.CR_UNTRANSLATED,CurX,CurY,String.Format("%d",sec2),DTA_VirtualWidthF,ClipX,DTA_VirtualHeightF,ClipY,DTA_KeepRatio,true);
		}
		CurX = x;
		CurY = y;
		Screen.DrawTexture(IconBase,false,CurX,CurY,DTA_VirtualWidthF,ClipX,DTA_VirtualHeightF,ClipY,DTA_KeepRatio,true);
		TextureID icon = CPlayer.ReadyWeapon.Icon.IsNull()?CPlayer.ReadyWeapon.Ammo1.Icon:CPlayer.ReadyWeapon.Icon;
		// scale to fit
		Vector2 scl = TexMan.GetScaledSize(icon);
		double mscl = 32./max(scl.x,scl.y);
		double dw = (ClipX/mscl), dh = (ClipY/mscl);
		double dx = CurX/mscl, dy = CurY/mscl;
		Screen.DrawTexture(icon,false,dx,dy,DTA_VirtualWidthF,dw,DTA_VirtualHeightF,dh,DTA_KeepRatio,true,DTA_TopOffset,0,DTA_LeftOffset,0);
		CurX += 32;
		CurY += 29;
		DrawIconValue(CPlayer.ReadyWeapon.Ammo1.Amount);
		CurX = X+2;
		CurY = Y+29;
		if ( (HudMode != 1) && (HudMode != 2) && (HudMode != 4) )
			Screen.DrawTexture(HudLine,false,CurX,CurY,DTA_VirtualWidthF,ClipX,DTA_VirtualHeightF,ClipY,DTA_KeepRatio,true,DTA_WindowRightF,Min(28.*(CPlayer.ReadyWeapon.Ammo1.Amount/double(CPlayer.ReadyWeapon.Ammo1.MaxAmount)),28.));
		if ( HudMode )
		{
			CurX = (x+30)-TinyFont.StringWidth(String.Format("%d",sec));
			CurY = y+2;
			if ( sec != -1 )
				Screen.DrawText(red?TinyRedFont:TinyFont,Font.CR_UNTRANSLATED,CurX,CurY,String.Format("%d",sec),DTA_VirtualWidthF,ClipX,DTA_VirtualHeightF,ClipY,DTA_KeepRatio,true);
			CurX = x+3;
			if ( sec2 != -1 )
				Screen.DrawText(red2?TinyRedFont:TinyFont,Font.CR_UNTRANSLATED,CurX,CurY,String.Format("%d",sec2),DTA_VirtualWidthF,ClipX,DTA_VirtualHeightF,ClipY,DTA_KeepRatio,true);
		}
	}

	private void DrawHealth( double x, double y )
	{
		CurX = X;
		CurY = Y;
		Font cfont = LargeFont;
		if ( CPlayer.mo.Health < 25 ) cfont = LargeRedFont;
		Screen.DrawTexture(IconHeal,false,CurX,CurY,DTA_VirtualWidthF,ClipX,DTA_VirtualHeightF,ClipY,DTA_KeepRatio,true);
		CurX += 32;
		CurY += 29;
		DrawIconValue(Max(0,CPlayer.mo.Health));
		CurY -= 29;
		if ( !HudMode ) Screen.DrawText(cfont,Font.CR_UNTRANSLATED,CurX+2,CurY,String.Format("%d",Max(0,CPlayer.mo.Health)),DTA_VirtualWidthF,ClipX,DTA_VirtualHeightF,ClipY,DTA_KeepRatio,true);
		CurX = X+2;
		CurY = Y+29;
		if ( (HudMode != 1) && (HudMode != 2) && (HudMode != 4) )
			Screen.DrawTexture(HudLine,false,CurX,CurY,DTA_VirtualWidthF,ClipX,DTA_VirtualHeightF,ClipY,DTA_KeepRatio,true,DTA_WindowRightF,Min(28.*(CPlayer.mo.Health/double(CPlayer.mo.SpawnHealth())),28.));
	}

	private void DrawIdentifyInfo( int state )
	{
		double lalpha = 2.0-((gametic+fractic)-lastseentic)/Thinker.TICRATE;
		if ( !lastseen || (lalpha <= 0) ) return;
		String cl1 = "DarkGreen", cl2 = "Green";
		if ( deathmatch && (lastseen.player.GetTeam() < teams.size()) )
		{
			cl2 = teams[lastseen.player.GetTeam()].mName;
			cl1 = String.Format("Dark%s",cl2);
		}
		String tname = String.Format("\c[%s]%s:\c[%s] %s",cl1,StringTable.Localize("$M_NAME"),cl2,lastseen.player.GetUserName());
		let fnt = (state==HUD_Fullscreen)?WhiteFont:OldSmallFont;
		CurX = (ClipX-fnt.StringWidth(tname))/2;
		CurY = ClipY-54;
		Screen.DrawText(fnt,Font.CR_UNTRANSLATED,CurX,CurY,tname,DTA_VirtualWidthF,ClipX,DTA_VirtualHeightF,ClipY,DTA_KeepRatio,true,DTA_Alpha,lalpha/2.);
		if ( !deathmatch || (lastseen.IsTeammate(CPlayer.mo)) )
		{
			CurY += 1.2*fnt.GetHeight();
			tname = String.Format("\c[%s]%s:\c[%s] %d",cl1,StringTable.Localize("$M_HEALTH"),cl2,lastseen.Health);
			Screen.DrawText(fnt,Font.CR_UNTRANSLATED,CurX,CurY,tname,DTA_VirtualWidthF,ClipX,DTA_VirtualHeightF,ClipY,DTA_KeepRatio,true,DTA_Alpha,lalpha/2.);
		}
	}

	private void DrawKeys( double x, double y, bool leftright = false )
	{
		if ( deathmatch ) return;	// no need to draw in DM
		if ( gameinfo.gametype&(GAME_Hexen|GAME_Strife) ) return;	// no key display for these ATM (will do eventually)
		CurX = x;
		CurY = y;
		int cnt = 0;
		for ( int i=0; i<6; i++ )
		{
			if ( !CPlayer.mo.CheckKeys(i+1,false,true) ) continue;
			Screen.DrawTexture(KeyIcons[((i==0)&&(gameinfo.gametype&GAME_Heretic))?6:i],false,CurX,CurY,DTA_VirtualWidthF,ClipX,DTA_VirtualHeightF,ClipY,DTA_KeepRatio,true);
			cnt++;
			if ( !(cnt%3) )
			{
				CurX = x;
				CurY -= 16;
			}
			else CurX += leftright?16:-16;
		}
	}

	private void DrawUnrealHUD()
	{
		if ( HudMode >= 5 )
		{
			// minimal hud
			int ArmorAmount = 0;
			for ( Inventory Inv=CPlayer.mo.Inv; Inv; Inv=Inv.Inv )
			{
				if ( !(Inv is 'UTArmor') ) continue;
				ArmorAmount += Inv.Amount;
			}
			int AmmoAmount1 = 0, AmmoAmount2 = 0;
			Ammo amo1, amo2;
			[amo1, amo2] = GetCurrentAmmo();
			if ( amo1 )
			{
				AmmoAmount1 = amo1.Amount;
				if ( amo2 ) AmmoAmount2 = amo2.Amount;
			}
			String str;
			if ( amo1 && amo2 && (amo2 != amo1) ) str = String.Format(StringTable.Localize("$S_MINHUD2"),CPlayer.Health,(deathmatch||teamplay)?CPlayer.fragcount:CPlayer.killcount,AmmoAmount2,AmmoAmount1);
			else str = String.Format(StringTable.Localize("$S_MINHUD"),CPlayer.Health,(deathmatch||teamplay)?CPlayer.fragcount:CPlayer.killcount,AmmoAmount1);
			Screen.DrawText(OldSmallFont,Font.CR_WHITE,(Screen.GetWidth()-OldSmallFont.StringWidth(str)*CleanXFac_1)/2,CleanYFac_1,str,DTA_CleanNoMove_1,true);
			return;
		}
		if ( ClipX < 320 ) HudMode = 4;
		// Draw Armor
		if ( HudMode < 2 ) DrawArmor(0,0);
		else if ( (HudMode == 3) || (HudMode == 2) ) DrawArmor(0,ClipY-32);
		else if ( HudMode == 4 ) DrawArmor(ClipX-64,ClipY-64,true);
		// Draw Ammo
		if ( HudMode != 4 ) DrawAmmo(ClipX-96,ClipY-32);
		else DrawAmmo(ClipX-32,ClipY-32);
		// Draw Health
		if ( HudMode < 2 ) DrawHealth(0,ClipY-32);
		else if ( (HudMode == 3) || (HudMode == 2) ) DrawHealth(ClipX-128,ClipY-32);
		else if ( HudMode == 4 ) DrawHealth(ClipX-64,ClipY-32);
		// Display Inventory
		if ( HudMode < 2 ) DrawInventory(ClipX-96,0);
		else if ( HudMode == 3 ) DrawInventory(ClipX-96,ClipY-64);
		else if ( HudMode == 4 ) DrawInventory(ClipX-64,ClipY-64,true);
		else if ( HudMode == 2 ) DrawInventory(ClipX/2-64,ClipY-32);
		if ( deathmatch )
		{
			// Display Frag count
			if ( HudMode < 3 ) DrawFragCount(ClipX-32,ClipY-64);
			else if ( HudMode == 3 ) DrawFragCount(0,ClipY-64);
			else if ( HudMode == 4 ) DrawFragCount(ClipX-96,ClipY-32);
		}
		else
		{
			// Display Keys
			if ( HudMode < 3 ) DrawKeys(ClipX-16,ClipY-48);
			else if ( HudMode == 3 ) DrawKeys(0,ClipY-48,true);
			else if ( HudMode == 4 ) DrawKeys(ClipX-80,ClipY-16);
		}
	}

	override void ReceivedWeapon( Weapon weapn )
	{
		Super.ReceivedWeapon(weapn);
		if ( weapn.SlotNumber == -1 ) return;
		// fuckin' hell
		EventHandler.SendNetworkEvent("Bar083SlotFlash",weapn.SlotNumber);
	}

	private void DrawUnrealBar()
	{
		// 0.83 status bar, just for funsies
		DrawImage("Bar083",(0,336),DI_ITEM_OFFSETS);
		// extra widescreen filler
		bool first = true;
		double base = -128;
		double rx, dummy;
		do
		{
			[rx, dummy, dummy] = StatusBarToRealCoords(base,0,HorizontalResolution);
			DrawImage(first?"BarL083":"BarM083",(base,336),DI_ITEM_OFFSETS);
			first = false;
			base -= 128;
		} while ( rx >= -128 );
		first = true;
		base = 640;
		do
		{
			[rx, dummy, dummy] = StatusBarToRealCoords(base,0,HorizontalResolution);
			DrawImage(first?"BarR083":"BarM083",(base,336),DI_ITEM_OFFSETS);
			first = false;
			base += 128;
		} while ( rx < Screen.GetWidth() );
		static const float slotofs[] = {525, 84, 128, 173, 216, 259, 349, 392, 436, 481};
		static const float keyofs[] = {187, 211, 235, 379, 403, 427};
		for ( int i=0; i<10; i++ )
		{
			if ( !CPlayer.HasWeaponsInSlot(i) ) continue;
			bool used = (CPlayer.ReadyWeapon&&(CPlayer.ReadyWeapon.SlotNumber==i));
			if ( CPlayer.PendingWeapon && (CPlayer.PendingWeapon != WP_NOCHANGE) )
				used = (CPlayer.PendingWeapon.SlotNumber==i);
			DrawImage(used?"Used083":"Slot083",(slotofs[i],342),DI_ITEM_OFFSETS);
		}
		let hnd = UnrealMainHandler(EventHandler.Find("UnrealMainHandler"));
		if ( hnd )
		{
			for ( int i=0; i<10; i++ )
				if ( hnd.slotflash[i] > gametic )
					DrawImage("Flsh083",(slotofs[i],342),DI_ITEM_OFFSETS,(hnd.slotflash[i]-gametic+FracTic)/20.);
		}
		DrawString(mOldDigits,FormatNumber(CPlayer.health,3),(358,366),DI_TEXT_ALIGN_RIGHT);
		int ArmorAmount = 0, CurAbs = -1;
		Inventory BestArmor = null;
		for ( Inventory Inv=CPlayer.mo.Inv; Inv; Inv=Inv.Inv )
		{
			if ( !(Inv is 'UTArmor') ) continue;
			ArmorAmount += Inv.Amount;
			if ( Inv.Amount <= 0 ) continue;
			if ( UTArmor(Inv).absorb > CurAbs )
			{
				CurAbs = UTArmor(Inv).absorb;
				BestArmor = Inv;
			}
		}
		for ( int i=0; i<6; i++ )
		{
			if ( !(BestArmor is OldArmorType[i]) ) continue;
			DrawImage(OldArmor[i],(4,340),DI_ITEM_OFFSETS);
			break;
		}
		if ( ArmorAmount ) DrawString(mOldDigits,FormatNumber(ArmorAmount,3),(167,366),DI_TEXT_ALIGN_RIGHT);
		Inventory Ammo1, Ammo2;
		[Ammo1, Ammo2] = GetCurrentAmmo();
		if ( Ammo1 )
		{
			if ( Ammo1.Amount ) DrawString(mOldDigits,FormatNumber(Ammo1.Amount,3),(549,366),DI_TEXT_ALIGN_RIGHT);
			for ( int i=0; i<19; i++ )
			{
				// match by ammo
				if ( (OldAmmoType[i] is 'Ammo') && !(Ammo1 is OldAmmoType[i]) ) continue;
				// match by weapon
				if ( (OldAmmoType[i] is 'Weapon') && !(CPlayer.ReadyWeapon is OldAmmoType[i]) ) continue;
				DrawImage(OldAmmo[i],(560,336),DI_ITEM_OFFSETS);
				break;
			}
		}
		int sec = -1, sec2 = -1;
		if ( CPlayer.ReadyWeapon && (CPlayer.ReadyWeapon is 'UnrealWeapon') )
			[sec, sec2] = UnrealWeapon(CPlayer.ReadyWeapon).GetClipAmount();
		if ( sec != -1 )
			DrawString(mOldDigitsSmall,FormatNumber(sec,3),(633,378),DI_TEXT_ALIGN_RIGHT);
		if ( sec2 != -1 )
			DrawString(mOldDigitsSmall,FormatNumber(sec2,3),(556,378),DI_TEXT_ALIGN_LEFT);
		for ( int i=0; i<6; i++ )
		{
			if ( !CPlayer.mo.CheckKeys(i+1,false,true) ) continue;
			DrawImage(OldKeys[((i==0)&&(gameinfo.gametype&GAME_Heretic))?6:i],(keyofs[i],366),DI_ITEM_OFFSETS);
		}
		if ( HudMode > 5 ) return;
		// Draw frags in DM
		if ( deathmatch ) DrawFragCount(ClipX-32,0);
		// Need to draw the inventory bar
		DrawInventory(ClipX-(deathmatch?128:96),0,false,true);
	}

	override void Tick()
	{
		Super.Tick();
		if ( CPlayer.inventorytics > 0 )
		{
			if ( CPlayer.mo.InvSel )
				Console.Printf(StringTable.Localize("$M_ISELECT"),CPlayer.mo.InvSel.GetTag());
			CPlayer.inventorytics = 0;
		}
		vtracer.ignore = CPlayer.mo;
		vtracer.trace(CPlayer.mo.Vec2OffsetZ(0,0,CPlayer.viewz),CPlayer.mo.CurSector,(cos(CPlayer.mo.angle)*cos(CPlayer.mo.pitch),sin(CPlayer.mo.angle)*cos(CPlayer.mo.pitch),-sin(CPlayer.mo.pitch)),1000,0);
		if ( vtracer.Results.HitType != TRACE_HitActor ) return;
		lastseen = vtracer.Results.HitActor;
		lastseentic = gametic;
	}

	override void FlushNotify()
	{
		for ( int i=0; i<4; i++ )
		{
			ShortMsg[i] = "";
			ShortMsgTic[i] = int.min;
		}
		PickupMsg = "";
		PickupMsgTic = int.min;
		MidPrintStr = "";
		MidPrintTic = int.min;
	}

	override bool ProcessNotify( EPrintLevel printlevel, String outline )
	{
		if ( printlevel == PRINT_LOW )
		{
			// set pickup message
			PickupMsg = outline;
			PickupMsgTic = gametic+50;
			return true;
		}
		else
		{
			// put message into queue
			for ( int i=3; i>0; i-- )
			{
				ShortMsg[i] = ShortMsg[i-1];
				ShortMsgTic[i] = ShortMsgTic[i-1];
				ShortMsgCol[i] = ShortMsgCol[i-1];
			}
			if ( printlevel == PRINT_MEDIUM ) ShortMsgCol[0] = Font.CR_RED;
			else if ( (printlevel == PRINT_CHAT) || (printlevel == PRINT_TEAMCHAT) ) ShortMsgCol[0] = Font.CR_GREEN;
			else ShortMsgCol[0] = Font.CR_WHITE;
			ShortMsg[0] = outline;
			ShortMsgTic[0] = gametic+70;
			return true;
		}
		return false;
	}

	override bool ProcessMidPrint( Font fnt, String msg, bool bold )
	{
		if ( !fnt || (fnt == SmallFont) || (fnt == OriginalSmallFont) || (fnt == NewSmallFont) )
		{
			MidPrintStr = msg;
			MidPrintLarge = false;
			MidPrintTic = gametic+70;
			return true;
		}
		else if ( fnt == BigFont )
		{
			MidPrintStr = msg;
			MidPrintLarge = true;
			MidPrintTic = gametic+70;
			return true;
		}
		return false;
	}

	override bool DrawChat( String txt )
	{
		int xpos = 4*CleanXFac_1;
		int ypos = ((screenblocks<=10)||automapactive)?GetTopOfStatusBar():(Screen.GetHeight()-((screenblocks>11)?0:int(32*scalev.y)));
		ypos -= (WhiteFont.GetHeight()+4)*CleanYFac_1;
		String fullstr = String.Format("(> Say %s%s",txt,WhiteFont.GetCursor());
		// cut out until it fits
		while ( (WhiteFont.StringWidth(fullstr) > CleanWidth_1) && fullstr.Length() > 7 )
			fullstr.Remove(7,1);
		Screen.DrawText(WhiteFont,Font.CR_GREEN,xpos,ypos,fullstr,DTA_CleanNoMove_1,true);
		return true;
	}

	private void DrawMessages( int state )
	{
		int xpos, ypos;
		if ( (MidPrintStr.Length() > 0) && (MidPrintTic > gametic) )
		{
			let mfnt = MidPrintLarge?UBigFont:WhiteFont;
			xpos = (Screen.GetWidth()-mfnt.StringWidth(MidPrintStr)*CleanXFac_1)/2;
			ypos = 96*CleanYFac_1;
			Screen.DrawText(mfnt,Font.FindFontColor('UTHudText'),xpos,ypos,MidPrintStr,DTA_CleanNoMove_1,true,DTA_Alpha,clamp((MidPrintTic-gametic+fractic)*0.05,0,1),DTA_LegacyRenderStyle,STYLE_Add);
		}
		if ( PickupMsgTic > gametic )
		{
			xpos = (Screen.GetWidth()-WhiteFont.StringWidth(PickupMsg)*CleanXFac_1)/2;
			if ( state == HUD_Statusbar ) ypos = GetTopOfStatusBar()-21*CleanYFac_1;
			else ypos = Screen.GetHeight()-41*CleanYFac_1;
			Screen.DrawText(WhiteFont,Font.CR_WHITE,xpos,ypos,PickupMsg,DTA_CleanNoMove_1,true,DTA_Alpha,clamp((PickupMsgTic-gametic+fractic)*0.05,0,1),DTA_LegacyRenderStyle,STYLE_Add);
		}
		// draw messages
		xpos = 4*CleanXFac_1;
		ypos = 4*CleanYFac_1;
		if ( (state == HUD_Fullscreen) && (HudMode < 2) && DrawArmor(0,0,false,true) ) ypos += int(32*scalev.y);
		for ( int i=3; i>=0; i-- )
		{
			if ( ShortMsgTic[i] < gametic ) continue;
			let lines = WhiteFont.BreakLines(ShortMsg[i],CleanWidth_1/2);
			for ( int j=0; j<lines.Count(); j++ )
			{
				Screen.DrawText(WhiteFont,ShortMsgCol[i],xpos,ypos,lines.StringAt(j),DTA_CleanNoMove_1,true);
				ypos += (WhiteFont.GetHeight()+2)*CleanYFac_1;
			}
		}
	}

	override void DrawAutomapHUD( double ticFrac )
	{
		int crdefault = Font.CR_GREY;
		int highlight = Font.CR_RED;
		let scale = GetHUDScale();
		double textdist = 8./scale.Y;
		int height = WhiteFont.GetHeight();
		String printtext;
		int SCREENWIDTH = screen.GetWidth();
		BeginHUD();
		let y = textdist;
		let width = WhiteFont.StringWidth("00:00:00");
		double tmp, hres;
		[tmp,tmp,hres] = StatusbarToRealCoords(0,0,HorizontalResolution);
		double swidth = 0;
		double ltop = 0, rtop = 0;
		if ( (HudMode < 5) && CPlayer.mo.InvSel )
			rtop += (32*scalev.y)/scale.Y;
		double cbottom = GetTopOfStatusBar()-textdist;
		int protrusion = GetProtrusion(swidth/hres);
		[tmp,tmp,hres] = StatusbarToRealCoords(0,0,protrusion);
		width += int((swidth-hres)/scale.X);
		if ( am_showtime )
		{
			printtext = level.TimeFormatted();
			DrawString(mMapFont,level.TimeFormatted(),(-textdist-width,y+rtop),0,crdefault);
			y += height;
		}
		if ( am_showtotaltime ) DrawString(mMapFont,level.TimeFormatted(true),(-textdist-width,y+rtop),0,crdefault);
		if ( !deathmatch )
		{
			y = textdist;
			if ( am_showmonsters )
			{
				DrawString(mMapFont,String.Format("%s\34%c %d/%d",Stringtable.Localize("$AM_MONSTERS"),crdefault+65,level.killed_monsters,level.total_monsters),(textdist,y+ltop),0,highlight);
				y += height;
			}
			if ( am_showsecrets )
			{
				DrawString(mMapFont,String.Format("%s\34%c %d/%d",Stringtable.Localize("$AM_SECRETS"),crdefault+65,level.found_secrets,level.total_secrets),(textdist,y+ltop),0,highlight);
				y += height;
			}
			if ( am_showitems ) DrawString(mMapFont,String.Format("%s\34%c %d/%d",Stringtable.Localize("$AM_ITEMS"),crdefault+65,level.found_items,level.total_items),(textdist,y+ltop),0,highlight);
		}
		String mapname = level.FormatMapName(crdefault);
		BrokenLines lines = WhiteFont.BreakLines(mapname,int(SCREENWIDTH/scale.X));
		int numlines = lines.Count();
		int finalwidth = int(WhiteFont.StringWidth(lines.StringAt(numlines-1))*scale.X);
		[tmp,tmp,hres] = StatusbarToRealCoords(0,0,HorizontalResolution);
		protrusion = GetProtrusion(finalwidth/hres);
		[tmp,tmp,tmp,hres] = StatusbarToRealCoords(0,0,0,protrusion);
		y = (cbottom-hres)/scale.Y-height*numlines;
		for ( int i = 0; i < numlines; i++ )
		{
			DrawString(mMapFont,lines.StringAt(i),(0,y+ltop),DI_TEXT_ALIGN_CENTER|DI_SCREEN_HCENTER|DI_SCREEN_TOP,highlight);
			y += height;
		}
	}
}
