Stages/HighscoreStage.cs
namespace BlockParty;
/// <summary>
/// High-score screen. Port of the original <c>HighscoreStage</c>, but instead of a locally
/// stored top-10 with 3-letter initials it shows the online sbox leaderboard — top 100
/// real players with Steam name + avatar (see <see cref="Leaderboard"/>). Reached from the
/// title's LEADERBOARD option and the level-select footer's trophy button (finished runs
/// return straight to the map).
/// </summary>
public sealed class HighscoreStage : MenuStageBase
{
/// <summary>Row to focus when the board opens, e.g. the run we just watched (-1 = default/BACK).</summary>
public int ReturnSelection { get; }
/// <summary>True when BACK should return to level select instead of the title screen.</summary>
public bool BackToLevelSelect { get; }
/// <summary>True when BACK should return to the workshop browser (a workshop level's board).</summary>
public bool BackToWorkshop { get; }
/// <summary>Set when this board was opened from a run's score tally (its trophy button): BACK — and
/// a replay launched from here — rebuilds that tally, already counted out. Null otherwise.</summary>
public ScoreReturn ReturnScore { get; }
/// <summary>Set when this stage shows a WORKSHOP level's board: the screen reads its level from
/// here instead of the persisted last-viewed filter (a ws id must never become that — the normal
/// leaderboard's level picker can't select it back off). Null on every normal board.</summary>
public string WorkshopLevelId { get; }
private HighscoreScreen _screen;
public HighscoreStage( GameManager manager, string levelId = null, int returnSelection = -1, bool backToLevelSelect = false, bool backToWorkshop = false, ScoreReturn returnScore = null ) : base( manager )
{
ReturnSelection = returnSelection;
BackToLevelSelect = backToLevelSelect;
BackToWorkshop = backToWorkshop;
ReturnScore = returnScore;
if ( WorkshopLevels.IsWorkshopId( levelId ) )
{
WorkshopLevelId = levelId;
// Same rule as the explicit-level arrival below: opening a level's board opens its OVERALL
// board. Without this, a character filter left on the normal leaderboard silently applies
// here — typically "NO ENTRIES YET" on a level that has scores.
if ( Settings.Current.LeaderboardCharacterId is not null )
{
Settings.Current.LeaderboardCharacterId = null;
Settings.Save();
}
return;
}
// Arriving with a level (the level-select footer's trophy button): open the OVERALL board
// for that level, not a character-filtered one. These become the last-viewed filters.
if ( levelId is not null
&& (Settings.Current.LeaderboardLevelId != levelId
|| Settings.Current.LeaderboardCharacterId is not null) )
{
Settings.Current.LeaderboardLevelId = levelId;
Settings.Current.LeaderboardCharacterId = null;
Settings.Save();
}
}
/// <summary>The leaderboard sits on the dark transition fill (carried over from the score tally /
/// grown over the menu), so the wipe square appears to persist as the background here.</summary>
public override bool CoveredBackground => true;
protected override void OnEnter()
{
var go = CreateUiRoot();
_screen = go.Components.Create<HighscoreScreen>();
_screen.Stage = this;
}
public override void Tick( float dt )
{
base.Tick( dt );
if ( _screen?.ModalOpen == true )
{
if ( InputState.BackJust )
_screen.CloseModal();
return;
}
// Space/Confirm watches the selected run's replay; W/S (Up/Down) selection + key-repeat
// is handled in the screen itself. Back returns to the screen that opened this board.
if ( InputState.ConfirmJust )
_screen?.LaunchSelected();
if ( InputState.BackJust )
GoBack();
}
public void GoBack() => FadeToStage(
ReturnScore is not null ? ReturnScore.Create( Manager )
: BackToWorkshop ? new WorkshopStage( Manager )
: BackToLevelSelect ? new LevelSelectStage( Manager )
: new TitleStage( Manager ) );
}