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 ) );
}
}