UI/FightHud.razor
@using System;
@using Sandbox;
@using Sandbox.UI;

<root>
	@if ( Fight.IsValid() && Fight.Left.IsValid() && Fight.Right.IsValid() )
	{
		@if ( Fight.State == FightState.Intro )
		{
			@* the Flash game's VS splash *@
			<div class="vs">
				<div class="half left"></div>
				<div class="half right"></div>
				<div class="slash"></div>
				<div class="name left outlined">@Fight.Left.DisplayName</div>
				<div class="sub left outlined">@($"Level {Fight.Left.Level}")</div>
				<div class="name right outlined">@Fight.Right.DisplayName</div>
				<div class="sub right outlined">@($"{Fight.Right.Title}, level {Fight.Right.Level}")</div>
				<div class="versus outlined">VS</div>
				<div class="live outlined">@(Fight.IsDuel ? "Live from the Dungeon" : "Live from the Arena")</div>
				@if ( Fight.IsDuel )
				{
					<div class="rules outlined">Duel: weapons only hurt armour. Break all of their armour to win - a fighter with no armour falls to a single blow.</div>
				}
				<div class="sas-round tick go" onclick=@(() => Fight.BeginFight())><div class="mark"></div></div>
			</div>
		}
		else
		{
			<div class="tabs">
				<div class="tab @(ShowInfo ? "active" : "")" onclick=@(() => { ShowInfo = !ShowInfo; StateHasChanged(); })>info</div>
			</div>

			@if ( !Fight.IsDuel )
			{
				<div class="crowd">
					<div class="crowd-fill" style="width: @(Fight.Crowd)%"></div>
					<div class="crowd-text">crowd: @Fight.CrowdMood</div>
				</div>
			}

			<div class="message">@Fight.LastMessage</div>

			<FighterCard [email protected] [email protected]></FighterCard>
			<FighterCard [email protected] Flip=@true [email protected]></FighterCard>

			@if ( MyTurn )
			{
				<div class="ring" @ref="Ring">
					@for ( int i = 0; i < Actions.Length; i++ )
					{
						var action = Actions[i];
						var slot = Slots[i];
						int x = MeIsLeft ? slot.x : -slot.x - 92;
						bool usable = Fight.CanUse( Me, action );
						<div class="action @(Blue( action ) ? "blue" : "red") @(usable ? "" : "disabled")" style="left: @(x)px; top: @(slot.y)px;"
							onclick=@(() => Choose( action )) onmouseover=@(() => SetHover( action )) onmouseout=@(() => ClearHover( action ))>
							<div class="icon" style="background-image: url('resources/sad/ss/ui/@(Icon( action )).png')"></div>
							@if ( Hovered == action )
							{
								<div class="hover-label">@HoverText( action, usable )</div>
							}
						</div>
					}
				</div>

				@* spells and potions along the bottom middle *@
				<div class="extras">
					@foreach ( var a in Extras )
					{
						var action = a;
						bool usable = Fight.CanUse( Me, action );
						<div class="extra @(ArenaFight.IsSpell( action ) ? "spell" : "potion") @(usable ? "" : "disabled")"
							onclick=@(() => Choose( action )) onmouseover=@(() => SetHover( action )) onmouseout=@(() => ClearHover( action ))>
							<div class="icon" style="background-image: url('resources/sad/@(ExtraArt( action ))')"></div>
							@if ( ArenaFight.IsPotion( action ) )
							{
								<div class="count outlined">@Me.Potions( ArenaFight.PotionOf( action ) )</div>
							}
							@if ( Hovered == action )
							{
								<div class="hover-label">@HoverText( action, usable )</div>
							}
						</div>
					}
				</div>
			}

			@if ( ShowInfo )
			{
				<div class="parchment info">
					@foreach ( var g in new[] { Fight.Left, Fight.Right } )
					{
						<div class="info-col">
							<div class="info-name">@($"{g.DisplayName} (level {g.Level})")</div>
							@foreach ( var s in Profile.StatOrder )
							{
								<div class="info-line">@($"{Profile.StatNames[s]}: {StatOf( g, s )}")</div>
							}
							<div class="info-line">@($"{g.Weapon.Name} ({g.Weapon.Min}-{g.Weapon.Max})")</div>
							@if ( g.Enchant != Enchant.None )
							{
								<div class="info-line">@($"{Catalog.EnchantTierName( g.EnchantTier )} {g.Enchant.ToString().ToLower()} enchant")</div>
							}
						</div>
					}
				</div>
			}

			@if ( Fight.State == FightState.Over )
			{
				<div class="result">
					<div class="title">@(Won ? "VICTORY!" : "DEFEAT")</div>
					<div class="btn" onclick=@(() => GameManager.Instance?.LeaveArena())>continue</div>
				</div>
			}
		}
	}
</root>

@code
{
	// left column = movement (blue), right column = attacks (red); mirrored if you're on the right
	static readonly FightAction[] Actions =
	{
		FightAction.MoveBack, FightAction.MoveForward, FightAction.Leap, FightAction.Rest,
		FightAction.QuickAttack, FightAction.NormalAttack, FightAction.PowerAttack, FightAction.Taunt, FightAction.Push
	};

	static readonly (int x, int y)[] Slots =
	{
		(-230, -170), (-250, -70), (-250, 30), (-230, 130),
		(140, -220), (162, -120), (170, -20), (162, 80), (140, 180)
	};

	Panel Ring { get; set; }
	bool ShowInfo;
	FightAction? Hovered;

	void SetHover( FightAction a ) { Hovered = a; StateHasChanged(); }
	void ClearHover( FightAction a ) { if ( Hovered == a ) { Hovered = null; StateHasChanged(); } }

	static bool Blue( FightAction a ) => a is FightAction.MoveBack or FightAction.MoveForward or FightAction.Leap or FightAction.Rest;

	/// <summary>Spells you know, then potions you carry.</summary>
	IEnumerable<FightAction> Extras
	{
		get
		{
			foreach ( var s in Catalog.Spells )
				if ( Me.Knows( s.Id ) ) yield return ArenaFight.CastOf( s.Id );
			foreach ( var p in Enum.GetValues<Potion>() )
				if ( Me.Potions( p ) > 0 ) yield return FightAction.DrinkHealth + (int)p;
		}
	}

	static string ExtraArt( FightAction a ) => ArenaFight.IsSpell( a )
		? $"ss/ui/{Catalog.SpellInfo( ArenaFight.SpellOf( a ) ).Icon}.png"
		: Catalog.PotionArt( ArenaFight.PotionOf( a ) );

	/// <summary>The small "Name (36%)" under the button you point at.</summary>
	string HoverText( FightAction a, bool usable )
	{
		string chance = Chance( a, usable );
		return string.IsNullOrEmpty( chance ) ? Label( a ) : $"{Label( a )} ({chance})";
	}

	ArenaFight Fight => Scene?.Get<ArenaFight>();

	Gladiator Me => Fight.Left.IsValid() && Fight.Left.IsLocalPlayer ? Fight.Left : Fight.Right.IsValid() && Fight.Right.IsLocalPlayer ? Fight.Right : null;
	bool MeIsLeft => Me == Fight.Left;
	bool MyTurn => Me.IsValid() && Me.Energy > 0 && Fight.IsTurnOf( Me );
	bool Won => Fight.Winner.IsValid() && Fight.Winner.IsLocalPlayer;

	static int StatOf( Gladiator g, int s ) => s switch
	{
		0 => g.Strength, 1 => g.Attack, 2 => g.Defence, 3 => g.Vitality, 4 => g.Agility, 5 => g.Stamina, 6 => g.Charisma, _ => g.Magicka
	};

	// the S&S2 action pictures
	static string Icon( FightAction a ) => a switch
	{
		FightAction.MoveBack => "836",
		FightAction.MoveForward => "834",
		FightAction.Leap => "832",
		FightAction.Rest => "840",
		FightAction.QuickAttack => "90",
		FightAction.NormalAttack => "88",
		FightAction.PowerAttack => "98",
		FightAction.Taunt => "843",
		_ => "838"
	};

	string Label( FightAction a )
	{
		if ( ArenaFight.IsSpell( a ) ) return Catalog.SpellInfo( ArenaFight.SpellOf( a ) ).Name;
		if ( ArenaFight.IsPotion( a ) ) return Catalog.PotionName( ArenaFight.PotionOf( a ) );
		return a switch
		{
			FightAction.MoveBack => "Walk backward",
			FightAction.MoveForward => "Walk forward",
			FightAction.Leap => "Leap",
			FightAction.Rest => "Rest",
			FightAction.QuickAttack => "Quick attack",
			FightAction.NormalAttack => "Charge",
			FightAction.PowerAttack => "Power attack",
			FightAction.Taunt => "Taunt",
			_ => "Push"
		};
	}

	// the chance / why-not after the move's name
	string Chance( FightAction a, bool usable )
	{
		var opp = Fight.OpponentOf( Me );
		if ( !usable )
		{
			if ( ArenaFight.IsSpell( a ) ) return "not enough magicka";
			return a switch
			{
				FightAction.Leap when Fight.Distance <= 2 => "too close",
				FightAction.Leap => "too tired",
				FightAction.MoveForward => "already there",
				FightAction.MoveBack => "against the wall",
				_ => "too far away"
			};
		}

		if ( ArenaFight.IsAttack( a ) ) return $"{(int)(Fight.HitChance( Me, opp, a ) * 100)}%";
		if ( a == FightAction.Taunt ) return $"{(int)(Fight.TauntChance( Me, opp ) * 100)}%";
		if ( a == FightAction.Push ) return Fight.PushRoom( opp ) ? $"{(int)(Fight.PushChance( Me ) * 100)}%" : "they're against the wall";
		if ( a == FightAction.CastFreeze ) return $"{(int)(Fight.FreezeChance( Me, opp ) * 100)}%";
		if ( ArenaFight.IsSpell( a ) ) return $"{Catalog.SpellInfo( ArenaFight.SpellOf( a ) ).Cost} magicka";
		if ( ArenaFight.IsPotion( a ) ) return $"{Me.Potions( ArenaFight.PotionOf( a ) )} left";
		return "";
	}

	void Choose( FightAction action )
	{
		if ( !Fight.CanUse( Me, action ) ) return;
		Hovered = null;
		Fight.RequestAction( action );
	}

	public override void Tick()
	{
		base.Tick();

		var w = World3D.Instance;
		if ( Ring.IsValid() && Me.IsValid() && w.IsValid() && !float.IsNaN( Me.DisplayX ) )
		{
			var chest = w.ToStage( w.FighterWorld( Me.DisplayX, Me.Lift + 260f * ArenaView.BaseScale ) );
			Ring.Style.Left = chest.x;
			Ring.Style.Top = chest.y;
		}
	}

	protected override int BuildHash()
	{
		var f = Fight;
		if ( !f.IsValid() ) return 0;
		return HashCode.Combine(
			HashCode.Combine( f.State, f.Current, MyTurn, f.LastMessage, f.Crowd, f.Winner, ShowInfo, Hovered ),
			HashCode.Combine( f.Left?.Step, f.Right?.Step, Me?.Energy, Me?.PowerRoll, f.Left?.Health, f.Right?.Health ),
			HashCode.Combine( Me?.Magic, Me?.HealthPotions, Me?.EnergyPotions, Me?.MagicPotions, f.Left?.ArmourPoints, f.Right?.ArmourPoints ) );
	}
}