DiamondPanel.GameOver.cs
using System;
using Sandbox;
namespace Diamonds;
public sealed partial class DiamondPanel
{
const float GameOverFontSize = 116, GameOverWordGap = 34, GameOverRestartFontSize = 46;
// Hotload leaves new fields at their defaults, so a fresh game-over clock must be the default state.
float gameOverColorTime;
Vector2 GameOverCenter => new( Left + DiamondBoard.BoardWidth * 0.5f, Top + boardOffsetY + DiamondBoard.BoardHeight * 0.5f );
Rect GameOverRestartButton => new( GameOverCenter + new Vector2( -150, 46 ), new Vector2( 300, 84 ) );
/// <summary>Advance the GAME OVER word colors; each game over starts from the same phase.</summary>
public void UpdateGameOver( float delta )
{
gameOverColorTime = Board is { GameOver: true } ? (gameOverColorTime + MathF.Max( 0, delta )) % 100 : 0;
}
/// <summary>A click on the game-over RESTART button fades into a new game, like a completed restart hold.</summary>
public bool HandleGameOverClick( Vector2 mouse, Vector2 size )
{
if ( TitleScreen || ModalOpen || TransitionPending || Board is not { GameOver: true } ) return false;
float scale = MathF.Min( size.x / CanvasWidth, size.y / CanvasHeight );
if ( scale <= 0 ) return false;
var point = (mouse - (size - new Vector2( CanvasWidth, CanvasHeight ) * scale) * 0.5f) / scale;
// Holding R fades RESTART out beneath its RESTARTING frame; a faded-out button isn't there to click (see HoveredButton).
if ( RestartOverlayFade >= 1 || !GameOverRestartButton.IsInside( point ) ) return false;
PlayClickSound();
BeginTransition( toGame: true );
return true;
}
void DrawGameOver( Painter p, float scale )
{
var center = GameOverCenter;
// Opposing color cycles, matching the two title words.
var title = center + new Vector2( 0, -44 );
DrawScoreNumber( p, "GAME", title - new Vector2( GameOverWordGap * 0.5f, 0 ), GameOverFontSize, TitleWordColor( 0, gameOverColorTime ), align: 1 );
DrawScoreNumber( p, "OVER", title + new Vector2( GameOverWordGap * 0.5f, 0 ), GameOverFontSize, TitleWordColor( 1, gameOverColorTime ), align: 0 );
var size = Box.Rect.Size;
var mouse = (MousePosition - (size - new Vector2( CanvasWidth, CanvasHeight ) * scale) * 0.5f) / scale;
var button = GameOverRestartButton;
// A held restart's RESTARTING frame replaces the button, so fade it out underneath.
float shown = 1 - RestartOverlayFade;
if ( shown <= 0 ) return;
bool hover = !ModalOpen && !TransitionPending && button.IsInside( mouse );
if ( hover )
{
p.Fill = Ink.WithAlpha( (HasActive ? 0.2f : 0.12f) * shown );
p.Stroke = Stroke.None;
p.Rect( button );
}
// Split the word so the hotkey letter cycles colors on its own, underlined beneath its glyph.
float r = MeasureScoreText( p, "R", GameOverRestartFontSize ).x;
float rest = MeasureScoreText( p, "ESTART", GameOverRestartFontSize ).x;
float left = button.Center.x - (r + rest) * 0.5f;
var key = ScoreColor( restartColorPhase ).WithAlpha( shown );
DrawScoreNumber( p, "R", new Vector2( left, button.Center.y ), GameOverRestartFontSize, key, align: 0 );
DrawScoreNumber( p, "ESTART", new Vector2( left + r, button.Center.y ), GameOverRestartFontSize, Color.White.WithAlpha( shown ), align: 0 );
p.Stroke = Stroke.None;
p.Fill = Color.Black.WithAlpha( 0.8f * shown );
p.Rect( new Rect( left + 2 - 1.5f, button.Center.y + GameOverRestartFontSize * 0.42f - 1.5f, r + 3, 7 ) );
p.Fill = key;
p.Rect( new Rect( left + 2, button.Center.y + GameOverRestartFontSize * 0.42f, r, 4 ) );
}
/// <summary>The ink size of <paramref name="value"/> as <see cref="DrawScoreNumber"/> lays it out, excluding its padding.</summary>
static Vector2 MeasureScoreText( Painter p, string value, float fontSize )
{
using var scope = p.Scope();
p.TextStyle = new TextStyle( DiamondScoreEffects.ScoreFont, DiamondScoreEffects.PickupFontSize, Color.White ) { Alignment = TextFlag.Center };
return p.MeasureText( value ) * (fontSize / DiamondScoreEffects.PickupFontSize);
}
}