UI panel for the arena fight. Draws floating damage and miss popups over each gladiator's head, placing them in stage coordinates from World3D and fading them out by age.
using System;
using Sandbox.UI;
using Sandbox.UI.Construct;
/// <summary>
/// The fight's UI layer: the damage / miss popups over the fighters' heads. The fight itself is drawn in
/// the 3D dungeon or arena (<see cref="World3D"/>).
/// </summary>
public sealed class ArenaView : Panel
{
/// <summary>Stage pixels per puppet pixel (fight distances and lifts are in stage pixels).</summary>
public const float BaseScale = 0.78f;
/// <summary>A fighter position (DisplayX, height above the ground in stage pixels) on the 1920x1080 stage.</summary>
public static Vector2 ToStage( float displayX, float lift )
{
var w = World3D.Instance;
if ( w.IsValid() ) return w.ToStage( w.FighterWorld( displayX, lift ) );
return new Vector2( 960f + displayX, 965f - lift );
}
readonly Panel popupLayer;
readonly Dictionary<Gladiator.Popup, Panel> popups = new();
public ArenaView()
{
AddClass( "arena-view" );
popupLayer = Add.Panel( "popup-layer" );
}
public override void Tick()
{
base.Tick();
var fight = Scene?.Get<ArenaFight>();
var l = fight?.Left;
var r = fight?.Right;
CleanPopups( l, r );
UpdatePopups( l );
UpdatePopups( r );
}
void CleanPopups( Gladiator l, Gladiator r )
{
bool Alive( Gladiator.Popup p ) => (l.IsValid() && l.Popups.Contains( p )) || (r.IsValid() && r.Popups.Contains( p ));
foreach ( var dead in popups.Keys.Where( p => !Alive( p ) ).ToList() )
{
popups[dead].Delete();
popups.Remove( dead );
}
}
void UpdatePopups( Gladiator g )
{
if ( !g.IsValid() || float.IsNaN( g.DisplayX ) ) return;
foreach ( var p in g.Popups )
{
if ( !popups.TryGetValue( p, out var panel ) )
{
panel = popupLayer.Add.Panel( "popup" );
panel.AddClass( p.Kind.ToString().ToLower() );
var label = panel.Add.Label( p.Text, "popup-text" );
if ( p.Colour is not null ) label.Style.FontColor = Color.Parse( p.Colour ) ?? Color.White;
popups[p] = panel;
}
float age = p.Age;
var head = ToStage( g.DisplayX, g.Lift + 470f * BaseScale );
panel.Style.Left = head.x + p.OffsetX;
panel.Style.Top = head.y - age * 70f;
panel.Style.Opacity = Math.Clamp( 1.6f - age, 0f, 1f );
}
}
}